Migrate from @astrojs/image to Astro Assets and Unpic
@ -5,7 +5,6 @@ import { defineConfig } from 'astro/config';
|
|||||||
|
|
||||||
import tailwind from '@astrojs/tailwind';
|
import tailwind from '@astrojs/tailwind';
|
||||||
import sitemap from '@astrojs/sitemap';
|
import sitemap from '@astrojs/sitemap';
|
||||||
import image from '@astrojs/image';
|
|
||||||
import mdx from '@astrojs/mdx';
|
import mdx from '@astrojs/mdx';
|
||||||
import icon from 'astro-icon';
|
import icon from 'astro-icon';
|
||||||
import partytown from '@astrojs/partytown';
|
import partytown from '@astrojs/partytown';
|
||||||
@ -37,9 +36,7 @@ export default defineConfig({
|
|||||||
applyBaseStyles: false,
|
applyBaseStyles: false,
|
||||||
}),
|
}),
|
||||||
sitemap(),
|
sitemap(),
|
||||||
image({
|
|
||||||
serviceEntryPoint: '@astrojs/image/sharp',
|
|
||||||
}),
|
|
||||||
mdx(),
|
mdx(),
|
||||||
icon({
|
icon({
|
||||||
include: {
|
include: {
|
||||||
@ -72,6 +69,10 @@ export default defineConfig({
|
|||||||
remarkPlugins: [readingTimeRemarkPlugin],
|
remarkPlugins: [readingTimeRemarkPlugin],
|
||||||
},
|
},
|
||||||
|
|
||||||
|
experimental:{
|
||||||
|
assets: true
|
||||||
|
},
|
||||||
|
|
||||||
vite: {
|
vite: {
|
||||||
resolve: {
|
resolve: {
|
||||||
alias: {
|
alias: {
|
||||||
|
@ -13,7 +13,6 @@
|
|||||||
"lint:eslint": "eslint . --ext .js,.ts,.astro"
|
"lint:eslint": "eslint . --ext .js,.ts,.astro"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@astrojs/image": "^0.17.3",
|
|
||||||
"@astrojs/mdx": "^0.19.7",
|
"@astrojs/mdx": "^0.19.7",
|
||||||
"@astrojs/partytown": "^1.2.3",
|
"@astrojs/partytown": "^1.2.3",
|
||||||
"@astrojs/rss": "^2.4.4",
|
"@astrojs/rss": "^2.4.4",
|
||||||
@ -45,7 +44,8 @@
|
|||||||
"svgo": "3.0.2",
|
"svgo": "3.0.2",
|
||||||
"tailwind-merge": "^1.14.0",
|
"tailwind-merge": "^1.14.0",
|
||||||
"tailwindcss": "^3.3.3",
|
"tailwindcss": "^3.3.3",
|
||||||
"typescript": "^5.1.6"
|
"typescript": "^5.1.6",
|
||||||
|
"unpic": "^3.10.0"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=16.12.0"
|
"node": ">=16.12.0"
|
||||||
|
Before Width: | Height: | Size: 29 KiB |
Before Width: | Height: | Size: 33 KiB |
Before Width: | Height: | Size: 276 KiB |
Before Width: | Height: | Size: 136 KiB |
Before Width: | Height: | Size: 108 KiB |
Before Width: | Height: | Size: 91 KiB |
Before Width: | Height: | Size: 49 KiB |
@ -1,19 +1,19 @@
|
|||||||
---
|
---
|
||||||
import { Picture } from '@astrojs/image/components';
|
|
||||||
import type { ImageMetadata } from 'astro';
|
|
||||||
|
|
||||||
import { APP_BLOG_CONFIG } from '~/utils/config';
|
import { APP_BLOG_CONFIG } from '~/utils/config';
|
||||||
import type { Post } from '~/types';
|
import type { Post } from '~/types';
|
||||||
|
|
||||||
|
import Image from '~/components/common/Image.astro';
|
||||||
|
|
||||||
import { findImage } from '~/utils/images';
|
import { findImage } from '~/utils/images';
|
||||||
import { getPermalink } from '~/utils/permalinks';
|
import { getPermalink } from '~/utils/permalinks';
|
||||||
|
|
||||||
|
|
||||||
export interface Props {
|
export interface Props {
|
||||||
post: Post;
|
post: Post;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { post } = Astro.props;
|
const { post } = Astro.props;
|
||||||
const image = (await findImage(post.image)) as ImageMetadata | undefined;
|
const image = (await findImage(post.image));
|
||||||
---
|
---
|
||||||
|
|
||||||
<article class="mb-6 transition">
|
<article class="mb-6 transition">
|
||||||
@ -21,15 +21,15 @@ const image = (await findImage(post.image)) as ImageMetadata | undefined;
|
|||||||
{
|
{
|
||||||
image && (
|
image && (
|
||||||
<a href={getPermalink(post.permalink, 'post')}>
|
<a href={getPermalink(post.permalink, 'post')}>
|
||||||
<Picture
|
<Image
|
||||||
src={image}
|
src={image}
|
||||||
class="md:object-cover w-full md:w-auto md:h-full rounded shadow-lg bg-gray-400 dark:bg-slate-700"
|
class="w-full md:h-full rounded shadow-lg bg-gray-400 dark:bg-slate-700"
|
||||||
widths={[400, 900]}
|
widths={[400, 900]}
|
||||||
width={400}
|
width={400}
|
||||||
height={224}
|
|
||||||
sizes="(max-width: 900px) 400px, 900px"
|
sizes="(max-width: 900px) 400px, 900px"
|
||||||
alt={post.title}
|
alt={post.title}
|
||||||
aspectRatio="16:9"
|
aspectRatio="16:9"
|
||||||
|
layout="cover"
|
||||||
loading="lazy"
|
loading="lazy"
|
||||||
decoding="async"
|
decoding="async"
|
||||||
/>
|
/>
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
---
|
---
|
||||||
import { Picture } from '@astrojs/image/components';
|
|
||||||
import type { ImageMetadata } from 'astro';
|
import type { ImageMetadata } from 'astro';
|
||||||
import { Icon } from 'astro-icon/components';
|
import { Icon } from 'astro-icon/components';
|
||||||
|
import Image from '~/components/common/Image.astro';
|
||||||
import PostTags from '~/components/blog/Tags.astro';
|
import PostTags from '~/components/blog/Tags.astro';
|
||||||
|
|
||||||
import { APP_BLOG_CONFIG } from '~/utils/config';
|
import { APP_BLOG_CONFIG } from '~/utils/config';
|
||||||
@ -27,10 +27,11 @@ const link = APP_BLOG_CONFIG?.post?.isEnabled ? getPermalink(post.permalink, 'po
|
|||||||
<a class="relative block group" href={link ?? 'javascript:void(0)'}>
|
<a class="relative block group" href={link ?? 'javascript:void(0)'}>
|
||||||
<div class="relative h-0 pb-[56.25%] md:pb-[75%] md:h-72 lg:pb-[56.25%] overflow-hidden bg-gray-400 dark:bg-slate-700 rounded shadow-lg">
|
<div class="relative h-0 pb-[56.25%] md:pb-[75%] md:h-72 lg:pb-[56.25%] overflow-hidden bg-gray-400 dark:bg-slate-700 rounded shadow-lg">
|
||||||
{image && (
|
{image && (
|
||||||
<Picture
|
<Image
|
||||||
src={image}
|
src={image}
|
||||||
class="absolute inset-0 object-cover w-full h-full mb-6 rounded shadow-lg bg-gray-400 dark:bg-slate-700"
|
class="absolute inset-0 object-cover w-full h-full mb-6 rounded shadow-lg bg-gray-400 dark:bg-slate-700"
|
||||||
widths={[400, 900]}
|
widths={[400, 900]}
|
||||||
|
width={900}
|
||||||
sizes="(max-width: 900px) 400px, 900px"
|
sizes="(max-width: 900px) 400px, 900px"
|
||||||
alt={post.title}
|
alt={post.title}
|
||||||
aspectRatio="16:9"
|
aspectRatio="16:9"
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
---
|
---
|
||||||
import { Icon } from 'astro-icon/components';
|
import { Icon } from 'astro-icon/components';
|
||||||
import { Picture } from '@astrojs/image/components';
|
|
||||||
|
|
||||||
|
import Image from '~/components/common/Image.astro';
|
||||||
import PostTags from '~/components/blog/Tags.astro';
|
import PostTags from '~/components/blog/Tags.astro';
|
||||||
import SocialShare from '~/components/common/SocialShare.astro';
|
import SocialShare from '~/components/common/SocialShare.astro';
|
||||||
|
|
||||||
@ -53,19 +53,17 @@ const Content = post?.Content || null;
|
|||||||
|
|
||||||
{
|
{
|
||||||
post.image ? (
|
post.image ? (
|
||||||
<Picture
|
<Image
|
||||||
src={post.image}
|
src={post.image}
|
||||||
class="max-w-full lg:max-w-6xl mx-auto mb-6 sm:rounded-md bg-gray-400 dark:bg-slate-700"
|
class="max-w-full lg:max-w-[900px] mx-auto mb-6 sm:rounded-md bg-gray-400 dark:bg-slate-700"
|
||||||
widths={[400, 900]}
|
widths={[400, 900]}
|
||||||
sizes="(max-width: 900px) 400px, 900px"
|
sizes="(max-width: 900px) 400px, 900px"
|
||||||
alt={post?.excerpt || ''}
|
alt={post?.excerpt || ''}
|
||||||
loading="eager"
|
loading="eager"
|
||||||
aspectRatio={16 / 9}
|
|
||||||
width={900}
|
width={900}
|
||||||
height={506}
|
height={506}
|
||||||
loading="eager"
|
loading="eager"
|
||||||
decoding="async"
|
decoding="async"
|
||||||
background={undefined}
|
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<div class="max-w-3xl mx-auto px-4 sm:px-6">
|
<div class="max-w-3xl mx-auto px-4 sm:px-6">
|
||||||
|
52
src/components/common/Image.astro
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
---
|
||||||
|
import { findImage } from '~/utils/images';
|
||||||
|
import {
|
||||||
|
getImagesOptimized,
|
||||||
|
astroAsseetsOptimizer,
|
||||||
|
unpicOptimizer,
|
||||||
|
type ImageProps,
|
||||||
|
type AttributesProps
|
||||||
|
} from '~/utils/images-optimization';
|
||||||
|
|
||||||
|
type Props = ImageProps;
|
||||||
|
type ImageType = {
|
||||||
|
src: string;
|
||||||
|
attributes: AttributesProps;
|
||||||
|
}
|
||||||
|
|
||||||
|
const props = Astro.props;
|
||||||
|
|
||||||
|
if (props.alt === undefined || props.alt === null) {
|
||||||
|
throw new Error();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof props.width === 'string') {
|
||||||
|
props.width = parseInt(props.width);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof props.height === 'string') {
|
||||||
|
props.height = parseInt(props.height);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!props.loading) {
|
||||||
|
props.loading = 'lazy';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!props.decoding) {
|
||||||
|
props.decoding = 'async';
|
||||||
|
}
|
||||||
|
|
||||||
|
const _image = await findImage(props.src);
|
||||||
|
|
||||||
|
let image: ImageType | undefined = undefined;
|
||||||
|
|
||||||
|
if (_image !== null && typeof _image === 'object') {
|
||||||
|
image = await getImagesOptimized(_image, props, astroAsseetsOptimizer);
|
||||||
|
} else if (typeof _image === 'string' && (_image.startsWith('http://') || _image.startsWith('https://'))) {
|
||||||
|
image = await getImagesOptimized(_image, props, unpicOptimizer);
|
||||||
|
} else if (_image) {
|
||||||
|
image = await getImagesOptimized(_image, props);
|
||||||
|
}
|
||||||
|
---
|
||||||
|
|
||||||
|
{!image ? <Fragment /> : <img src={image.src} {...image.attributes} />}
|
@ -1,9 +1,9 @@
|
|||||||
---
|
---
|
||||||
import { Icon } from 'astro-icon/components';
|
import { Icon } from 'astro-icon/components';
|
||||||
import { Picture } from '@astrojs/image/components';
|
|
||||||
import type { Content } from '~/types';
|
import type { Content } from '~/types';
|
||||||
import Headline from '../ui/Headline.astro';
|
import Headline from '../ui/Headline.astro';
|
||||||
import WidgetWrapper from '../ui/WidgetWrapper.astro';
|
import WidgetWrapper from '../ui/WidgetWrapper.astro';
|
||||||
|
import Image from '~/components/common/Image.astro';
|
||||||
|
|
||||||
const {
|
const {
|
||||||
title = await Astro.slots.render('title'),
|
title = await Astro.slots.render('title'),
|
||||||
@ -70,13 +70,13 @@ const {
|
|||||||
{typeof image === 'string' ? (
|
{typeof image === 'string' ? (
|
||||||
<Fragment set:html={image} />
|
<Fragment set:html={image} />
|
||||||
) : (
|
) : (
|
||||||
<Picture
|
<Image
|
||||||
class="mx-auto w-full rounded-lg bg-gray-500 shadow-lg"
|
class="mx-auto w-full rounded-lg bg-gray-500 shadow-lg"
|
||||||
width={500}
|
width={500}
|
||||||
height={500}
|
height={500}
|
||||||
widths={[400, 768]}
|
widths={[400, 768]}
|
||||||
sizes="(max-width: 768px) 100vw, 432px"
|
sizes="(max-width: 768px) 100vw, 432px"
|
||||||
aspectRatio="500:500"
|
layout="responsive"
|
||||||
{...(image as any)}
|
{...(image as any)}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
---
|
---
|
||||||
import { Picture } from '@astrojs/image/components';
|
|
||||||
import Headline from '~/components/ui/Headline.astro';
|
import Headline from '~/components/ui/Headline.astro';
|
||||||
import ItemGrid from '~/components/ui/ItemGrid.astro';
|
import ItemGrid from '~/components/ui/ItemGrid.astro';
|
||||||
import WidgetWrapper from '~/components/ui/WidgetWrapper.astro';
|
import WidgetWrapper from '~/components/ui/WidgetWrapper.astro';
|
||||||
|
import Image from '~/components/common/Image.astro';
|
||||||
import type { Features } from '~/types';
|
import type { Features } from '~/types';
|
||||||
|
|
||||||
const {
|
const {
|
||||||
@ -39,12 +39,12 @@ const {
|
|||||||
{typeof image === 'string' ? (
|
{typeof image === 'string' ? (
|
||||||
<Fragment set:html={image} />
|
<Fragment set:html={image} />
|
||||||
) : (
|
) : (
|
||||||
<Picture
|
<Image
|
||||||
class="w-full h-80 object-cover rounded-xl mx-auto bg-gray-500 shadow-lg"
|
class="w-full h-80 object-cover rounded-xl mx-auto bg-gray-500 shadow-lg"
|
||||||
width={0}
|
width="auto"
|
||||||
height={320}
|
height={320}
|
||||||
widths={[400, 768]}
|
widths={[400, 768]}
|
||||||
aspectRatio="16:7"
|
layout="fullWidth"
|
||||||
{...(image as any)}
|
{...(image as any)}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
---
|
---
|
||||||
import { Icon } from 'astro-icon/components';
|
import { Icon } from 'astro-icon/components';
|
||||||
import { Picture } from '@astrojs/image/components';
|
import Image from '~/components/common/Image.astro';
|
||||||
|
|
||||||
const {
|
const {
|
||||||
title = await Astro.slots.render('title'),
|
title = await Astro.slots.render('title'),
|
||||||
@ -77,11 +77,10 @@ const {
|
|||||||
{typeof image === 'string' ? (
|
{typeof image === 'string' ? (
|
||||||
<Fragment set:html={image} />
|
<Fragment set:html={image} />
|
||||||
) : (
|
) : (
|
||||||
<Picture
|
<Image
|
||||||
class="mx-auto rounded-md w-full"
|
class="mx-auto rounded-md w-full"
|
||||||
widths={[400, 768, 1024, 2040]}
|
widths={[400, 768, 1024, 2040]}
|
||||||
sizes="(max-width: 767px) 400px, (max-width: 1023px) 768px, (max-width: 2039px) 1024px, 2040px"
|
sizes="(max-width: 767px) 400px, (max-width: 1023px) 768px, (max-width: 2039px) 1024px, 2040px"
|
||||||
aspectRatio={1024 / 576}
|
|
||||||
loading="eager"
|
loading="eager"
|
||||||
width={1024}
|
width={1024}
|
||||||
height={576}
|
height={576}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
---
|
---
|
||||||
import { Icon } from 'astro-icon/components';
|
import { Icon } from 'astro-icon/components';
|
||||||
import { Picture } from '@astrojs/image/components';
|
import Image from '~/components/common/Image.astro';
|
||||||
import { CallToAction } from '~/types';
|
import { CallToAction } from '~/types';
|
||||||
|
|
||||||
export interface Props {
|
export interface Props {
|
||||||
@ -88,11 +88,10 @@ const {
|
|||||||
{typeof image === 'string' ? (
|
{typeof image === 'string' ? (
|
||||||
<Fragment set:html={image} />
|
<Fragment set:html={image} />
|
||||||
) : (
|
) : (
|
||||||
<Picture
|
<Image
|
||||||
class="mx-auto rounded-md w-full"
|
class="mx-auto rounded-md w-full"
|
||||||
widths={[400, 768, 1024, 2040]}
|
widths={[400, 768, 1024, 2040]}
|
||||||
sizes="(max-width: 767px) 400px, (max-width: 1023px) 768px, (max-width: 2039px) 1024px, 2040px"
|
sizes="(max-width: 767px) 400px, (max-width: 1023px) 768px, (max-width: 2039px) 1024px, 2040px"
|
||||||
aspectRatio={600 / 600}
|
|
||||||
loading="eager"
|
loading="eager"
|
||||||
width={600}
|
width={600}
|
||||||
height={600}
|
height={600}
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
---
|
---
|
||||||
import { Picture } from '@astrojs/image/components';
|
|
||||||
import WidgetWrapper from "~/components/ui/WidgetWrapper.astro";
|
import WidgetWrapper from "~/components/ui/WidgetWrapper.astro";
|
||||||
import Timeline from "~/components/ui/Timeline.astro";
|
import Timeline from "~/components/ui/Timeline.astro";
|
||||||
import Headline from "~/components/ui/Headline.astro";
|
import Headline from "~/components/ui/Headline.astro";
|
||||||
|
import Image from '~/components/common/Image.astro';
|
||||||
import type { Steps } from "~/types";
|
import type { Steps } from "~/types";
|
||||||
|
|
||||||
const {
|
const {
|
||||||
@ -37,13 +37,13 @@ const {
|
|||||||
(typeof image === 'string' ? (
|
(typeof image === 'string' ? (
|
||||||
<Fragment set:html={image} />
|
<Fragment set:html={image} />
|
||||||
) : (
|
) : (
|
||||||
<Picture
|
<Image
|
||||||
class="inset-0 object-cover object-top w-full rounded-md shadow-lg md:absolute md:h-full bg-gray-400 dark:bg-slate-700"
|
class="inset-0 object-cover object-top w-full rounded-md shadow-lg md:absolute md:h-full bg-gray-400 dark:bg-slate-700"
|
||||||
widths={[400, 768]}
|
widths={[400, 768]}
|
||||||
sizes="(max-width: 768px) 100vw, 432px"
|
sizes="(max-width: 768px) 100vw, 432px"
|
||||||
aspectRatio="432:768"
|
|
||||||
width={432}
|
width={432}
|
||||||
height={768}
|
height={768}
|
||||||
|
layout="cover"
|
||||||
src={image?.src}
|
src={image?.src}
|
||||||
alt={image?.alt || ""}
|
alt={image?.alt || ""}
|
||||||
/>
|
/>
|
||||||
|
@ -1,9 +1,10 @@
|
|||||||
---
|
---
|
||||||
import Headline from '~/components/ui/Headline.astro';
|
import Headline from '~/components/ui/Headline.astro';
|
||||||
import WidgetWrapper from '~/components/ui/WidgetWrapper.astro';
|
import WidgetWrapper from '~/components/ui/WidgetWrapper.astro';
|
||||||
|
import CTA from '~/components/ui/CTA.astro';
|
||||||
|
import Image from '~/components/common/Image.astro';
|
||||||
import type { Testimonials } from '~/types';
|
import type { Testimonials } from '~/types';
|
||||||
import CTA from '../ui/CTA.astro';
|
|
||||||
import { Picture } from '@astrojs/image/components';
|
|
||||||
|
|
||||||
const {
|
const {
|
||||||
title = '',
|
title = '',
|
||||||
@ -43,12 +44,12 @@ const {
|
|||||||
{typeof image === 'string' ? (
|
{typeof image === 'string' ? (
|
||||||
<Fragment set:html={image} />
|
<Fragment set:html={image} />
|
||||||
) : (
|
) : (
|
||||||
<Picture
|
<Image
|
||||||
class="h-10 w-10 rounded-full border border-slate-200 dark:border-slate-600"
|
class="h-10 w-10 rounded-full border border-slate-200 dark:border-slate-600"
|
||||||
width={40}
|
width={40}
|
||||||
height={40}
|
height={40}
|
||||||
widths={[400, 768]}
|
widths={[400, 768]}
|
||||||
aspectRatio="1:1"
|
layout="fixed"
|
||||||
{...(image as any)}
|
{...(image as any)}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
publishDate: 2023-07-17T00:00:00Z
|
publishDate: 2023-07-17T00:00:00Z
|
||||||
title: AstroWind template in depth
|
title: AstroWind template in depth
|
||||||
excerpt: While easy to get started, Astrowind is quite complex internally. This page provides documentation on some of the more intricate parts.
|
excerpt: While easy to get started, Astrowind is quite complex internally. This page provides documentation on some of the more intricate parts.
|
||||||
image: ~/assets/images/stickers.jpg
|
image: https://images.unsplash.com/photo-1534307671554-9a6d81f4d629?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=1651&q=80
|
||||||
category: Documentation
|
category: Documentation
|
||||||
tags:
|
tags:
|
||||||
- astro
|
- astro
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
publishDate: 2023-01-12T00:00:00Z
|
publishDate: 2023-01-12T00:00:00Z
|
||||||
title: Get started with AstroWind to create a website using Astro and Tailwind CSS
|
title: Get started with AstroWind to create a website using Astro and Tailwind CSS
|
||||||
excerpt: Sint sit cillum pariatur eiusmod nulla pariatur ipsum. Sit laborum anim qui mollit tempor pariatur.
|
excerpt: Sint sit cillum pariatur eiusmod nulla pariatur ipsum. Sit laborum anim qui mollit tempor pariatur.
|
||||||
image: ~/assets/images/do-more.jpg
|
image: https://images.unsplash.com/photo-1516216628859-9bccecab13ca?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=2069&q=80
|
||||||
category: Tutorials
|
category: Tutorials
|
||||||
tags:
|
tags:
|
||||||
- astro
|
- astro
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
publishDate: 2023-01-06T00:00:00Z
|
publishDate: 2023-01-06T00:00:00Z
|
||||||
title: How to customize AstroWind template to suit your branding
|
title: How to customize AstroWind template to suit your branding
|
||||||
excerpt: Sint sit cillum pariatur eiusmod nulla pariatur ipsum. Sit laborum anim qui mollit tempor pariatur.
|
excerpt: Sint sit cillum pariatur eiusmod nulla pariatur ipsum. Sit laborum anim qui mollit tempor pariatur.
|
||||||
image: ~/assets/images/colors.jpg
|
image: https://images.unsplash.com/photo-1546984575-757f4f7c13cf?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=2070&q=80
|
||||||
tags:
|
tags:
|
||||||
- astro
|
- astro
|
||||||
- tailwind css
|
- tailwind css
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
publishDate: 2023-01-09T00:00:00Z
|
publishDate: 2023-01-09T00:00:00Z
|
||||||
title: Useful tools and resources to create a professional website
|
title: Useful tools and resources to create a professional website
|
||||||
excerpt: Sint sit cillum pariatur eiusmod nulla pariatur ipsum. Sit laborum anim qui mollit tempor pariatur.
|
excerpt: Sint sit cillum pariatur eiusmod nulla pariatur ipsum. Sit laborum anim qui mollit tempor pariatur.
|
||||||
image: ~/assets/images/tools.jpg
|
image: https://images.unsplash.com/photo-1637144113536-9c6e917be447?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=1674&q=80
|
||||||
tags:
|
tags:
|
||||||
- front-end
|
- front-end
|
||||||
- tools
|
- tools
|
||||||
|
2
src/env.d.ts
vendored
@ -1,3 +1,3 @@
|
|||||||
// eslint-disable-next-line @typescript-eslint/triple-slash-reference
|
// eslint-disable-next-line @typescript-eslint/triple-slash-reference
|
||||||
/// <reference path="../.astro/types.d.ts" />
|
/// <reference path="../.astro/types.d.ts" />
|
||||||
/// <reference types="@astrojs/image/client" />
|
/// <reference types="astro/client-image" />
|
||||||
|
@ -28,7 +28,7 @@ const metadata = merge(
|
|||||||
},
|
},
|
||||||
openGraph: {
|
openGraph: {
|
||||||
type: 'article',
|
type: 'article',
|
||||||
...(image ? { images: [{ url: image?.src, width: image?.width, height: image?.height }] } : {}),
|
...(image ? { images: [{ url: image, width: image?.width, height: image?.height }] } : {}),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{ ...(post?.metadata ? { ...post.metadata, canonical: post.metadata?.canonical || url } : {}) }
|
{ ...(post?.metadata ? { ...post.metadata, canonical: post.metadata?.canonical || url } : {}) }
|
||||||
|
@ -14,7 +14,7 @@ const metadata = {
|
|||||||
<Layout metadata={metadata}>
|
<Layout metadata={metadata}>
|
||||||
<!-- Hero Widget ******************* -->
|
<!-- Hero Widget ******************* -->
|
||||||
|
|
||||||
<Hero image={{ src: import('~/assets/images/caos.jpg'), alt: 'Caos Image' }}>
|
<Hero image={{ src: 'https://images.unsplash.com/photo-1559136555-9303baea8ebd?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=2070&q=80', alt: 'Caos Image' }}>
|
||||||
<Fragment slot="title">
|
<Fragment slot="title">
|
||||||
Elevate your online presence with our <br />
|
Elevate your online presence with our <br />
|
||||||
<span class="text-accent dark:text-white highlight"> Beautiful Website Templates</span>
|
<span class="text-accent dark:text-white highlight"> Beautiful Website Templates</span>
|
||||||
@ -112,7 +112,7 @@ const metadata = {
|
|||||||
},
|
},
|
||||||
]}
|
]}
|
||||||
image={{
|
image={{
|
||||||
src: import('~/assets/images/colors.jpg'),
|
src: '~/assets/images/colors.jpg',
|
||||||
alt: 'Colorful Image',
|
alt: 'Colorful Image',
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
@ -24,7 +24,7 @@ const metadata = {
|
|||||||
<Hero
|
<Hero
|
||||||
callToAction={{ text: 'Get template', href: 'https://github.com/onwidget/astrowind', icon: 'tabler:download' }}
|
callToAction={{ text: 'Get template', href: 'https://github.com/onwidget/astrowind', icon: 'tabler:download' }}
|
||||||
callToAction2={{ text: 'Learn more', href: '#features' }}
|
callToAction2={{ text: 'Learn more', href: '#features' }}
|
||||||
image={{ src: import('~/assets/images/hero.png'), alt: 'AstroWind Hero Image' }}
|
image={{ src: '~/assets/images/hero.png', alt: 'AstroWind Hero Image' }}
|
||||||
>
|
>
|
||||||
<Fragment slot="title">
|
<Fragment slot="title">
|
||||||
Free template for <span class="hidden xl:inline">creating websites with</span>
|
Free template for <span class="hidden xl:inline">creating websites with</span>
|
||||||
@ -116,7 +116,7 @@ const metadata = {
|
|||||||
},
|
},
|
||||||
]}
|
]}
|
||||||
image={{
|
image={{
|
||||||
src: import('~/assets/images/caos.jpg'),
|
src: 'https://images.unsplash.com/photo-1517134191118-9d595e4c8c2b?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=2070&q=80',
|
||||||
alt: 'Colorful Image',
|
alt: 'Colorful Image',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
@ -157,7 +157,7 @@ const metadata = {
|
|||||||
},
|
},
|
||||||
]}
|
]}
|
||||||
image={{
|
image={{
|
||||||
src: import('~/assets/images/vintage.jpg'),
|
src: 'https://images.unsplash.com/photo-1483058712412-4245e9b90334?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=2070&q=80',
|
||||||
alt: 'Vintage Image',
|
alt: 'Vintage Image',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
@ -195,7 +195,7 @@ const metadata = {
|
|||||||
},
|
},
|
||||||
]}
|
]}
|
||||||
image={{
|
image={{
|
||||||
src: import('~/assets/images/creativity.jpg'),
|
src: 'https://images.unsplash.com/photo-1616198814651-e71f960c3180?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=987&q=80',
|
||||||
alt: 'Steps image',
|
alt: 'Steps image',
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
@ -40,7 +40,7 @@ const metadata = {
|
|||||||
<Hero2
|
<Hero2
|
||||||
callToAction={{ text: 'Download App', href: 'https://github.com/onwidget/astrowind', icon: 'tabler:download' }}
|
callToAction={{ text: 'Download App', href: 'https://github.com/onwidget/astrowind', icon: 'tabler:download' }}
|
||||||
callToAction2={{ text: 'Learn more', href: '#features' }}
|
callToAction2={{ text: 'Learn more', href: '#features' }}
|
||||||
image={{ src: import('~/assets/images/hero.png'), alt: 'AstroWind Hero Image' }}
|
image={{ src: 'https://images.unsplash.com/photo-1535303311164-664fc9ec6532?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=987&q=80', alt: 'AstroWind Hero Image' }}
|
||||||
>
|
>
|
||||||
<Fragment slot="title">
|
<Fragment slot="title">
|
||||||
Free template for <span class="hidden lg:inline">create your website <br />with</span>
|
Free template for <span class="hidden lg:inline">create your website <br />with</span>
|
||||||
@ -91,7 +91,7 @@ const metadata = {
|
|||||||
},
|
},
|
||||||
]}
|
]}
|
||||||
image={{
|
image={{
|
||||||
src: import('~/assets/images/colors.jpg'),
|
src: 'https://images.unsplash.com/photo-1521517407911-565264e7d82d?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=2069&q=80',
|
||||||
alt: 'Colorful Image',
|
alt: 'Colorful Image',
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
@ -99,6 +99,7 @@ const metadata = {
|
|||||||
<!-- Content Widget **************** -->
|
<!-- Content Widget **************** -->
|
||||||
|
|
||||||
<Content
|
<Content
|
||||||
|
isReversed
|
||||||
items={[
|
items={[
|
||||||
{
|
{
|
||||||
title: 'High-Quality Designs',
|
title: 'High-Quality Designs',
|
||||||
@ -128,7 +129,7 @@ const metadata = {
|
|||||||
},
|
},
|
||||||
]}
|
]}
|
||||||
image={{
|
image={{
|
||||||
src: import('~/assets/images/caos.jpg'),
|
src: 'https://images.unsplash.com/photo-1576153192621-7a3be10b356e?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=1674&q=80',
|
||||||
alt: 'Colorful Image',
|
alt: 'Colorful Image',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
@ -140,7 +141,6 @@ const metadata = {
|
|||||||
<!-- Content Widget **************** -->
|
<!-- Content Widget **************** -->
|
||||||
|
|
||||||
<Content
|
<Content
|
||||||
isReversed
|
|
||||||
isAfterContent
|
isAfterContent
|
||||||
items={[
|
items={[
|
||||||
{
|
{
|
||||||
@ -171,7 +171,7 @@ const metadata = {
|
|||||||
},
|
},
|
||||||
]}
|
]}
|
||||||
image={{
|
image={{
|
||||||
src: import('~/assets/images/vintage.jpg'),
|
src: 'https://images.unsplash.com/photo-1453738773917-9c3eff1db985?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=2070&q=80',
|
||||||
alt: 'Vintage Image',
|
alt: 'Vintage Image',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
@ -202,7 +202,7 @@ const metadata = {
|
|||||||
name: 'Cary Kennedy',
|
name: 'Cary Kennedy',
|
||||||
job: 'Film director',
|
job: 'Film director',
|
||||||
image: {
|
image: {
|
||||||
src: import('~/assets/images/colors.jpg'),
|
src: 'https://images.unsplash.com/photo-1438761681033-6461ffad8d80?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=2070&q=80',
|
||||||
alt: 'Cary Kennedy Image',
|
alt: 'Cary Kennedy Image',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -212,7 +212,7 @@ const metadata = {
|
|||||||
name: 'Josh Wilkinson',
|
name: 'Josh Wilkinson',
|
||||||
job: 'Product Manager',
|
job: 'Product Manager',
|
||||||
image: {
|
image: {
|
||||||
src: import('~/assets/images/vintage.jpg'),
|
src: 'https://images.unsplash.com/flagged/photo-1570612861542-284f4c12e75f?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=2070&q=80',
|
||||||
alt: 'Josh Wilkinson Image',
|
alt: 'Josh Wilkinson Image',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -222,7 +222,7 @@ const metadata = {
|
|||||||
name: 'Sidney Hansen',
|
name: 'Sidney Hansen',
|
||||||
job: 'Decorator',
|
job: 'Decorator',
|
||||||
image: {
|
image: {
|
||||||
src: import('~/assets/images/caos.jpg'),
|
src: 'https://images.unsplash.com/photo-1512361436605-a484bdb34b5f?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=2070&q=80',
|
||||||
alt: 'Sidney Hansen Image',
|
alt: 'Sidney Hansen Image',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -3,6 +3,7 @@ import Layout from '~/layouts/PageLayout.astro';
|
|||||||
|
|
||||||
import Header from '~/components/widgets/Header.astro';
|
import Header from '~/components/widgets/Header.astro';
|
||||||
import Hero2 from '~/components/widgets/Hero2.astro';
|
import Hero2 from '~/components/widgets/Hero2.astro';
|
||||||
|
import Features from '~/components/widgets/Features.astro';
|
||||||
import Steps2 from '~/components/widgets/Steps2.astro';
|
import Steps2 from '~/components/widgets/Steps2.astro';
|
||||||
import Content from '~/components/widgets/Content.astro';
|
import Content from '~/components/widgets/Content.astro';
|
||||||
import CallToAction from '~/components/widgets/CallToAction.astro';
|
import CallToAction from '~/components/widgets/CallToAction.astro';
|
||||||
@ -39,7 +40,7 @@ const metadata = {
|
|||||||
<Hero2
|
<Hero2
|
||||||
callToAction={{ text: 'Get template', href: 'https://github.com/onwidget/astrowind', icon: 'tabler:download' }}
|
callToAction={{ text: 'Get template', href: 'https://github.com/onwidget/astrowind', icon: 'tabler:download' }}
|
||||||
callToAction2={{ text: 'Learn more', href: '#features' }}
|
callToAction2={{ text: 'Learn more', href: '#features' }}
|
||||||
image={{ src: import('~/assets/images/hero.png'), alt: 'AstroWind Hero Image' }}
|
image={{ src: 'https://images.unsplash.com/photo-1580481072645-022f9a6dbf27?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=2070&q=80', alt: 'AstroWind Hero Image' }}
|
||||||
>
|
>
|
||||||
<Fragment slot="title">
|
<Fragment slot="title">
|
||||||
Free template for <br />
|
Free template for <br />
|
||||||
@ -56,6 +57,51 @@ const metadata = {
|
|||||||
</Fragment>
|
</Fragment>
|
||||||
</Hero2>
|
</Hero2>
|
||||||
|
|
||||||
|
<!-- Features Widget *************** -->
|
||||||
|
|
||||||
|
<Features
|
||||||
|
id="features"
|
||||||
|
tagline="Features"
|
||||||
|
title="Main features of our templates"
|
||||||
|
subtitle="Possess several key characteristics to effectively cater to the needs of startups and entrepreneurs."
|
||||||
|
columns={3}
|
||||||
|
items={[
|
||||||
|
{
|
||||||
|
title: 'Modern and Professional Design',
|
||||||
|
description:
|
||||||
|
'Have a contemporary design that reflects current design trends and gives a professional impression.',
|
||||||
|
icon: 'tabler:artboard',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Responsive and Mobile-Friendly',
|
||||||
|
description: 'Adapt seamlessly to different screen sizes and devices to ensure a consistent experience.',
|
||||||
|
icon: 'tabler:picture-in-picture',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Customizability',
|
||||||
|
description:
|
||||||
|
'Easily customizable, allowing users to adapt the design, colors, typography, and content to match their brand identity.',
|
||||||
|
icon: 'tabler:adjustments-horizontal',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Fast Loading Times',
|
||||||
|
description: 'Optimized for speed to ensure a smooth user experience and favorable search engine rankings.',
|
||||||
|
icon: 'tabler:rocket',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Search Engine Optimization (SEO)',
|
||||||
|
description:
|
||||||
|
'Incorporate SEO best practices in template structure and code to improve visibility in search engine results.',
|
||||||
|
icon: 'tabler:arrows-right-left',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Compatibility',
|
||||||
|
description: 'The templates work seamlessly across various content management systems and website builders.',
|
||||||
|
icon: 'tabler:plug-connected',
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
|
||||||
<!-- Content Widget **************** -->
|
<!-- Content Widget **************** -->
|
||||||
|
|
||||||
<Content
|
<Content
|
||||||
@ -78,7 +124,7 @@ const metadata = {
|
|||||||
},
|
},
|
||||||
]}
|
]}
|
||||||
image={{
|
image={{
|
||||||
src: import('~/assets/images/caos.jpg'),
|
src: 'https://images.unsplash.com/photo-1620558138198-cfb9b4f3c294?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=1671&q=80',
|
||||||
alt: 'Colorful Image',
|
alt: 'Colorful Image',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
@ -115,7 +161,7 @@ const metadata = {
|
|||||||
},
|
},
|
||||||
]}
|
]}
|
||||||
image={{
|
image={{
|
||||||
src: import('~/assets/images/caos.jpg'),
|
src: 'https://images.unsplash.com/photo-1531973486364-5fa64260d75b?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=1658&q=80',
|
||||||
alt: 'Colorful Image',
|
alt: 'Colorful Image',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
@ -153,7 +199,7 @@ const metadata = {
|
|||||||
},
|
},
|
||||||
]}
|
]}
|
||||||
image={{
|
image={{
|
||||||
src: import('~/assets/images/caos.jpg'),
|
src: 'https://images.unsplash.com/photo-1635070041078-e363dbe005cb?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=2070&q=80',
|
||||||
alt: 'Colorful Image',
|
alt: 'Colorful Image',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
@ -43,14 +43,18 @@ const metadata = {
|
|||||||
</Fragment>
|
</Fragment>
|
||||||
|
|
||||||
<Fragment slot="image">
|
<Fragment slot="image">
|
||||||
|
<div class="relative h-0 pb-[56.25%]">
|
||||||
<iframe
|
<iframe
|
||||||
width="560"
|
width="560"
|
||||||
height="315"
|
height="315"
|
||||||
src="https://www.youtube.com/embed/dsTXcSeAZq8"
|
src="https://www.youtube.com/embed/dsTXcSeAZq8"
|
||||||
title="YouTube video player"
|
title="YouTube video player"
|
||||||
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
|
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture;"
|
||||||
allowfullscreen
|
allowfullscreen
|
||||||
style="width:100%"></iframe>
|
class="absolute top-0 left-0 w-full h-full"
|
||||||
|
>
|
||||||
|
</iframe>
|
||||||
|
</div>
|
||||||
</Fragment>
|
</Fragment>
|
||||||
</Hero>
|
</Hero>
|
||||||
|
|
||||||
|
@ -178,7 +178,7 @@ const metadata = {
|
|||||||
},
|
},
|
||||||
]}
|
]}
|
||||||
image={{
|
image={{
|
||||||
src: import('~/assets/images/creativity.jpg'),
|
src: 'https://images.unsplash.com/photo-1536816579748-4ecb3f03d72a?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=987&q=80',
|
||||||
alt: 'Steps image',
|
alt: 'Steps image',
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
321
src/utils/images-optimization.ts
Normal file
@ -0,0 +1,321 @@
|
|||||||
|
import { getImage } from 'astro:assets';
|
||||||
|
import { transformUrl, parseUrl } from 'unpic';
|
||||||
|
|
||||||
|
import type { ImageMetadata } from 'astro';
|
||||||
|
import type { HTMLAttributes } from 'astro/types';
|
||||||
|
|
||||||
|
type Layout = 'fixed' | 'constrained' | 'fullWidth' | 'cover' | 'responsive' | 'contained';
|
||||||
|
|
||||||
|
export interface AttributesProps extends HTMLAttributes<'img'> {}
|
||||||
|
|
||||||
|
export interface ImageProps extends Omit<HTMLAttributes<'img'>, 'src'> {
|
||||||
|
src?: string | ImageMetadata | null;
|
||||||
|
width?: string | number | null;
|
||||||
|
height?: string | number | null;
|
||||||
|
alt?: string | null;
|
||||||
|
loading?: 'eager' | 'lazy' | null;
|
||||||
|
decoding?: 'sync' | 'async' | 'auto' | null;
|
||||||
|
style?: string;
|
||||||
|
srcset?: string | null;
|
||||||
|
sizes?: string | null;
|
||||||
|
fetchpriority?: 'high' | 'low' | 'auto' | null;
|
||||||
|
|
||||||
|
layout?: Layout;
|
||||||
|
widths?: number[] | null;
|
||||||
|
aspectRatio?: string | number | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type ImagesOptimizer = (
|
||||||
|
image: ImageMetadata | string,
|
||||||
|
breakpoints: number[],
|
||||||
|
width?: number,
|
||||||
|
height?: number
|
||||||
|
) => Promise<Array<{ src: string; width: number }>>;
|
||||||
|
|
||||||
|
/* ******* */
|
||||||
|
const config = {
|
||||||
|
// FIXME: Use this when image.width is minor than deviceSizes
|
||||||
|
imageSizes: [16, 32, 48, 64, 96, 128, 256, 384],
|
||||||
|
|
||||||
|
deviceSizes: [
|
||||||
|
640, // older and lower-end phones
|
||||||
|
750, // iPhone 6-8
|
||||||
|
828, // iPhone XR/11
|
||||||
|
960, // older horizontal phones
|
||||||
|
1080, // iPhone 6-8 Plus
|
||||||
|
1280, // 720p
|
||||||
|
1668, // Various iPads
|
||||||
|
1920, // 1080p
|
||||||
|
2048, // QXGA
|
||||||
|
2560, // WQXGA
|
||||||
|
3200, // QHD+
|
||||||
|
3840, // 4K
|
||||||
|
4480, // 4.5K
|
||||||
|
5120, // 5K
|
||||||
|
6016, // 6K
|
||||||
|
],
|
||||||
|
|
||||||
|
formats: ['image/webp'],
|
||||||
|
};
|
||||||
|
|
||||||
|
const computeHeight = (width: number, aspectRatio: number) => {
|
||||||
|
return Math.floor(width / aspectRatio);
|
||||||
|
};
|
||||||
|
|
||||||
|
const parseAspectRatio = (aspectRatio: number | string | null | undefined): number | undefined => {
|
||||||
|
if (typeof aspectRatio === 'number') return aspectRatio;
|
||||||
|
|
||||||
|
if (typeof aspectRatio === 'string') {
|
||||||
|
const match = aspectRatio.match(/(\d+)\s*[/:]\s*(\d+)/);
|
||||||
|
|
||||||
|
if (match) {
|
||||||
|
const [, num, den] = match.map(Number);
|
||||||
|
if (den && !isNaN(num)) return num / den;
|
||||||
|
} else {
|
||||||
|
const numericValue = parseFloat(aspectRatio);
|
||||||
|
if (!isNaN(numericValue)) return numericValue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return undefined;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the `sizes` attribute for an image, based on the layout and width
|
||||||
|
*/
|
||||||
|
export const getSizes = (width?: number, layout?: Layout): string | undefined => {
|
||||||
|
if (!width || !layout) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
switch (layout) {
|
||||||
|
// If screen is wider than the max size, image width is the max size,
|
||||||
|
// otherwise it's the width of the screen
|
||||||
|
case `constrained`:
|
||||||
|
return `(min-width: ${width}px) ${width}px, 100vw`;
|
||||||
|
|
||||||
|
// Image is always the same width, whatever the size of the screen
|
||||||
|
case `fixed`:
|
||||||
|
return `${width}px`;
|
||||||
|
|
||||||
|
// Image is always the width of the screen
|
||||||
|
case `fullWidth`:
|
||||||
|
return `100vw`;
|
||||||
|
|
||||||
|
default:
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const pixelate = (value?: number) => (value || value === 0 ? `${value}px` : undefined);
|
||||||
|
|
||||||
|
const getStyle = ({
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
aspectRatio,
|
||||||
|
layout,
|
||||||
|
objectFit = 'cover',
|
||||||
|
objectPosition = 'center',
|
||||||
|
background,
|
||||||
|
}: {
|
||||||
|
width?: number;
|
||||||
|
height?: number;
|
||||||
|
aspectRatio?: number;
|
||||||
|
objectFit?: string;
|
||||||
|
objectPosition?: string;
|
||||||
|
layout?: string;
|
||||||
|
background?: string;
|
||||||
|
}) => {
|
||||||
|
const styleEntries: Array<[prop: string, value: string | undefined]> = [
|
||||||
|
['object-fit', objectFit],
|
||||||
|
['object-position', objectPosition],
|
||||||
|
];
|
||||||
|
|
||||||
|
// If background is a URL, set it to cover the image and not repeat
|
||||||
|
if (background?.startsWith('https:') || background?.startsWith('http:') || background?.startsWith('data:')) {
|
||||||
|
styleEntries.push(['background-image', `url(${background})`]);
|
||||||
|
styleEntries.push(['background-size', 'cover']);
|
||||||
|
styleEntries.push(['background-repeat', 'no-repeat']);
|
||||||
|
} else {
|
||||||
|
styleEntries.push(['background', background]);
|
||||||
|
}
|
||||||
|
if (layout === 'fixed') {
|
||||||
|
styleEntries.push(['width', pixelate(width)]);
|
||||||
|
styleEntries.push(['height', pixelate(height)]);
|
||||||
|
styleEntries.push(['object-position', 'top left']);
|
||||||
|
}
|
||||||
|
if (layout === 'constrained') {
|
||||||
|
styleEntries.push(['max-width', pixelate(width)]);
|
||||||
|
styleEntries.push(['max-height', pixelate(height)]);
|
||||||
|
styleEntries.push(['aspect-ratio', aspectRatio ? `${aspectRatio}` : undefined]);
|
||||||
|
styleEntries.push(['width', '100%']);
|
||||||
|
}
|
||||||
|
if (layout === 'fullWidth') {
|
||||||
|
styleEntries.push(['width', '100%']);
|
||||||
|
styleEntries.push(['aspect-ratio', aspectRatio ? `${aspectRatio}` : undefined]);
|
||||||
|
styleEntries.push(['height', pixelate(height)]);
|
||||||
|
}
|
||||||
|
if (layout === 'responsive') {
|
||||||
|
styleEntries.push(['width', '100%']);
|
||||||
|
styleEntries.push(['height', 'auto']);
|
||||||
|
styleEntries.push(['aspect-ratio', aspectRatio ? `${aspectRatio}` : undefined]);
|
||||||
|
}
|
||||||
|
if (layout === 'contained') {
|
||||||
|
styleEntries.push(['max-width', '100%']);
|
||||||
|
styleEntries.push(['max-height', '100%']);
|
||||||
|
styleEntries.push(['object-fit', 'contain']);
|
||||||
|
styleEntries.push(['aspect-ratio', aspectRatio ? `${aspectRatio}` : undefined]);
|
||||||
|
}
|
||||||
|
if (layout === 'cover') {
|
||||||
|
styleEntries.push(['max-width', '100%']);
|
||||||
|
styleEntries.push(['max-height', '100%']);
|
||||||
|
}
|
||||||
|
|
||||||
|
const styles = Object.fromEntries(styleEntries.filter(([, value]) => value));
|
||||||
|
|
||||||
|
return Object.entries(styles)
|
||||||
|
.map(([key, value]) => `${key}: ${value};`)
|
||||||
|
.join(' ');
|
||||||
|
};
|
||||||
|
|
||||||
|
const getBreakpoints = ({
|
||||||
|
width,
|
||||||
|
breakpoints,
|
||||||
|
layout,
|
||||||
|
}: {
|
||||||
|
width?: number;
|
||||||
|
breakpoints?: number[];
|
||||||
|
layout: Layout;
|
||||||
|
}): number[] => {
|
||||||
|
if (layout === 'fullWidth' || layout === 'cover' || layout === 'responsive' || layout === 'contained') {
|
||||||
|
return breakpoints || config.deviceSizes;
|
||||||
|
}
|
||||||
|
if (!width) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
const doubleWidth = width * 2;
|
||||||
|
if (layout === 'fixed') {
|
||||||
|
return [width, doubleWidth];
|
||||||
|
}
|
||||||
|
if (layout === 'constrained') {
|
||||||
|
return [
|
||||||
|
// Always include the image at 1x and 2x the specified width
|
||||||
|
width,
|
||||||
|
doubleWidth,
|
||||||
|
// Filter out any resolutions that are larger than the double-res image
|
||||||
|
...(breakpoints || config.deviceSizes).filter((w) => w < doubleWidth),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
return [];
|
||||||
|
};
|
||||||
|
|
||||||
|
/* ** */
|
||||||
|
export const astroAsseetsOptimizer: ImagesOptimizer = async (image, breakpoints) => {
|
||||||
|
if (!image || typeof image === 'string') {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
return Promise.all(
|
||||||
|
breakpoints.map(async (w: number) => {
|
||||||
|
const url = (await getImage({ src: image, width: w })).src;
|
||||||
|
return {
|
||||||
|
src: url,
|
||||||
|
width: w,
|
||||||
|
};
|
||||||
|
})
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
/* ** */
|
||||||
|
export const unpicOptimizer: ImagesOptimizer = async (image, breakpoints, width, height) => {
|
||||||
|
if (!image || typeof image !== 'string') {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
const urlParsed = parseUrl(image);
|
||||||
|
if (!urlParsed) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
return Promise.all(
|
||||||
|
breakpoints.map(async (w: number) => {
|
||||||
|
const url =
|
||||||
|
(await transformUrl({
|
||||||
|
url: image,
|
||||||
|
width: w,
|
||||||
|
height: width && height ? computeHeight(w, width / height) : height,
|
||||||
|
cdn: urlParsed.cdn,
|
||||||
|
})) || image;
|
||||||
|
return {
|
||||||
|
src: String(url),
|
||||||
|
width: w,
|
||||||
|
};
|
||||||
|
})
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
/* ** */
|
||||||
|
export async function getImagesOptimized(
|
||||||
|
image: ImageMetadata | string,
|
||||||
|
{ src: _, width, height, sizes, aspectRatio, widths, layout = 'constrained', style = '', ...rest }: ImageProps,
|
||||||
|
transform: ImagesOptimizer = () => Promise.resolve([])
|
||||||
|
): Promise<{ src: string; attributes: AttributesProps }> {
|
||||||
|
if (typeof image !== 'string') {
|
||||||
|
width ||= Number(image.width) || undefined;
|
||||||
|
height ||= typeof width === 'number' ? computeHeight(width, image.width / image.height) : undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
width = (width && Number(width)) || undefined;
|
||||||
|
height = (height && Number(height)) || undefined;
|
||||||
|
|
||||||
|
widths ||= config.deviceSizes;
|
||||||
|
sizes ||= getSizes(Number(width) || undefined, layout);
|
||||||
|
aspectRatio = parseAspectRatio(aspectRatio);
|
||||||
|
|
||||||
|
// Calculate dimensions from aspect ratio
|
||||||
|
if (aspectRatio) {
|
||||||
|
if (width) {
|
||||||
|
if (height) {
|
||||||
|
/* empty */
|
||||||
|
} else {
|
||||||
|
height = width / aspectRatio;
|
||||||
|
}
|
||||||
|
} else if (height) {
|
||||||
|
width = Number(height * aspectRatio);
|
||||||
|
} else if (layout !== 'fullWidth') {
|
||||||
|
// Fullwidth images have 100% width, so aspectRatio is applicable
|
||||||
|
console.error('When aspectRatio is set, either width or height must also be set');
|
||||||
|
console.error('Image', image);
|
||||||
|
}
|
||||||
|
} else if (width && height) {
|
||||||
|
aspectRatio = width / height;
|
||||||
|
} else if (layout !== 'fullWidth') {
|
||||||
|
// Fullwidth images don't need dimensions
|
||||||
|
console.error('Either aspectRatio or both width and height must be set');
|
||||||
|
console.error('Image', image);
|
||||||
|
}
|
||||||
|
|
||||||
|
let breakpoints = getBreakpoints({ width: width, breakpoints: widths, layout: layout });
|
||||||
|
breakpoints = [...new Set(breakpoints)].sort((a, b) => a - b);
|
||||||
|
|
||||||
|
const srcset = (await transform(image, breakpoints, Number(width) || undefined, Number(height) || undefined))
|
||||||
|
.map(({ src, width }) => `${src} ${width}w`)
|
||||||
|
.join(', ');
|
||||||
|
|
||||||
|
return {
|
||||||
|
src: typeof image === 'string' ? image : image.src,
|
||||||
|
attributes: {
|
||||||
|
width: width,
|
||||||
|
height: height,
|
||||||
|
srcset: srcset || undefined,
|
||||||
|
sizes: sizes,
|
||||||
|
style: `${getStyle({
|
||||||
|
width: width,
|
||||||
|
height: height,
|
||||||
|
aspectRatio: aspectRatio,
|
||||||
|
layout: layout,
|
||||||
|
})}${style ?? ''}`,
|
||||||
|
...rest,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
@ -1,11 +1,11 @@
|
|||||||
import { getImage } from '@astrojs/image';
|
import { getImage } from 'astro:assets';
|
||||||
import type { OpenGraph } from '@astrolib/seo/src/types';
|
|
||||||
import type { ImageMetadata } from 'astro';
|
import type { ImageMetadata } from 'astro';
|
||||||
|
import type { OpenGraph } from '@astrolib/seo/src/types';
|
||||||
|
|
||||||
const load = async function () {
|
const load = async function () {
|
||||||
let images: Record<string, () => Promise<unknown>> | undefined = undefined;
|
let images: Record<string, () => Promise<unknown>> | undefined = undefined;
|
||||||
try {
|
try {
|
||||||
images = import.meta.glob('~/assets/images/**');
|
images = import.meta.glob('~/assets/images/**/*.{jpeg,jpg,png,tiff,webp,gif,svg,JPEG,JPG,PNG,TIFF,WEBP,GIF,SVG}');
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// continue regardless of error
|
// continue regardless of error
|
||||||
}
|
}
|
||||||
@ -21,24 +21,27 @@ export const fetchLocalImages = async () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
/** */
|
/** */
|
||||||
export const findImage = async (imagePath?: string) => {
|
export const findImage = async (imagePath?: string | ImageMetadata | null): Promise<string | ImageMetadata | undefined | null> => {
|
||||||
|
// Not string
|
||||||
if (typeof imagePath !== 'string') {
|
if (typeof imagePath !== 'string') {
|
||||||
return null;
|
return imagePath;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Absolute paths
|
||||||
if (imagePath.startsWith('http://') || imagePath.startsWith('https://') || imagePath.startsWith('/')) {
|
if (imagePath.startsWith('http://') || imagePath.startsWith('https://') || imagePath.startsWith('/')) {
|
||||||
return imagePath;
|
return imagePath;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!imagePath.startsWith('~/assets')) {
|
// Relative paths or not "~/assets/"
|
||||||
return null;
|
if (!imagePath.startsWith('~/assets/images')) {
|
||||||
} // For now only consume images using ~/assets alias (or absolute)
|
return imagePath;
|
||||||
|
}
|
||||||
|
|
||||||
const images = await fetchLocalImages();
|
const images = await fetchLocalImages();
|
||||||
const key = imagePath.replace('~/', '/src/');
|
const key = imagePath.replace('~/', '/src/');
|
||||||
|
|
||||||
return images && typeof images[key] === 'function'
|
return images && typeof images[key] === 'function'
|
||||||
? ((await images[key]()) as { default: unknown })['default']
|
? ((await images[key]()) as { default: ImageMetadata })['default']
|
||||||
: null;
|
: null;
|
||||||
};
|
};
|
||||||
|
|
||||||
|