Native lazy loading turns on by writing loading="lazy" on an <img> or <iframe>. Every modern browser supports it as of 2026 (Baseline), and you no longer need a JavaScript library.
The explanations you find online, however, mix in outdated and simply incorrect claims. This article separates what actually works from what is commonly asserted but false, and covers how fetchpriority and decoding fit alongside it.
Sponsored
Which elements support the loading attribute
Short version: only <img> and <iframe>. The values are lazy and eager (the default).
<img src="photo.jpg" width="800" height="600" alt="Sample" loading="lazy">
<iframe src="https://www.google.com/maps/embed?pb=..." width="600" height="400"
loading="lazy" title="Store location map"></iframe>
| Element | loading |
Use instead |
|---|---|---|
<img> |
Supported | — |
<iframe> |
Supported | — |
<video> |
Not supported | preload="none" plus poster |
<source> |
Not supported | Put it on the inner <img> |
| CSS background image | Not supported | Intersection Observer |
<script> |
Not supported | defer / async / dynamic import() |
Inside <picture>, write loading="lazy" on the <img>, not on <source>—the <img> is what makes the decision.
<picture>
<source srcset="photo.avif" type="image/avif">
<source srcset="photo.webp" type="image/webp">
<img src="photo.jpg" width="800" height="600" alt="Sample" loading="lazy">
</picture>
“The browser lazy-loads automatically” is false
If you do not write the attribute, nothing is deferred. The default is eager.
This misconception has a real origin. In 2019 Chrome shipped a feature that automatically deferred off-screen images and iframes for Android users on Lite mode (Data Saver), and there was a loading="auto" value.
But both Lite mode and loading="auto" have been removed, and Chrome has stated there are no plans to lazy-load images automatically. As of 2026, writing loading="lazy" yourself is the only path.
Similarly, no loading attribute has ever been added to <video> or <source>. To defer a video:
<video src="movie.mp4" poster="thumbnail.jpg"
width="640" height="360" controls preload="none"></video>
preload="none" stops the video data from downloading, and poster shows a lightweight still image. That is the video equivalent.
Sponsored
Why width and height are mandatory
Without them you get layout shift (CLS). An unloaded image has zero height, so everything below it jumps down the moment the image arrives.
<!-- Box reserved in advance -->
<img src="photo.jpg" width="800" height="600" alt="Sample" loading="lazy">
<!-- Content jumps when the image lands -->
<img src="photo.jpg" alt="Sample" loading="lazy">
Even for fluid responsive images, put the intrinsic dimensions in the HTML attributes and adjust with CSS. The browser derives the aspect ratio from width and height and reserves the space.
img {
max-width: 100%;
height: auto;
}
Do not put lazy on above-the-fold images
It will degrade LCP. With loading="lazy" on the main image at the top of the page, the browser does not begin fetching until layout is resolved, delaying the largest paint by hundreds of milliseconds or more.
For that image, raise the priority instead:
<img src="hero.jpg" width="1200" height="630" alt="Main visual"
fetchpriority="high" decoding="async">
| Attribute | Effect | When to use |
|---|---|---|
loading="lazy" |
Defers the fetch | Images below the fold |
fetchpriority="high" |
Raises fetch priority | The LCP hero image |
fetchpriority="low" |
Lowers fetch priority | Decorative images, footer banners |
decoding="async" |
Decodes off the main thread | Basically every image |
The test is one question: is it visible without scrolling? If you use a plugin that adds loading="lazy" to every image, check whether it can exclude the first one. Fixing just that often produces a visible LCP improvement.
Sponsored
How far in advance does loading start?
The browser begins fetching at some distance before the viewport, and that distance is not fixed—it varies with effective connection speed. Chrome uses a shorter threshold on fast connections and a longer one on slow ones.
In other words, you cannot tune the threshold. If you need images to start earlier, your options are:
- Do not mark it
lazyat all (it is an important image) - Use Intersection Observer and set
rootMarginyourself
The second option is covered in how to optimise lazy loading with the Intersection Observer API.
Fading images in as they load
You do not need to go back to the data-src pattern for this. Adding a class on the load event is enough.
img[loading="lazy"] {
background-color: #f2f2f2;
opacity: 0;
transition: opacity .4s;
}
img[loading="lazy"].is-loaded {
opacity: 1;
}
@media (prefers-reduced-motion: reduce) {
img[loading="lazy"] { opacity: 1; transition: none; }
}
document.querySelectorAll('img[loading="lazy"]').forEach((img) => {
if (img.complete) {
img.classList.add('is-loaded'); // catch cached images
return;
}
img.addEventListener('load', () => img.classList.add('is-loaded'), { once: true });
});
The img.complete check is required. For images served from cache, load may have already fired before your script runs. Skip it and some images stay permanently transparent—an unpleasant bug to track down.
Measuring the effect
Do not set and forget. Compare before and after on three metrics:
- LCP: did you lazy-load the hero image? Suspect this first if it got worse
- CLS: are
widthandheightpresent everywhere? - Transfer size: in DevTools’ Network panel, did the number of image requests on initial render drop?
Lighthouse reports both “Defer offscreen images” and “Largest Contentful Paint image was lazily loaded.” Seeing both at once is the quickest sign that something is wrong.
Summary
loadingworks on<img>and<iframe>only—not<video>or<source>- The default is
eager. Nothing is deferred unless you write it; no browser does it automatically loading="auto"and Chrome’s Lite mode auto-deferral are both gone- Always write
widthandheightto prevent CLS - Never lazy-load the hero image; use
fetchpriority="high" - The trigger distance is not configurable—use Intersection Observer if you need control
- Fade-in needs only the
loadevent plus animg.completecheck
It comes down to two attributes, but deciding which images should not get them matters more than adding them. Start by reviewing the first image on the page.