Intent to Ship: Lazy load scroll margin

912 views
Skip to first unread message

Traian Captan

unread,
Nov 21, 2023, 7:55:54 PM11/21/23
to blink-dev

Contact emails

tca...@chromium.org

Explainer

None

Specification

https://html.spec.whatwg.org/#lazy-load-root-margin

Summary

Changes the lazy load intersection observer's init dictionary to use a scrollMargin instead of a rootMargin. This allows lazy loading images contained inside CSS scrollers, like carousels, to load as expected when near the viewport instead of the current behavior where these images load when at least one pixel is intersecting the viewport.



Blink component

Blink>Image

Search tags

lazyloadscrollmargin

TAG review

None

TAG review status

Not applicable

Risks



Interoperability and Compatibility

Overall low as scroll margin also applies to the root element thus not affecting lazy loading images that are currently loading with just a root margin.



Gecko: Positive (https://github.com/w3c/IntersectionObserver/issues/431https://bugzilla.mozilla.org/show_bug.cgi?id=1864794

WebKit: Positive (https://github.com/w3c/IntersectionObserver/issues/431#issuecomment-930602435https://bugs.webkit.org/show_bug.cgi?id=264864

Web developers: Positive (https://bugs.chromium.org/p/chromium/issues/detail?id=1391989)

Other signals:

WebView application risks

Does this intent deprecate or change behavior of existing APIs, such that it has potentially high risk for Android WebView-based applications?

None



Debuggability

None



Will this feature be supported on all six Blink platforms (Windows, Mac, Linux, Chrome OS, Android, and Android WebView)?

Yes

Is this feature fully tested by web-platform-tests?

Yes

https://wpt.fyi/results/html/semantics/embedded-content/the-img-element?label=master&label=experimental&aligned&q=image-loading-lazy-in-



Flag name on chrome://flags

LazyLoadScrollMargin

Finch feature name

None

Non-finch justification

This feature is behind an enabled-by-default flag that can be disabled if needed.



Requires code in //chrome?

False

Tracking bug

https://bugs.chromium.org/p/chromium/issues/detail?id=1391989

Estimated milestones

Shipping on desktop121
DevTrial on desktop121
Shipping on Android121
DevTrial on Android121
Shipping on WebView121


Anticipated spec changes

Open questions about a feature may be a source of future web compat or interop issues. Please list open issues (e.g. links to known github issues in the project for the feature specification) whose resolution may introduce web compat/interop risk (e.g., changing to naming or structure of the API in a non-backward-compatible way).

None

Link to entry on the Chrome Platform Status

https://chromestatus.com/feature/5106926245642240

Links to previous Intent discussions

Intent to prototype: https://groups.google.com/a/chromium.org/d/msgid/blink-dev/CAFxahvtrmHkoOpTuD2eZYVOyFuAhP8ZFVoTuNBS8zYTVY%3DTaaQ%40mail.gmail.com

This intent message was generated by Chrome Platform Status.

Yoav Weiss

unread,
Nov 22, 2023, 12:18:54 AM11/22/23
to Traian Captan, blink-dev
Do I understand correctly that currently lazy-loaded images in CSS scrollers have suboptimal behavior and this would improve that without potentially harming other cases? 

--
You received this message because you are subscribed to the Google Groups "blink-dev" group.
To unsubscribe from this group and stop receiving emails from it, send an email to blink-dev+...@chromium.org.
To view this discussion on the web visit https://groups.google.com/a/chromium.org/d/msgid/blink-dev/CAFxahvsUb0GEG9WNWRN7Akkowjm03gLj%2Biiq5rG8%2BzdAWMBTNA%40mail.gmail.com.

Traian Captan

unread,
Nov 22, 2023, 12:25:47 AM11/22/23
to Yoav Weiss, blink-dev
Yes, that's correct.

Yoav Weiss

unread,
Nov 22, 2023, 12:37:36 AM11/22/23
to Traian Captan, blink-dev
Thanks, that sounds like a strict improvement.

On Wed, Nov 22, 2023 at 6:25 AM Traian Captan <tca...@chromium.org> wrote:
Yes, that's correct.


On Tue, Nov 21, 2023 at 9:18 PM Yoav Weiss <yoav...@chromium.org> wrote:
Do I understand correctly that currently lazy-loaded images in CSS scrollers have suboptimal behavior and this would improve that without potentially harming other cases? 

On Wed, Nov 22, 2023 at 1:55 AM Traian Captan <tca...@chromium.org> wrote:

Contact emails

tca...@chromium.org

Explainer

None

A short (inline) explainer would help reviewers to understand e.g. if this involves changes to the API surface that developers need to care about.
Can you write a few words on the impact on developers? 


The spec doesn't mention anything specific around root margins or scroll margins (other than the algorithm name).
Are these concepts interoperable?

Rick Byers

unread,
Nov 28, 2023, 2:41:06 AM11/28/23
to Yoav Weiss, Traian Captan, blink-dev
On Wed, Nov 22, 2023 at 2:37 PM Yoav Weiss <yoav...@chromium.org> wrote:
Thanks, that sounds like a strict improvement.

On Wed, Nov 22, 2023 at 6:25 AM Traian Captan <tca...@chromium.org> wrote:
Yes, that's correct.


On Tue, Nov 21, 2023 at 9:18 PM Yoav Weiss <yoav...@chromium.org> wrote:
Do I understand correctly that currently lazy-loaded images in CSS scrollers have suboptimal behavior and this would improve that without potentially harming other cases? 

On Wed, Nov 22, 2023 at 1:55 AM Traian Captan <tca...@chromium.org> wrote:

Contact emails

tca...@chromium.org

Explainer

None

A short (inline) explainer would help reviewers to understand e.g. if this involves changes to the API surface that developers need to care about.
Can you write a few words on the impact on developers? 


The spec doesn't mention anything specific around root margins or scroll margins (other than the algorithm name).
Are these concepts interoperable?

I dug around a little to try to better understand this. The HTML spec does mention setting the "scrollMargin" on the IntersectionObserver, a new property we recently shipped (I2S). 
While WebKit and Gecko aren't yet passing the WPT tests for this yet, interestingly WebKit is already passing most of the newly added WPTs for lazy loaded images in particular. So perhaps their implementation already handled this?
 
Seems reasonable to me - LGTM1

Traian Captan

unread,
Nov 30, 2023, 2:33:51 AM11/30/23
to Yoav Weiss, blink-dev

Thanks, that sounds like a strict improvement.
Yes.

Explainer

None

A short (inline) explainer would help reviewers to understand e.g. if this involves changes to the API surface that developers need to care about.
Can you write a few words on the impact on developers? 
Here's a link to the explainer I extracted out of the motivation section:
I added it to the "Doc link(s)" section in Chrome Platform Status tool since I didn't see a specific place label "Explainer".
This change is internal to how we set up the intersection observer for lazy loaded images and no new API surface is exposed.
Developers only need to add the `loading="lazy"` attribute to the out of viewport images like they did before, the only difference being that the images now will load when near the viewport like expected instead of loading late, when at least one pixel is intersecting the viewport.


The spec doesn't mention anything specific around root margins or scroll margins (other than the algorithm name).
Are these concepts interoperable?
 It is mentioned in the intersection observer setup options:
"The options is an IntersectionObserverInit dictionary with the following dictionary members: «[ "scrollMargin" → lazy load scroll margin ]»"
As for interoperability between them, you can think of scrollMargin as a superset of rootMargin. In cases without nested scrollers, initializing an intersection observer with either scroll or root margin will behave the same. However, in cases with nested scrollers, using root margin will cause images to load late while using scroll margin will cause them to load as expected.

Traian Captan

unread,
Nov 30, 2023, 3:07:52 AM11/30/23
to Rick Byers, Yoav Weiss, blink-dev
Thank you Rick!

I did some investigating into why WebKit is passing some of the new WPTs for lazy loaded images.
I think it might be because WebKit is considering the edge as inclusive, while Blink and Gecko do not.
In the following example if the spacer height is 100px Safari will report intersecting as true while Chrome and FireFox would report it as false.
If the height is increased to 101px, all 3 browsers will report the intersection as false.
<!DOCTYPE html>
<style>
#scroller { width: 100px; height: 100px; overflow: scroll; background-color: gray; }
#spacer { width: 50px; height: 100px; }
#target { width: 50px; height: 50px; background-color: green; }
</style>

<div id=scroller>
<div id=spacer></div>
<div id=target></div>
</div>

<script>
let entries = [];

window.onload = function() {
const observer = new IntersectionObserver(
callback,
{
rootMargin: "0px"
}
);
observer.observe(target);
};

function callback(entries) {
console.log(`isIntersecting = ${entries[0].isIntersecting}`);
}
</script>



Rick Byers

unread,
Nov 30, 2023, 3:14:16 AM11/30/23
to Traian Captan, Yoav Weiss, blink-dev
Interesting. Could you try to improve the tests to capture the interop difference and ensure passing reflects full conformance to the spec? We rely on WPTs as our quantifiable measure of interoperability...

Rick

Traian Captan

unread,
Nov 30, 2023, 5:27:42 AM11/30/23
to Rick Byers, Yoav Weiss, blink-dev
Hi Rick,

Yes. I uploaded a CL that increases the spacer size by 30px:

The tests are now failing as expected on Safari:

PhistucK

unread,
Nov 30, 2023, 5:34:15 AM11/30/23
to Traian Captan, Rick Byers, Yoav Weiss, blink-dev
I hit a case where even when 5 - 10 pixels of the image is in the scrolling viewport, it is still not loaded, so hopefully it will take care of it as well. Let me know when this is in canary and I can try and test it out (at the moment I am not using lazy loading as a result). :)

PhistucK


Rick Byers

unread,
Nov 30, 2023, 8:33:43 AM11/30/23
to Traian Captan, Yoav Weiss, blink-dev
Thank you Traian!

Traian Captan

unread,
Nov 30, 2023, 12:33:34 PM11/30/23
to PhistucK, Rick Byers, Yoav Weiss, blink-dev
Will do.
Would you mind sharing an example of the case you encountered so that I can test it locally?

Traian Captan

unread,
Nov 30, 2023, 12:33:46 PM11/30/23
to Rick Byers, Yoav Weiss, blink-dev
You're welcome Rick!

Mike Taylor

unread,
Nov 30, 2023, 1:54:19 PM11/30/23
to Traian Captan, Rick Byers, Yoav Weiss, blink-dev

Chris Harrelson

unread,
Nov 30, 2023, 1:57:12 PM11/30/23
to Mike Taylor, Traian Captan, Rick Byers, Yoav Weiss, blink-dev

Traian Captan

unread,
Dec 1, 2023, 1:33:35 AM12/1/23
to Mike Taylor, Rick Byers, Yoav Weiss, blink-dev
Thank you Mike!

On Thu, Nov 30, 2023 at 10:54 AM Mike Taylor <mike...@chromium.org> wrote:

Traian Captan

unread,
Dec 1, 2023, 1:33:36 AM12/1/23
to Chris Harrelson, Mike Taylor, Rick Byers, Yoav Weiss, blink-dev
Thank you Chris!

PhistucK

unread,
Dec 3, 2023, 1:01:57 PM12/3/23
to Traian Captan, Rick Byers, Yoav Weiss, blink-dev
When trying to generate a reduced test case, I found the underlying issue that explains the issue I was having -
The image is centered and has a container with a fixed width with the image itself having a 100% width.
This is just a snippet for illustrating the structure -
<div style="width: 100px; display: flex; justify-content: center;">
 <img style="width: 100%;" loading="lazy" src="some-image.jpg">
</div>

Issue - the image does not actually use the fixed width of the container (= 100%) until it is loaded.
As a result, a 0x0 image is centered and it is not within the viewport, therefore not loaded.

Chrome as well as Safari exhibit this behaviour, while Firefox gets it right.

I will file an issue with the reduced test case for the no-dimensions-until-loaded issue.



PhistucK

Traian Captan

unread,
Dec 4, 2023, 7:58:00 PM12/4/23
to PhistucK, Rick Byers, Yoav Weiss, blink-dev
Thanks for the update!

Since this involves a lazy loaded image, and you mentioned this is a reduced test case, I wanted to check if the image is in viewport or out of viewport in your test page?

Regards,
Traian

PhistucK

unread,
Dec 5, 2023, 3:23:51 PM12/5/23
to Traian Captan, Rick Byers, Yoav Weiss, blink-dev
Well, that is debatable... Because the image is 0x0 before it is loaded and centered, it is technically not in the viewport, but I am not sure why it would be 0x0. I am changing the original snippet to this (untested, just more like what I have) -
<div style="width: 100px; display: flex; justify-content: center;">
 <div><img style="width: 100%;" loading="lazy" src="some-image.jpg"></div>
</div>
That extraneous <div> seems to cause this... I need to look into it. I got confusing results.

PhistucK

Casey Carroll

unread,
Dec 9, 2023, 12:49:24 PM12/9/23
to blink-dev, PhistucK, Rick Byers, Yoav Weiss, blink-dev, Traian Captan
Hi all! 

This sounds like a great addition to lazy loading behavior. I'm wondering if this change had an effect on a lazy loading strategy implemented on REI.com. 

Prior to 121, images with loading="lazy" tucked away in our hamburger menu did not download until the hamburger menu's visibility changed from hidden to visible. In v121 and 122 the images now download on initial render, ignoring visibility: hidden. 

I can achieve the old behavior by using display: none rather than visibility: hidden. However, I wonder if there are other similar implementations in the wild and if their lazy loading behavior will change as people upgrade to v121. 

Expected behavior (v120 and older):
(Mobile viewports): 
1. With the network panel open and filters set to images and "merch-zones", visit www.rei.com
2. Notice empty network panel 
3. Click the hamburger menu
4. Notice all merch-zones images download

(Desktop viewports):
1. With the network panel open and filters set to images and "merch-zones", visit www.rei.com
2. Notice empty network panel 
3. Click on "Gifts"
4. Notice merch-zones/gifts/live.jpg image downloads

I have also attached videos to explain the change. 

Thank you!
v119 lazy load.mov
v122 no lazy load.mov

Mateusz Krzeszowiak

unread,
Dec 11, 2023, 6:58:07 AM12/11/23
to blink-dev, Casey Carroll, PhistucK, Rick Byers, Yoav Weiss, blink-dev, Traian Captan
Hi all!

We were considering removing the lazy-loading inside the slider on shop.app product pages because the images appeared too late when scrolling, even on fast connections.
I tested the new behaviour in Chrome Canary and it works perfectly, we won't have to change anything! Also, I'm not sure how the margin is configured, but in our case only the next image is loaded. This seems like a really good balance between performance and saving data.

Thanks!

Chris Harrelson

unread,
Dec 11, 2023, 6:52:38 PM12/11/23
to Mateusz Krzeszowiak, blink-dev, Casey Carroll, PhistucK, Rick Byers, Yoav Weiss, Traian Captan
On Mon, Dec 11, 2023 at 3:58 AM 'Mateusz Krzeszowiak' via blink-dev <blin...@chromium.org> wrote:
Hi all!

We were considering removing the lazy-loading inside the slider on shop.app product pages because the images appeared too late when scrolling, even on fast connections.
I tested the new behaviour in Chrome Canary and it works perfectly, we won't have to change anything! Also, I'm not sure how the margin is configured, but in our case only the next image is loaded. This seems like a really good balance between performance and saving data.

This is great feedback, thank you! Glad it worked for you.
 

Casey Carroll

unread,
Dec 12, 2023, 6:08:09 PM12/12/23
to blink-dev, Chris Harrelson, blink-dev, Casey Carroll, PhistucK, Rick Byers, Yoav Weiss, Traian Captan, Mateusz Krzeszowiak
I'd like to correct my original post. I assumed lazy images did not download until their visibility toggled from hidden to visible. It turns out our implementation actually places those images in a child of a parent with a hidden x-axis overflow. The images are placed outside the parent element box and are clipped. When the lazy load's IO init dictionary uses rootMargin, these images do not download until they are moved into the parent's element box. Now that scrollMargin is used, they are downloaded as soon as the page loads because they are near the viewport. 

Here is a MRE of the behavior: https://statuesque-sheer-innocent.glitch.me/ 

On chrome v120 the lazy image does not download until you open menu level 2. On Chrome 121+, the image will download on page load. 

At the end of the day, we should probably change our mega menu implementation. We know Safari downloads all of these off-screen lazy images on page load anyway. 

Traian Captan

unread,
Dec 12, 2023, 10:40:24 PM12/12/23
to Mateusz Krzeszowiak, blink-dev, Casey Carroll, PhistucK, Rick Byers, Yoav Weiss
Thanks for the feedback Mateusz!

Also, I'm not sure how the margin is configured, but in our case only the next image is loaded.
 The margin is configured based on the connection type.

Traian Captan

unread,
Dec 12, 2023, 11:19:58 PM12/12/23
to Casey Carroll, blink-dev, Chris Harrelson, PhistucK, Rick Byers, Yoav Weiss, Mateusz Krzeszowiak
Thanks for the update Casey!

Your understanding of how the change to using scroll-margin instead of root-margin for lazy loaded images is correct.
The MRE provided is working as expected.
Given that images near the viewport will be loaded, changing the menu implementation seems like the right path forward to me.

Regards,
Traian

Reply all
Reply to author
Forward
0 new messages