Problem/Motivation

In #3167034: Leverage the 'loading' html attribute to enable lazy-load by default for images in Drupal core, we added the basic pattern of how to add lazy loading for images to drupal. Let's continue that into picture tag too.

Steps to reproduce




Only local images are allowed.

Proposed resolution

Remaining tasks

User interface changes

API changes

Data model changes

Release notes snippet

CommentFileSizeAuthor
#3 3173231-3.patch1.58 KBedysmp

Comments

heddn created an issue. See original summary.

edysmp’s picture

Assigned: Unassigned » edysmp

Checking this one.

edysmp’s picture

Title: Add loading=lazy to img tag of <picture> » Enable lazy-load by default for responsive images
Assigned: edysmp » Unassigned
Status: Active » Needs review
StatusFileSize
new1.58 KB

Changed the title because the img tag is not always within a picture element, some times is just the img tag. This img tag does doesn't have set their dimensions, it uses the size and srcset attributes, so the the default loading attributes isn't added by the image theme preprocess, already committed.

Adding a patch that adds the loading=lazy attribute by default for images rendered by responsive_image module along with a small test.

The UI could be added it in this issue: https://www.drupal.org/project/drupal/issues/3173180

gaëlg’s picture

Thank you edysmp!

This patch adds the loading lazy attribute even if the height and width attributes are not set? It can lead to layout shifts.

I guess we should not add it if the dimension attributes are not present. And if core doesn't usually add these attributes, try to add them, where possible.

heddn’s picture

Status: Needs review » Needs work

NW for #4.

edysmp’s picture

Status: Needs work » Needs review

In this case when are multiple files, what dimensions could be added to the img tag?

Wondering me if the browser will calculate the dimensions from the <source> tags or the sizes attribute.

For my researches, Responsive images could be rendered in two ways:

Using picture and img tags.

<picture>
                  <source srcset="/sites/default/files/styles/max_650x650/public/2020-10/dario-mueller-oeeDkjfAxlw-unsplash.jpg?itok=c7OhCoyb 1x" media="all and (min-width: 851px)" type="image/jpeg">
              <source srcset="/sites/default/files/styles/large/public/2020-10/dario-mueller-oeeDkjfAxlw-unsplash.jpg?itok=TgdtqcSK 1x" media="all and (min-width: 560px) and (max-width: 850px)" type="image/jpeg">
              <source srcset="/sites/default/files/styles/medium/public/2020-10/dario-mueller-oeeDkjfAxlw-unsplash.jpg?itok=tAckVJ0p 1x" type="image/jpeg">
                  
<img property="schema:image" loading="lazy" src="/sites/default/files/styles/thumbnail/public/2020-10/dario-mueller-oeeDkjfAxlw-unsplash.jpg?itok=bdASlygG" alt="image1" typeof="foaf:Image">

  </picture>

or just img tag.

<img srcset="/sites/default/files/styles/max_325x325/public/2020-10/janik-rohland-fL8XSHbTQyg-unsplash.jpg?itok=5hAu3qJS 325w, /sites/default/files/styles/max_650x650/public/2020-10/janik-rohland-fL8XSHbTQyg-unsplash.jpg?itok=dINpFU2J 650w, /sites/default/files/styles/max_1300x1300/public/2020-10/janik-rohland-fL8XSHbTQyg-unsplash.jpg?itok=1KVTDx4j 1300w" sizes="(min-width: 1290px) 325px, (min-width: 851px) 25vw, (min-width: 560px) 50vw, 100vw" loading="lazy" src="/sites/default/files/styles/max_325x325/public/2020-10/janik-rohland-fL8XSHbTQyg-unsplash.jpg?itok=5hAu3qJS" alt="image 2" typeof="foaf:Image">

Thoughts?

edysmp’s picture

Status: Needs review » Needs work
gaëlg’s picture

Status: Needs work » Active

That's the somewhat difficult question!

