withastro / roadmap

Ideas, suggestions, and formal RFC proposals for the Astro project.
320 stars 30 forks source link

Responsive images #1042

Closed ascorbic closed 3 weeks ago

ascorbic commented 1 month ago

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

Non-goals

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:

ascorbic commented 1 month ago

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.

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:

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 commented 1 month ago

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 commented 1 month ago

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

BuckyBuck135 commented 1 month ago

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:

ascorbic commented 3 weeks ago

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