-
-
Notifications
You must be signed in to change notification settings - Fork 3.1k
feat(assets): Add support for srcset and a Picture component
#8620
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
77f7774
cccd165
83eac94
207edd6
050143d
3809a45
7398d63
db0c862
dc21d08
2022ce5
73a0629
fc86fdf
bfe8e09
68a8830
0726390
65b7464
370fd1c
41f5fc6
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,56 @@ | ||
| --- | ||
| 'astro': minor | ||
| --- | ||
|
|
||
| Adds experimental support for generating `srcset` attributes and a new `<Picture />` component. | ||
|
|
||
| ## `srcset` support | ||
|
|
||
| Two new properties have been added to `Image` and `getImage()`: `densities` and `widths`. | ||
|
|
||
| These properties can be used to generate a `srcset` attribute, either based on absolute widths in pixels (e.g. [300, 600, 900]) or pixel density descriptors (e.g. `["2x"]` or `[1.5, 2]`). | ||
|
|
||
|
|
||
| ```astro | ||
| --- | ||
| import { Image } from "astro"; | ||
| import myImage from "./my-image.jpg"; | ||
| --- | ||
|
|
||
| <Image src={myImage} width={myImage.width / 2} densities={[1.5, 2]} alt="My cool image" /> | ||
| ``` | ||
|
|
||
| ```html | ||
| <img | ||
| src="/_astro/my_image.hash.webp" | ||
| srcset="/_astro/my_image.hash.webp 1.5x, /_astro/my_image.hash.webp 2x" | ||
Princesseuh marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| alt="My cool image" | ||
| /> | ||
| ``` | ||
|
|
||
| ## Picture component | ||
|
|
||
| The experimental `<Picture />` component can be used to generate a `<picture>` element with multiple `<source>` elements. | ||
|
|
||
| The example below uses the `format` property to generate a `<source>` in each of the specified image formats: | ||
|
|
||
| ```astro | ||
| --- | ||
| import { Picture } from "astro:assets"; | ||
| import myImage from "./my-image.jpg"; | ||
| --- | ||
|
|
||
| <Picture src={myImage} formats={["avif", "webp"]} alt="My super image in multiple formats!" /> | ||
| ``` | ||
|
|
||
| The above code will generate the following HTML, and allow the browser to determine the best image to display: | ||
|
|
||
| ```html | ||
| <picture> | ||
| <source srcset="..." type="image/avif" /> | ||
| <source srcset="..." type="image/webp" /> | ||
| <img src="..." alt="My super image in multiple formats!" /> | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. will the |
||
| </picture> | ||
| ``` | ||
|
|
||
| The `Picture` component takes all the same props as the `Image` component, including the new `densities` and `widths` properties. | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -23,6 +23,12 @@ if (typeof props.height === 'string') { | |
| } | ||
|
|
||
| const image = await getImage(props); | ||
|
|
||
| const additionalAttributes: Record<string, any> = {}; | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. why not
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Mostly so we don't have to check the attributes before using them, since it doesn't really matter (it's classic Astro serializing, so it supports everything) |
||
|
|
||
| if (image.srcSet.values.length > 0) { | ||
| additionalAttributes.srcset = image.srcSet.attribute; | ||
| } | ||
| --- | ||
|
|
||
| <img src={image.src} {...image.attributes} /> | ||
| <img src={image.src} {...additionalAttributes} {...image.attributes} /> | ||
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
| @@ -0,0 +1,57 @@ | ||||||
| --- | ||||||
| import { getImage, type LocalImageProps, type RemoteImageProps } from 'astro:assets'; | ||||||
| import type { GetImageResult, ImageOutputFormat } from '../dist/@types/astro'; | ||||||
| import { isESMImportedImage } from '../dist/assets/internal'; | ||||||
| import { AstroError, AstroErrorData } from '../dist/core/errors/index.js'; | ||||||
| import type { HTMLAttributes } from '../types'; | ||||||
|
|
||||||
| type Props = (LocalImageProps | RemoteImageProps) & { | ||||||
| formats?: ImageOutputFormat[]; | ||||||
Princesseuh marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||
| fallbackFormat?: ImageOutputFormat; | ||||||
| pictureAttributes?: HTMLAttributes<'picture'>; | ||||||
| }; | ||||||
|
|
||||||
| const { formats = ['webp'], pictureAttributes = {}, ...props } = Astro.props; | ||||||
|
|
||||||
| if (props.alt === undefined || props.alt === null) { | ||||||
| throw new AstroError(AstroErrorData.ImageMissingAlt); | ||||||
| } | ||||||
|
|
||||||
| const optimizedImages: GetImageResult[] = await Promise.all( | ||||||
| formats.map( | ||||||
| async (format) => | ||||||
| await getImage({ ...props, format: format, widths: props.widths, densities: props.densities }) | ||||||
| ) | ||||||
| ); | ||||||
|
|
||||||
| const fallbackFormat = | ||||||
| props.fallbackFormat ?? isESMImportedImage(props.src) | ||||||
| ? ['svg', 'gif'].includes(props.src.format) | ||||||
| ? props.src.format | ||||||
| : 'png' | ||||||
| : 'png'; | ||||||
|
|
||||||
| const fallbackImage = await getImage({ | ||||||
| ...props, | ||||||
| format: fallbackFormat, | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ah, so the answer is yes! |
||||||
| widths: props.widths, | ||||||
| densities: props.densities, | ||||||
| }); | ||||||
|
|
||||||
| const additionalAttributes: Record<string, any> = {}; | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
perhaps? |
||||||
| if (fallbackImage.srcSet.values.length > 0) { | ||||||
| additionalAttributes.srcset = fallbackImage.srcSet.attribute; | ||||||
| } | ||||||
| --- | ||||||
|
|
||||||
| <picture {...pictureAttributes}> | ||||||
| { | ||||||
| Object.entries(optimizedImages).map(([_, image]) => ( | ||||||
| <source | ||||||
| srcset={`${image.src}${image.srcSet.values.length > 0 ? ' , ' + image.srcSet.attribute : ''}`} | ||||||
| type={"image/" + image.options.format} | ||||||
| /> | ||||||
| )) | ||||||
| } | ||||||
| <img src={fallbackImage.src} {...additionalAttributes} {...fallbackImage.attributes} /> | ||||||
| </picture> | ||||||
Uh oh!
There was an error while loading. Please reload this page.