Brief explanation of the purpose of the various HTML things

The various source tags in a picture are used to give rules to browsers about what file to load. In your example, media queries are used as criteria for the two first source tags. The third source tag has a rule for the type of file supported by the browser, they must support jpeg to match this source tag.
If the browser matches the rule of the first source tag, it will load the file from this source tag. If not, it tries with the second source tag, and so on.

So these rules serve two purposes:
* "art direction", when you want a different image look and feel on different devices (like a portrait aspect ratio on mobiles and a landscape one on desktops, or like B&W images on phones, why not) : media queries
* browser file format support, like webp support (and a fallback usually, for browsers not supporting the first suggested format) : type attribute
Detailed documentation: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/picture

The srcset attribute has only one usage (in source tags or in img tag): smartly take care of both various screen pixel densities (aka retina) and downloading performance. To display an image at a 200-wide size on a "normal" screen, you need a 200-wide file. But if the screen has a 300% pixel density for example, like HTC One, the finish will be better with a 600-wide file. So, we suggest different files to the browser. It will choose the better one for its needs, thanks to the "1x", "2x" or thanks to the "sizes" attribute and the width indication of each file with "325w", "650w".
In your example, the sizes attribute says: "Overall*, on 1290+px viewports, the display size will be 325px. On 851+px viewports, the display size will be a quarter of the viewport. On 560+px viewports, it will take half of the viewport and on smaller viewports it will take the whole viewport".
*Usually, the exact display size will be determined by CSS, but for good performance, the browser needs to have an idea of the display size soon enough, before it loads the CSS, so that he can choose the appropriate file: not too big for fast download, but not too small for a beautiful finish.

If it can help understand better, here's a real life HTML which displays images at a width of 800px, unless the viewport is too small, in which case the image is reduced to fit in the viewport:

<picture>
  <source
    type="image/webp" sizes="(min-width:800px)800px, 100vw"
    srcset="/system/files/styles/low/private/inline-images/large_0.webp?itok=j8Rop7Tz 480w,
    /system/files/styles/medium/private/inline-images/large_0.webp?itok=sY_SMalM 600w,
    /system/files/styles/high/private/inline-images/large_0.webp?itok=mQXL6Tif 800w,
    /system/files/styles/veryhigh/private/inline-images/large_0.webp?itok=EMB_1no3 1600w,
    /system/files/styles/highest/private/inline-images/large_0.webp?itok=DMlMHffd 2400w"
  ></source>
  <source
    type="image/jpeg" sizes="(min-width:800px)800px, 100vw"
    srcset="/system/files/styles/low/private/inline-images/large_0.jpg?itok=j8Rop7Tz 480w,
    /system/files/styles/medium/private/inline-images/large_0.jpg?itok=sY_SMalM 600w,
    /system/files/styles/high/private/inline-images/large_0.jpg?itok=mQXL6Tif 800w,
    /system/files/styles/veryhigh/private/inline-images/large_0.jpg?itok=EMB_1no3 1600w,
    /system/files/styles/highest/private/inline-images/large_0.jpg?itok=DMlMHffd 2400w"
  ></source>
  <img
    height="1247" width="2400"
    loading="lazy"
    sizes="(min-width:800px)800px, 100vw"
    srcset="/system/files/styles/low/private/inline-images/large_0.jpg?itok=j8Rop7Tz 480w,
       /system/files/styles/medium/private/inline-images/large_0.jpg?itok=sY_SMalM 600w,
       /system/files/styles/high/private/inline-images/large_0.jpg?itok=mQXL6Tif 800w,
       /system/files/styles/veryhigh/private/inline-images/large_0.jpg?itok=EMB_1no3 1600w,
       /system/files/styles/highest/private/inline-images/large_0.jpg?itok=DMlMHffd 2400w"
    src="/system/files/styles/highest/private/inline-images/large_0.jpg?itok=DMlMHffd"/>
</picture>

Layout shift prevention

