Complete Guide: Native lazy-loading for images and frames

The web's next performance-boosting technology will be enabled by default starting from Chrome 75 (stable release planned for 04th June 2019)

Written by Erk Struwe, Founder & CEO @ tiny.pictures
09th April, 2019

Eager cat loaded lazily (but still immediately because it's above the fold)
Eager cat loaded lazily (but still immediately because it's above the fold)

Today's websites are packed with heavy media assets like images and videos. Images make up around 70 % of an average website's traffic. Many of them, however, are never shown to a user because they're placed way below the fold (the website's area which is immediately visible to the user after loading).

Lazy-loading is a mechanism to defer the network traffic necessary to load still invisible content until it is actually about to be seen by the user, thus preferring content above the fold and improving perceived load time.

Current approach

Until now, web developers had to use JavaScript libraries in order to achieve lazy-loading. Most libraries work like this:

  • The initial, server-side HTML response includes an img element without the src attribute. Thus, the browser does not load any data. Instead, the image's URL is set as another attribute in the element's dataset, e. g. data-src.
  • <img data-src="https://tiny.pictures/example1.jpg" alt="...">
  • Then, a lazy-loading library is loaded and executed.
  • <script src="LazyLoadingLibrary.js"></script>
    <script>
        LazyLoadingLibrary.run()
    </script>
  • It keeps track of the user's scrolling behavior and makes the browser load the image when it is about to be scrolled into view. It does that by copying the data-src attribute's value to src.
  • <img src="https://tiny.pictures/example1.jpg" data-src="https://tiny.pictures/example1.jpg" alt="...">
    

The problem with JavaScript lazy-loading libraries

The obvious problem with this approach is the length of the critical path for displaying the website. It consists of three steps, which have to be carried out in sequence (after each other):

  1. Load the initial HTML response
  2. Load the lazy-loading library
  3. Load the image file

If this technique is used for images above the fold the website will flicker during loading because it is first painted without the image (after step 1 or 2, depending on the script being marked as deferred or async) and then — after having been loaded — including it. It will also be perceived as loading slowly.

In addition, the lazy-loading library itself puts an extra weight on the website's bandwidth and CPU requirements. And finally, it won't work for people having JavaScript disabled (although we shouldn't really care about them in 2019, should we?).

Native lazy-loading to the rescue

Lazy cat loaded lazily
Lazy cat loaded lazily

Beginning with version 75 (available as preview version now, beta in May, stable on 04th June 2019), Chromium and Google's Chrome browser will be shipped with a native lazy-loading mechanism enabled by default. It had been in development since February 2018.

Enable native lazy-loading

Until then, you can enable it manually by switching two flags. This is even necessary if you are already on the 75 canary preview version (75.0.3759.4) because the feature has not been merged as whole yet. To enable it:

  1. Open chrome://flags in Chromium or Chrome.
  2. Search for lazy.
  3. Enable both the "Enable lazy image loading" and the "Enable lazy frame loading" flag.
  4. Native lazy-loading flags in Google Chrome
    Native lazy-loading flags in Google Chrome
  5. Restart your browser (remember to copy this page's URL to your clipboard in case your browser starts with a blank page).

The loading attribute

Both the img and the iframe element will consider the loading attribute. It's important to note that its values will not be taken as a strict order by the browser but rather as a hint to help the browser make its own decision whether or not to load the image or frame lazily.

The attribute can have three values which are explained below. Next to the images, you'll find tables listing your individual resource loading timings for this page load. Range response refers to a kind of partial pre-flight request made to determine the image's dimensions (see How it works for details). If this column is filled, your browser made a successful range request.

Please note the startTime column, which states the time image loading was deferred to after the DOM had been parsed. You might have to perform a hard reload (ctrl + shift + r) to retrigger range requests.

auto or unset

<img src="auto-cat.jpg" loading="auto" alt="...">
<img src="auto-cat.jpg" alt="...">
Auto cat loaded automatically
Auto cat loaded automatically
Your request timings for auto cat
EventFull responseRange response

Lets the browser decide whether or not to load the image lazily. It takes things like platform, Data Saver mode, network conditions, image size, the CSS display property, etc. into consideration (see How it works for info about why all this is important).

eager

<img src="auto-cat.jpg" loading="eager" alt="...">
Eager cat loaded eagerly
Eager cat loaded eagerly
Your request timings for eager cat
EventFull responseRange response

Hints to the browser that this image should be loaded immediately. If loading was already deferred (e. g. because it had been set to lazy and was then changed to eager by JavaScript), the browser should start loading immediately.

lazy

<img src="auto-cat.jpg" loading="lazy" alt="...">
Lazy cat loaded lazily
Lazy cat loaded lazily
Your request timings for lazy cat
EventFull responseRange response

Hints to the browser that this image should be loaded lazily. It's up to the browser to tell what exactly this means, but the explainer document states that it should start loading when the user scrolls "near" the image such that it is probably loaded once it actually comes into view.

How it works

In contrast to JavaScript lazy-loading libraries, native lazy-loading uses a kind of pre-flight request to get the first 2048 bytes of the image file. Using these, the browser tries to determine the image's dimensions in order to insert an invisible placeholder for the full image and prevent content from jumping during loading.

The image's load event is fired as soon as the full image is loaded, be it after the first request (for images smaller than 2 kB) or after the second one. Please note that the load event may very well never be fired for certain images because the second request is never made.

In future, browsers might make twice as many image requests as currently. First the range request, then the full request. Make sure your servers support the HTTP Range: 0-2047 header and respond with status code 206 (Partial Content) to prevent them from delivering the full image twice.

Due to the higher number of subsequent request made by the same user, web server support for the HTTP/2 protocol will become more important.

Consider using a specialized image optimization Content Delivery Network like tiny.pictures.

Deferred content

Chrome's rendering engine Blink uses heuristics to determine which content should be deferred and for how long so. You can find a comprehensive list of requirements in Scott Little's design documentation. This is a short breakdown of what will be deferred:

  • Images and frames on all platforms which have loading="lazy" set
  • Images on Chrome for Android with Data Saver turned on that satisfy all of the following
    • loading="auto" or unset
    • no width and height attributes smaller than 10x10 set
    • not created programmatically in JavaScript
  • Frames which satisfy all of the following
    • loading="auto" or unset
    • third-party (different domain or protocol than the embedding page)
    • larger than 4x4 pixels (to prevent deferring tiny tracking frames)
    • not marked as display: none or visibility: hidden (again, to prevent deferring tracking frames)
    • not positioned off-screen using negative x or y coordinates

Images with srcset

Native lazy-loading also works with img elements using the srcset attribute. This attribute offers a list of image file candidates to the browser. Based on the user's screen size, display pixel ratio, network conditions, etc. the browser chooses the optimal image candidate for the occasion. The tiny.pictures service is able to provide all image candidates in real-time without any backend development necessary.

<img src="https://tiny.pictures/images/guide/native-lazy-loading/lazy-cat.jpg" srcset="https://demo.tiny.pictures/main/images/guide/native-lazy-loading/lazy-cat.jpg?width=50 50w, https://demo.tiny.pictures/main/images/guide/native-lazy-loading/lazy-cat.jpg?width=75 75w, https://demo.tiny.pictures/main/images/guide/native-lazy-loading/lazy-cat.jpg?width=100 100w, https://demo.tiny.pictures/main/images/guide/native-lazy-loading/lazy-cat.jpg?width=120 120w, https://demo.tiny.pictures/main/images/guide/native-lazy-loading/lazy-cat.jpg?width=180 180w, https://demo.tiny.pictures/main/images/guide/native-lazy-loading/lazy-cat.jpg?width=360 360w, https://demo.tiny.pictures/main/images/guide/native-lazy-loading/lazy-cat.jpg?width=540 540w, https://demo.tiny.pictures/main/images/guide/native-lazy-loading/lazy-cat.jpg?width=720 720w, https://demo.tiny.pictures/main/images/guide/native-lazy-loading/lazy-cat.jpg?width=900 900w, https://demo.tiny.pictures/main/images/guide/native-lazy-loading/lazy-cat.jpg?width=1080 1080w, https://demo.tiny.pictures/main/images/guide/native-lazy-loading/lazy-cat.jpg?width=1296 1296w, https://demo.tiny.pictures/main/images/guide/native-lazy-loading/lazy-cat.jpg?width=1512 1512w, https://demo.tiny.pictures/main/images/guide/native-lazy-loading/lazy-cat.jpg?width=1728 1728w, https://demo.tiny.pictures/main/images/guide/native-lazy-loading/lazy-cat.jpg?width=1944 1944w, https://demo.tiny.pictures/main/images/guide/native-lazy-loading/lazy-cat.jpg?width=2160 2160w, https://demo.tiny.pictures/main/images/guide/native-lazy-loading/lazy-cat.jpg?width=2376 2376w, https://demo.tiny.pictures/main/images/guide/native-lazy-loading/lazy-cat.jpg?width=2592 2592w, https://demo.tiny.pictures/main/images/guide/native-lazy-loading/lazy-cat.jpg?width=2808 2808w, https://demo.tiny.pictures/main/images/guide/native-lazy-loading/lazy-cat.jpg?width=3024 3024w" loading="lazy" alt="...">

Browser support

As of the time of writing, no browser supports native-loading by default. However, Chrome will enable the feature by default starting from version 75 (04th June 2019). No other browser vendor has announced support so far (Edge being a kind of exception because they will switch to be Chromium-based as well in the near future).

You can detect the feature with a few lines of JavaScript:

if ("loading" in HTMLImageElement.prototype) {
    // Support.
} else {
    // No support. You might want to dynamically import a lazy-loading library here.
}

The following status display is based on the above code. However, even if the feature flags are enabled and lazy-loading is effectively happening, feature detection still fails in Chrome 75.0.3759.4 (canary preview version).

Conclusion

I'm really excited about this feature. And frankly, I'm still wondering why it hasn't got much more attention until now, given the fact that its release is imminent and the impact on global internet traffic will be remarkable, if only small parts of the heuristics are changed.

Think about it: After a gradual roll-out for the different Chrome platforms and with auto being the default setting, the world's most popular browser will soon lazy-load below-the-fold images and frames by default. Not only will the traffic amount of many badly-written websites drop significantly, web servers will be hammered with tiny requests for image dimension detection.

And then there's tracking: Assuming many unsuspecting tracking pixels and frames will be prevented from being loaded, the analytics and affiliate industry will have to act. We can only hope they don't panic and add loading="eager" to every single image, rendering this great feature useless for their users. They should rather change their code to be recognized as tracking pixels by the heuristics described above.

Web developers, analytics and operations managers should check their website's behavior with this feature and their servers' support for Range requests and HTTP/2 immediately.

Image optimization CDNs could help out in case there are any issues to be expected.

References

  1. Blink LazyLoad design documentation
  2. Blink LazyImages design documentation
  3. Blink LazyFrames design documentation
  4. Blink ImageReplacement design documentation
  5. Feature Policy proposal for disabling the feature page-wide
  6. Addy Osmani's blog post announcing native lazy-loading
  7. Chrome Platform Status feature page
  8. Lazy-load explainer by Scott Little
  9. HTML specs pull request
  10. Chrome platform status and release timeline