Today’s websites are packed with heavy media assets like images and videos. Images make up around 50% of an average website’s traffic. Many of them, however, are never shown to a user because they’re placed way below the fold.
What’s this thing about images being lazy, you ask? Lazy-loading is something that’s been covered quite a bit here on CSS-Tricks, including a thorough guide with documentation for different approaches using JavaScript. In short, we’re talking about a mechanism that defers the network traffic necessary to load content when it’s needed — or rather when trigger the load when the content enters the viewport.
The benefit? A smaller initial page that loads faster and saves network requests for items that may not be needed if the user never gets there.
If you read through other lazy-loading guides on this or other sites, you’ll see that we’ve had to resort to different tactics to make lazy-loading work. Well, that’s about to change when lazy-loading will be available natively in HTML as a new loading
attribute… at least in Chrome which will hopefully lead to wider adoption. Chrome is currently developing and testing support for native lazy-loading and is expected to enable it in Chrome 76, which is slated to release on 30th July 2019.
The pre-native approach
Until now, developers like ourselves have had to use JavaScript (whether it’s a library or something written from scratch) in order to achieve lazy-loading. Most libraries work like this:
-
The initial, server-side HTML response includes an
img
element without thesrc
attribute so the browser does not load any data. Instead, the image’s URL is set as another attribute in the element’s data set, e. g.data-src
.
<img data-src="https://tiny.pictures/example1.jpg" alt="...">
<script src="LazyLoadingLibrary.js"></script>
<script>LazyLoadingLibrary.run()</script>
data-src
attribute’s value to the previously empty src
attribute.
<img src="https://tiny.pictures/example1.jpg" data-src="https://tiny.pictures/example1.jpg" alt="...">
This has worked for a pretty long time now and gets the job done. But it’s not ideal for good reasons.
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):
- Load the initial HTML response
- Load the lazy-loading library
- 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 if the script uses defer
or async
) and then — after having been loaded — include the image. 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 let’s not forget that a JavaScript approach won’t work for people who have JavaScript disabled (although we shouldn’t really care about them in 2019, should we?).
Oh, and what about sites that rely on RSS to distribute content, like CSS-Tricks? The initial image-less render means there are no images in the RSS version of content as well.
And so on.
Native lazy-loading to the rescue!
As we noted at the start, Chromium and Google Chrome will ship a native lazy-loading mechanism in the form of a new loading
attribute, starting in Chrome 75. We’ll go over the attribute and its values in just a bit, but let’s first get it working in our browsers so we can check it out together.
Enable native lazy-loading
In Chrome versions starting from 75, we can enable lazy-loading manually by switching two flags. Chrome is expected to enable this feature by default from version 76 (release planned for 30th July 2019).
- Open
chrome://flags
in Chromium or Chrome Canary. - Search for
lazy
. - Enable both the “Enable lazy image loading” and the “Enable lazy frame loading” flag.
- Restart the browser with the button in the lower right corner of the screen.
You can check if the feature is properly enabled by opening your JavaScript console (F12
). You should see the following warning:
[Intervention] Images loaded lazily and replaced with placeholders. Load events are deferred.”
All set? Now we get to dig into the loading
attribute.
The loading attribute
Both the img
and the iframe
elements will accept 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, the browser made a successful range request.
Please note the startTime column, which states the time image loading was deferred after the DOM had been parsed. You might have to perform a hard reload (CTRL + Shift + R) to re-trigger range requests.
The auto (or unset) value
<img src="auto-cat.jpg" loading="auto" alt="...">
<img src="auto-cat.jpg" alt="...">
<iframe src="https://css-tricks.com/" loading="auto"></iframe>
<iframe src="https://css-tricks.com/"></iframe>
Setting the loading
attribute to auto
(or simply leaving the value blank, as in loading=""
) lets the browser decide whether or not to lazy-load an image. It takes many things into consideration to make that decision, like the platform, whether Data Saver mode is enabled, network conditions, image size, image vs. iframe, the CSS display
property, among others. (See How it works) for info about why all this is important.)
The eager value
<img src="auto-cat.jpg" loading="eager" alt="...">
<iframe src="https://css-tricks.com/" loading="eager"></iframe>
The eager
value provides a hint to the browser that an 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 the image immediately.
The lazy value
<img src="auto-cat.jpg" loading="lazy" alt="...">
<iframe src="https://css-tricks.com/" loading="lazy"></iframe>
The lazy
value provides a hints to the browser that an image should be lazy-loaded. It’s up to the browser to interpret 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 the loading attribute 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 never be fired for certain images because the second request is never made.
In the future, browsers might make twice as many image requests as there would be under the current proposal. 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 requests made by the same user, web server support for the HTTP/2 protocol will become more important.
Let’s talk about deferred content. Chrome’s rendering engine Blink uses heuristics to determine which content should be deferred and for how long to defer it. 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 enabled and that satisfy all of the following:
loading="auto"
or unset- no
width
andheight
attributes smaller than 10px - not created programmatically in JavaScript
- Frames which satisfy all of the following:
loading="auto"
or unset- is from a third-party (different domain or protocol than the embedding page)
- larger than 4 pixels in height and width (to prevent deferring tiny tracking frames)
- not marked as
display: none
orvisibility: hidden
(again, to prevent deferring tracking frames) - not positioned off-screen using negative
x
ory
coordinates
Responsive images with srcset
Native lazy-loading also works with responsive 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 will choose the optimal image candidate for the occasion. Image optimization CDNs like tiny.pictures are able to provide all image candidates in real-time without any back end development necessary.
<img src="https://demo.tiny.pictures/native-lazy-loading/lazy-cat.jpg" srcset="https://demo.tiny.pictures/native-lazy-loading/lazy-cat.jpg?width=400 400w, https://demo.tiny.pictures/native-lazy-loading/lazy-cat.jpg?width=800 800w" loading="lazy" alt="...">
Browser support
At the time of this writing, no browser supports native-loading by default. However, Chrome will enable the feature, as we’ve covered, starting in Chrome 76. No other browser vendor has announced support so far. (Edge being a kind of exception because it will soon make the switch to Chromium.)
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 (see below).
}
See the Pen
Native lazy-loading browser support by Erk Struwe (@erkstruwe)
on CodePen.
Automatic fallback to JavaScript solution with low-quality image placeholder
One very cool feature of most JavaScript-based lazy-loading libraries is the low-quality image placeholder (LQIP). Basically, it leverages the idea that browsers load (or perhaps I should say used to load) the src
of an img
element immediately, even if it gets later replaced by another URL. This way, it’s possible to load a tiny file size, low-quality image file on page load and later replace it with a full-sized version.
We can now use this to mimic the native lazy-loading’s 2 KB range requests in browsers that do not support this feature in order to achieve the same result, namely a placeholder with the actual image dimensions and a tiny file size.
See the Pen
Native lazy-loading with JavaScript library fallback and low-quality image placeholder by Erk Struwe (@erkstruwe)
on CodePen.
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, even 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, but 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 or if you’d like to take image delivery optimization to the max (including automatic WebP support, low-quality image placeholders, and much more). Read more about tiny.pictures!
References
- Blink LazyLoad design documentation
- Blink LazyImages design documentation
- Blink LazyFrames design documentation
- Blink ImageReplacement design documentation
- Public Chromium tracking bug
- Feature Policy proposal for disabling the feature page-wide
- Addy Osmani’s blog post announcing native lazy-loading
- Chrome Platform Status feature page
- Lazy-load explainer by Scott Little
- HTML specs pull request
- Chrome platform status and release timeline
Thanks for the write-up, Erk!
I almost sent an email to my team to beware of tracking pixel changes. But I’m confused by your remark,
I don’t get that from the above prerequisites for lazy-loading. First, images with
loading="auto"
will only translate to lazy-loading when Data Saver is enabled. Second, neither frames nor images will be lazy-loaded if the most common (ubiquitous?) implementation methods of tracking pixels/frames aren’t used.Can you clarify “by default” and “analytics… industry will have to act”?
Hi Michael,
thanks for leaving a comment.
By “after a gradual roll-out” I was referring to the field trial described in the Blink LazyLoad design documentation. I assume that, if all goes well, lazy-loading will also become the default for devices with Data Saver disabled and also on other platforms than mobile.
With “by default” I meant that omitting the
loading
attribute will be treated asloading="auto"
.Finally, I agree that most tracking pixels and frames will indeed disqualify for lazy-loading due to tiny width and height attributes on them. However, this will certainly not cover all of them and analytics users will have to check if that is the case. Please also think about display ads that are actually visible to the user, thus qualify for lazy-loading, and might be requested and counted up to twice as often as before (image range request + full image request) or less than before (frames that are not requested at all).
Thanks for letting us know about this change!
I’m sure I’m not alone in saying I’ve had to have a chat to the back-end people at my office today about Range headers, HTTP2 and the like.
Great write-up Erk – I’m excited about this feature too and have been following its progress eagerly. This feature won’t land in Chrome 75 though – Chrome 75 is already in Beta. The feature hasn’t landed (without requiring a flag change) in Chrome 76 either.
The status is still ‘In Development’
https://www.chromestatus.com/features#browsers.chrome.status%3A%22In%20development%22
Hi Christian,
thanks for your comment. Unfortunately, the original tracking ticket is not publicly accessible. I’ll contact the authors directly, though, to get an update on the status and will edit this article as soon as I have an response.
Hi again Erk – you can see the current status of it at: https://groups.google.com/a/chromium.org/forum/m/#!topic/blink-dev/jxiJvQc-gVg . Sounds like it’s quite close now in which case we can hope it’d be in Chrome 77.
There is a public tracking bug at https://bugs.chromium.org/p/chromium/issues/detail?id=954323. According to that, the feature is postponed to at least version 76. However, since some blocker tickets are scheduled for 77 and also the Chrome platform status page does not list the feature for 76, I assume the feature to land in version 77 and updated the article accordingly.
Finally, i lost count of all the time spent setting up lazy loading solutions in the projects I have worked on!
Thanks for the write-up
Erk, great article! I can’t reply to the other discussion you had above, so posting a direct comment:
“I assume that, if all goes well, lazy-loading will also become the default for devices with Data Saver disabled and also on other platforms than mobile.”
You are of course free to assume, but where do you base this on? I would find it extremely unlikely that Chrome changes image loading behavior for the entire web as a default. My assumption instead is that it’s now behind a flag as a general setting, so that it allows for wide testing even without the use of the loading attribute. Once it would go “live”, it would decide per image what to do, and the default will be to not lazy-load if the property is not set on the image.
Your analytics remark is even more alarming. As much as I personally hate tracking, ads and the like it is absolutely unacceptable for a browser to just break something that works today. If your claim or assumption is true, millions and millions of marketeers would have a huge problem and act immediately. Nowhere have I seen any communication at all from Google implying this.
I don’t how for sure whether you are right or wrong on both claims, but my point is that you should be absolutely sure that what you say is truthful and verifiable, as both claims have an enormous impact on the web. If you’re right, more people need to know asap, if you’re wrong, the information should be corrected.
Hi Ferdy,
thanks for sharing your thoughts and concerns.
Regarding your first point, I think we mean similar things here: When the flags get enabled by default, the
loading="auto"
setting will implicitly become the default. As you pointed out, Chrome will then decide what to do per image, and lazy-loading only occurring under certain, comparably rare circumstances (Chrome Android, Data Saver, etc., as mentioned in the article).The “Blink LazyLoad Design” document lists several metrics which are collected during the field trial, e. g. page size, paint and interactive times, memory consumption, and so on. In the “Core principle considerations” section, the authors state they expect page load times, network data usage, and memory usage to improve. My assumption, that lazy-loading will be considered for other platforms than Andriod as well, is based on the fact that, if the expected improvements prove to be correct, I can’t think of many reasons NOT to use the feature on all platforms. After all, web performance is a key goal of our and the Chrome developers’ everyday work, isn’t it?
In addition, the last section of the “Blink LazyLoad Design” document explicitly states that “[i]f LazyLoad for Android is successful, then LazyLoad should be considered for desktop platforms”.
However, there are SOME reasons putting this success at risk, which brings me to your second point: compatibility. The “Blink LazyLoad Design” and “Blink LazyImages” design documents show that the authors are indeed aware of and expect these issues: incorrect impression counting of ads, code waiting for the OnLoad event breaking, delays for features that expect the whole page to be loaded (e. g. “Save as” or “Print”), double-fetching tracking pixels, server load due to a larger amount of requests, and issues when drawing images on a canvas.
Given the fact that the feature was postponed to a Chrome version > 76, there’s still plenty of time for Google to come up with a warning and marketers to react. Finally, the authors’ concerns were what led me to warn the readers of my article about these issues.
@Erk: Thanks for getting back to me. I think the keyword in the following quote…
“the world’s most popular browser will soon lazy-load below-the-fold images and frames by default.”
…is the word “will”. This makes it sound like a given fact, whilst if I understand you correctly, is more of a personal assumption or long term expectation, not an announcement. I most certainly would not exclude it being a possibility, but I think and hope such a dramatic move would be far away and should be far away. Or never happen at all.
First, conceptually, the promise of the web is backwards compatibility. Something that works today, should work tomorrow. Only in highly exceptional scenarios should backwards-compatibility be broken, for example for reasons of security. This not an abstract theory, giant parts of the web are under no maintenance at all or low maintance, which means that if you break it, it’s a permanent breakage.
Lazy loading images by default can break lots of things, in particular things depending on the onload event and the various historical plugins that are based on it. As mentioned, there may also be server issues, worst case even leading to additional costs.
And here’s a user scenario few think of: some people in the developing world hate lazy loaded images. Why? Given their slow network they tend to load a heavy page and wait for it to finish downloading entirely. Let’s say this takes a minute. After that minute, the upside is that they browse the entire page in one go. Lazy-loaded images, instead, means the entire reading experience sucks.
A strange angle to the problem, I admit, but interesting to consider.
Apart from breaking what I call the “old web”, which is most of the web, breaking ads and tracking would lead to a much more visible outcry. It’s how money is made on the web. Touch or break that and get ready for hell.