To prevent a layout shift, the real display dimensions of the not-yet-loaded image will be calculated by browsers, based on CSS, and as a fallback when CSS tells nothing or "auto", based on the sizes, width and height attributes.
So, width and height attributes are useful when CSS does not force them. Usually it gives width only, so height is auto-calculated from the image to keep a correct aspect ratio.

If CSS or the sizes attribute forces width (in pixels, percents,...), we only need to give the aspect ratio of the image, not the dimensions. I mean, we give height and width attributes, but browsers will only use those to determine aspect ratio and finally display height. So that it's the same to tell "w=100, h=200" or "w=200, h=400".
Detailed explanation here: https://www.smashingmagazine.com/2020/03/setting-height-width-images-imp...

When there is only an img tag, all files will actually have the same aspect ratio, so we can take the size of any file to set the width and height attributes. I'd take the one of the src attribute (fallback for browsers not supporting srcset).

When there is a picture tag with various sources using the type attribute, this is only a matter of file format, so the aspect ratio is the same for all files. Again, I would take the one of the src attribute of the inner img tag.

And when there are media queries on source tags, we have art direction, so that aspect ratio is not always the same. Unfortunately we cannot set width and height on source tags (https://github.com/whatwg/html/issues/4968). Aspect ratio is determined from the img tag (https://github.com/web-platform-tests/wpt/pull/24256), so it's the same for all. In that case, we can't prevent a little layout shift if the aspect ratio we give to the browser is not the one of the actual file loaded.
Some recommend to give the aspect ratio for mobiles, as they usually have lower bandwith so are more subject to late layout shifts (https://github.com/GoogleChrome/lighthouse/issues/10085#issuecomment-583...). Anyway, we have no easy way to know which source tag is the one for mobiles, so, again, we could use the img/src fallback (the site builder must choose it on his own).
Or we could (should) not give aspect ratio in that case, which means not enable native lazy loading to make sure we get no layout shift.

And now, what if CSS/sizes tells nothing about display width? This is unusual on real-life websites, but I guess it has to be somehow handled by Drupal core anyway. And obviously, we have no way in PHP to know about CSS rules.

I have two options in mind:
* let lazyloaded responsive images be enabled manually by site builders (in the settings file, or in UI as a follow-up), with a warning/disclaimer about the need to have CSS determine image width (or at least a well-crafted sizes attribute).
* give exact display dimensions in HTML, based on the "1x" file.

I kinda chose the first option in my Native Lazy Loading contrib module, but now that I write all this, I'm thinking the second one might be better.

So, to me, we would have this kind of algorithm :

Is there a source tag with a media query?
Yes: do nothing.
No: is there an srcset attribute on the img tag?
No: use the img src file size to set height and width attributes.
Yes: is there a sizes attribute on the img tag?
Yes: use the src file size to set height and width attributes.
No: use the size of the "1x" file to set height and width attributes.

Anyway, a second advice would be appreciated on this somewhat tough topic.

Version: 9.1.x-dev » 9.2.x-dev

Drupal 9.1.0-alpha1 will be released the week of October 19, 2020, which means new developments and disruptive changes should now be targeted for the 9.2.x-dev branch. For more information see the Drupal 9 minor version schedule and the Allowed changes during the Drupal 9 release cycle.

heddn’s picture

I wonder if we need to add a UI element for this feature-set, just so we can let the site-builder configure a default height/width?

edysmp’s picture

Letting the site-builder to select the default dimensions will be an option too.

heddn’s picture

re #11: would that make things easier? I think it might.

edysmp’s picture

@heddn you're right.

edysmp’s picture

pameeela’s picture

Adding #3192234: Apply width and height attributes to allow responsive image tag use loading="lazy" as related because that's the actual fix for this.

The other referenced issue (#3173180: Add UI for 'loading' html attribute to images) does not address responsive images anymore, which I only figured out after going through the pain of creating a patch for 9.3.x from the MR!