Lazy load AVIF images correctly. Native attribute, IntersectionObserver, priority hints, and the rules that decide which images should never lazy load.
Load AVIF Images Only When They Matter
Lazy loading tells the browser to fetch an AVIF image when it nears the viewport, not at page load. This guide covers the native attribute, the one image that must never use it, and how to keep Core Web Vitals green.
When to lazy load an AVIF image
Lazy load every AVIF image except the one inside the first viewport. Above-the-fold images load eagerly; everything below the fold defers.
Three image roles decide the attribute:
- Hero or LCP image — eager, with
fetchpriority="high". Never lazy. - Below-the-fold photos — lazy, with
decoding="async". Examples: footer logos, article body figures, gallery thumbnails 3+. - Borderline images near the fold line — eager, because a wrong guess regresses Largest Contentful Paint.
Only 20–30% of below-the-fold images are ever scrolled into view, so deferring them saves real bandwidth.
The one-line native attribute
Add loading="lazy" to the <img> tag — no JavaScript required. Every current browser supports it.
<img
src="/photos/gallery-04.avif"
alt="Slate rooftops over a harbour at dusk"
width="1200"
height="800"
loading="lazy"
decoding="async"
/>
Four attributes do the work:
loading="lazy"— defers the network request until the image nears the viewport.decoding="async"— moves pixel decode off the main thread, so scrolling stays smooth.widthandheight— reserve layout space so the image cannot shift content.
loading="lazy" works on <img> and <iframe> only. CSS background-image needs the IntersectionObserver fallback.
Why the hero image must load eagerly
Lazy-loading the LCP image delays it by 200–800ms, because the browser skips it during the initial scan. AVIF cannot help an image the browser refuses to fetch early.
The hero gets the opposite treatment:
<img
src="/photos/hero.avif"
alt="Aerial view of terraced vineyards"
width="1600"
height="900"
fetchpriority="high"
decoding="async"
/>
Note what is absent: no loading="lazy". The default loading="eager" is correct for above-the-fold content.
fetchpriority="high" promotes the AVIF request ahead of fonts, scripts, and other images. Apply it to exactly one image per page — the LCP candidate. Google now recommends fetchpriority="high" with no loading attribute for above-the-fold images.
For an even faster start, preload that single AVIF from <head>:
<link
rel="preload"
as="image"
href="/photos/hero.avif"
type="image/avif"
fetchpriority="high"
/>
Preload one image only. Competing preloads slow the one that decides your LCP score.
Lazy loading inside a <picture> fallback
Put loading="lazy" on the <img>, never on a <source>. The deferral applies to whichever source the browser selects.
AVIF ships with a WebP or JPG fallback for the ~6% of users on older browsers — see AVIF Browser Support.
<picture>
<source type="image/avif" srcset="/photos/figure.avif" />
<source type="image/webp" srcset="/photos/figure.webp" />
<img
src="/photos/figure.jpg"
alt="Cross-section diagram of a turbine blade"
width="1024"
height="768"
loading="lazy"
decoding="async"
/>
</picture>
The browser picks AVIF first, falls back to WebP, then JPG — and lazy-loads whichever one it chose. The <source> elements carry only type and srcset; loading behaviour lives on the <img>.
Always set width and height
Set intrinsic width and height on every AVIF image to prevent layout shift. Missing dimensions are the top cause of Cumulative Layout Shift.
Without dimensions, the browser reserves no space, then jolts the page when the AVIF decodes. Lazy loading makes this worse: the shift fires mid-scroll instead of during first paint.
Two ways to declare the box:
- Attributes —
width="1200" height="800". The browser computes the aspect ratio automatically. - CSS
aspect-ratio—aspect-ratio: 3 / 2when the rendered size is fluid.
Either reserves the slot before the bytes arrive, holding CLS near zero.
The IntersectionObserver fallback
Use IntersectionObserver only when the native attribute cannot reach the element — chiefly CSS background images and custom load thresholds.
The attribute beats hand-written observers for ordinary <img> tags, because the browser knows connection speed and scroll velocity. Reserve JavaScript for these cases:
- CSS
background-image— the attribute does not apply to backgrounds. - Custom trigger distance —
rootMargincontrols how early the fetch starts. - Placeholder transitions — fading a blurred LQIP to the full AVIF on visibility.
A minimal observer that swaps a data-bg AVIF into a background:
const targets = document.querySelectorAll("[data-bg]");
const io = new IntersectionObserver(
(entries, observer) => {
for (const entry of entries) {
if (!entry.isIntersecting) continue;
const el = entry.target;
el.style.backgroundImage = `url(${el.dataset.bg})`;
observer.unobserve(el);
}
},
{ rootMargin: "300px 0px" },
);
for (const el of targets) io.observe(el);
rootMargin: "300px 0px" starts the AVIF fetch 300 pixels before the element scrolls in, so the background is ready on arrival.
What this does to Core Web Vitals
Correct lazy loading moves two of the three Core Web Vitals metrics in the right direction:
- LCP — improves when the hero loads eagerly with
fetchpriority="high"; target under 2.5s at the 75th percentile. - CLS — stays near zero when every image carries
width/height; target under 0.1. - INP — benefits indirectly, because deferred decodes free the main thread during interaction.
AVIF amplifies the win: files are ~50% smaller than JPEG, so the bytes you do fetch arrive sooner. See AVIF Optimisation for the full pipeline and AVIF in React for component-level handling.
Mistakes that cancel the benefit
Five errors turn a lazy-loading win into a regression:
- Lazy-loading the hero — the LCP image defers, the score jumps, and AVIF takes the blame. Exempt the LCP candidate per template.
- Omitting dimensions — every image without
width/heightshifts layout on decode. - Mixing
loading="lazy"withfetchpriority="high"— they contradict; the browser obeys lazy and ignores the hint. Pick one. - Shipping a lazy-load library — 5–15 KB of JavaScript to do what the browser does in zero bytes. Audit and remove it.
- Skipping
decoding="async"— the AVIF decode then blocks the main thread the moment the image scrolls in, stuttering the scroll.
Tie these fixes back to delivery with Image SEO Best Practices.
FAQ
Does AVIF need different lazy-loading code than JPG?
No. loading="lazy" is format-agnostic — identical markup defers AVIF, JPG, WebP, or PNG.
Can I lazy load AVIF in older browsers?
Yes, via the <picture> fallback. Browsers without AVIF support load the WebP or JPG source and still honour loading="lazy".
Should I lazy load every below-the-fold AVIF?
Yes, with one exception. Defer all below-fold images, but keep borderline images near the fold line eager to protect LCP.
Is a JavaScript lazy-load library still worth it?
Rarely. The native attribute covers <img> and <iframe>; reserve scripts for CSS backgrounds or custom thresholds.
Why is my LCP worse after adding lazy loading?
You likely lazy-loaded the hero. Remove loading="lazy" from the LCP image and add fetchpriority="high".
Convert your images to AVIF
These converters run entirely in your browser — the image never leaves your device: