Skip to content
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

Responsive images #1042

Closed
ascorbic opened this issue Oct 21, 2024 · 5 comments
Closed

Responsive images #1042

ascorbic opened this issue Oct 21, 2024 · 5 comments

Comments

@ascorbic
Copy link
Contributor

Summary

The current Astro image component offers a lot of flexibility for displaying images. It supports densities and widths props to help generate the correct img attributes, and the default image service supports modern formats such as AVIF and WebP. While this gives users the tools to create performant and responsive images, it does not give guidance in how to use them - and requires that they are set on all images. This proposal is for a more opinionated image component. It would offer all of the tools from the current component, and also introduce new props and config options that follow best practices by default.

Background & Motivation

Displaying images on the web is difficult, even for the most experienced developers. Users suffer slower page loads, and poor experience as the page layout jumps around. Meanwhile sites experience poor core web vitals scores for performance, cumulative layout shift (CLS) and largest contentful paint (LCP).

The most common imgtag attributes are well known: src, alt, width and height, there are several lesser-known attributes that are needed if an image is to have the best performance. All of these are optional according to the spec, but best practices require most of them. The most important are srcset, sizes, loading, decoding and fetchpriority.

These are a lot of attributes to remember and understand, though the final three have values that are usually safe to think of as dependent on just whether the image is onscreen when the page loads. Astro Image already sets loading and decoding to lazy and async by default. However srcset and sizes have no simple rules because they depend on how the image will be displayed, and can be very hard to do correctly. Images also need to be styled correctly if they are to be responsive and avoid CLS.

This proposal is inspired by the attributes generated by @unpic/astro, which I created, but with some changes to make it closer to the existing component behavior, and less focussed on image CDNs.

Goals

  • A single layout prop that, when combined with existing props, sets the attributes that will make an image responsive and following best practices.
  • Config options to change the defaults for all images
  • Backwards-compatible, so that existing images are unaffected unless they set the props or config options.
  • Add support for optional cropping in image services

Non-goals

  • Placeholder support
  • Automatic provider detection
  • Art direction
  • Implementing crop support for all existing image services

Example

Responsive images will be enabled by setting the layout prop to responsive, fixed or full-width.

---
import { Image } from "astro:assets"
import rocket from "./rocket.jpg"
---
<Image src={rocket} width={800} height={600} layout="responsive" />

A new layout option for the image config will default all images to that layout. This can be overridden on each image.

import { defineConfig } from 'astro/config';

export default defineConfig({
  image: {
    layout: "responsive",
  },
});

References

These served as inspiration, and/or are useful for understanding best practices:

@github-project-automation github-project-automation bot moved this to Stage 2: Accepted Proposals, No RFC in Public Roadmap Oct 21, 2024
@ascorbic
Copy link
Contributor Author

In order to automatically generate the correct attributes for an image, we need to know how it should behave when resized. I propose introducing a layout property to define this. When set, this will enable the new mode, and set defaults for all required attributes.

The most important attributes to set for best practices are shown below. These will all have appropriate values set, according to the specified layout. These can be overridden, but should not need to be in most cases.

  • srcset: the most important extra attribute, and the one that is most likely for developers to know about, this tells the browser which image sources are available to download. There should be several entries, the sizes of which depend on the layout and image size.
  • sizes: a browser uses this attribute as a hint to help it choose which source to download. It is a set of media conditions, which normally contains viewport sizes for responsive images. This would be set automatically based on the layout and image size.
  • loading: by default, all images are loaded as soon as they are encountered in the HTML. This is not needed for images that are offscreen when the page loads, so should be set to lazy for these. Currently set to lazy by default.
  • decoding: a browser needs to decode an image file before displaying it onscreen. This attribute is a hint as to whether this should happen while the page is rendering (sync), or wait until the DOM is complete (async). As with loading, offscreen images should be decoded asynchronously for best performance. Currently set to async by default.
  • fetchpriority: this tells the browser what the priority should be for network requests for this image. The priority of the main images on your page can make a significant difference to your LCP, so should be high for these. Similarly, setting offscreen images to low means that the network is used for more important images that are visible onscreen when the page loads.

New <Image> properties

There would be new properties added to the Image component. The existing densities property would not be supported when using the new image handling. widths would be supported, but not recommended as it is better to allow it to be set automatically when using layout.

layout

By default the images will have a class added that handles basic layout, as described below. These will be set at low specificity so they can be overriden by component and page styles. By default object-fit: cover is set for all layouts unless overridden by the fit prop. Layouts that preserve aspect ratio will have aspect-ratio set as an inline style.

I propose the following layouts, but I am open to discussion:

  • responsive: image is displayed at the specified width unless the container is smaller, at which point it resizes while maintaining the aspect ratio.
  • fixed: the image is displayed at the specified width, and does not resize.
  • full-width: image is displayed at the full width of the container. If height is supplied then the height is fixed at that size when the width is resized, otherwise the aspect ratio is maintained. If width is supplied, that will be the maximum width. Best for hero images that are displayed at the full width of the screen.

priority

By default, images will be lazy loaded setting loading="lazy", decoding="async" and fetchpriority="low". The boolean property priority enables the opposite. This should be used for the LCP image, and other important images above the fold.

fit

Sets the object-fit value. By default the value is cover, which means image is resized to fill the container, while retaining the aspect ratio. This may mean that some edges are cut off if the aspect ratio is different. This can be changed to any other supported value, but the most useful is likely to be contain, which resizes it to fit inside the specified size, while mainaining aspect ratio. This may leave space around the image if the aspect ratio does not match.

position

When cropping or padding an image where the aspect ratio does not match, the default behavior is to centre the image. Setting this will change the position. Some image services may support advanced values here that attempt to focus on a face or other point of interest. This will be documented by each service.

unstyled

If set, no style or class would be applied to the image. The developer would be responsible for setting the appropriate style for the image.

@lloydjatkinson
Copy link

lloydjatkinson commented Oct 21, 2024

Would this be one way to ensure images are the correct size for the current screen/container size? https://developer.chrome.com/docs/lighthouse/performance/uses-responsive-images

For each image on the page, Lighthouse compares the size of the rendered image against the size of the actual image. The rendered size also accounts for device pixel ratio. If the rendered size is at least 4KiB smaller than the actual size, then the image fails the audit.

@ascorbic
Copy link
Contributor Author

@lloydjatkinson yes. It would generate a srcset with breakpoints at the right sizes.

@BuckyBuck135
Copy link

BuckyBuck135 commented Oct 28, 2024

Hi Ascorbic,
This is a nice write-up, and it looks very interesting.
As a beginner with Astro, and webdev in general, the pain point I've had with Astro's and components were:

  • deciding which widths and srcsets to use => it looks like your proposal partially remedies this through the layout prop, if I understand correctly. However, your example still shows width and height props. Would these still be necessary?
  • the lack of art direction => your proposal states that as a non-goal. Is there a reason why?
  • the lack of "heavy" image modification (beyond the resizing and conversion) => One of the core team members said that it was not a desired feature back when they were creating the Astro components. I think it would be a great addition.

And some extra questions:

  1. Would all these extra feature work on local images too?
  2. The priority prop: would that set a rel="preload" link for that particular image, complete with [imagesizes](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/link#imagesizes) and [imagesrcset](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/link#imagesrcset) attributes?

Thank you, and good luck!

@ascorbic
Copy link
Contributor Author

ascorbic commented Nov 4, 2024

This has now moved to stage 3. Continue the discussion in the PR

@ascorbic ascorbic closed this as completed Nov 4, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
Status: Stage 3: Accepted Proposals, Has RFC
Development

No branches or pull requests

3 participants