How to Lazy-Load CSS Background Images
This guide extends Lazy-Loading Images Without Hurting LCP, part of Image & Media Optimization. The native loading="lazy" attribute only applies to <img> and <iframe> elements. Images set with background-image in CSS follow different rules: the browser fetches a background image when an element that matches the rule is rendered (has a computed style and a box), regardless of whether it is on screen. A long landing page with a dozen decorative section backgrounds therefore downloads all of them during the initial load, competing with the LCP image and fonts for bandwidth.
There is also the opposite problem. Hero banners are often CSS backgrounds, and a background image can be the LCP element. Because the browser discovers it only after the CSS has downloaded and the style has been computed, a background hero is already late; lazy-loading it with JavaScript makes it later still. The right approach is to defer the backgrounds below the fold and make the one above the fold discoverable early.
Rapid Diagnosis
- Filter the Network panel by Img and reload: CSS-initiated images show the stylesheet as the initiator. Count how many download before any scrolling.
- Check the LCP element in the Performance panel. If it is a
divorsection, the LCP resource is a background image. - Look at request start times. Background images cannot start until the stylesheet that references them has arrived and layout is under way.
- Check element visibility rules. Elements hidden with
display: nonedo not load their backgrounds; elements merely off-screen do.
Root Cause Analysis
1. Backgrounds load when styled, not when visible. Any rendered element matching the rule triggers the fetch, regardless of scroll position.
2. No native lazy attribute. CSS has no equivalent to loading="lazy", so deferral needs a class toggle or a rendering trick.
3. Late discovery for heroes. The preload scanner reads HTML, not CSS rules, so a background hero is found only after the CSS is parsed.
4. Responsive backgrounds via media queries. Several background-image declarations across breakpoints are fine — only the matching one loads — but authors sometimes stack them in ways that load two.
Step-by-Step Resolution
1. Gate below-the-fold backgrounds behind a class
/* Backgrounds are only applied once the section is near the viewport. */
.promo { background-color: #e8eef6; } /* placeholder colour */
.promo.is-visible { background-image: image-set(url(/img/promo.avif) type("image/avif"), url(/img/promo.jpg) type("image/jpeg")); }
/* trade-off: without JavaScript the class never appears; keep the placeholder
colour attractive, or add a noscript style that applies the image. */
const io = new IntersectionObserver((entries) => {
for (const e of entries) if (e.isIntersecting) { e.target.classList.add('is-visible'); io.unobserve(e.target); }
}, { rootMargin: '400px 0px' });
document.querySelectorAll('.promo, .feature-bg').forEach((el) => io.observe(el));
// trade-off: a larger rootMargin hides loading on fast scrolls but loads more
// images that may never be seen; 300-600px suits most pages.
2. Use content-visibility for whole sections
content-visibility: auto on long sections skips rendering offscreen content, which also delays backgrounds inside it until the section approaches the viewport — without JavaScript.
.section { content-visibility: auto; contain-intrinsic-size: auto 800px; }
/* trade-off: browser support and behaviour vary; treat it as a progressive
enhancement, and always set contain-intrinsic-size to avoid layout shifts. */
3. Make a background hero discoverable early
If the hero must stay a background, preload it with responsive attributes and high priority, or better, convert it to an <img> with object-fit: cover.
<link rel="preload" as="image" fetchpriority="high"
imagesrcset="/img/hero-800.avif 800w, /img/hero-1600.avif 1600w" imagesizes="100vw" type="image/avif">
<!-- trade-off: the preload must match what CSS selects at each breakpoint, or
the browser downloads two images. An <img> avoids this duplication. -->
4. Avoid double downloads across breakpoints
Put alternative backgrounds in mutually exclusive media queries (min-width and max-width pairs) rather than overriding a default image at a breakpoint, which can load both in some cases.
Verification
Reload with the cache disabled and confirm only above-the-fold backgrounds download before interaction; scroll and watch the deferred ones appear in the Network panel shortly before their sections reach the viewport. In the Performance panel, the LCP resource should start earlier and finish without competing downloads. In RUM, compare LCP resourceLoadDelay for pages with background heroes before and after.
Worked Example: A SaaS Landing Page
A SaaS landing page had nine full-width sections, each with a decorative AVIF background (60–180KB each), and a hero set as a CSS background. On a mid-range phone over 4G, all ten images started downloading within 100ms of the stylesheet arriving, and the hero (the LCP element) finished at 2.9s. The team converted the hero to an <img fetchpriority="high"> with object-fit: cover, and gated the nine section backgrounds behind an is-visible class with a 500px root margin. Initial image bytes fell from 1.2MB to 140KB, and LCP p75 on mobile improved from 2.9s to 1.7s. The sections still looked complete on normal scrolling because their backgrounds arrived before they entered the viewport.
Backgrounds vs Inline Images
Many problems with CSS backgrounds disappear when the image is content rather than decoration. An <img> gets responsive selection with srcset and sizes, native lazy loading, fetchpriority, alt text, early discovery by the preload scanner, and accurate LCP attribution. CSS backgrounds are best kept for genuinely decorative textures, patterns and gradients. A useful rule: if removing the image would change the meaning of the section, or if it is larger than a few kilobytes and above the fold, it probably belongs in the markup.
Common Mistakes
- Lazy-loading a background hero. Delays the LCP further.
- Using
display: noneto defer. It works but also removes the element from layout and accessibility; it is not a deferral strategy for visible content. - Forgetting a placeholder colour. Sections flash white as backgrounds arrive.
- Preloads that do not match CSS. Two downloads of the same image in different sizes.
Edge Cases
Pseudo-elements. Backgrounds on ::before and ::after follow the same rules; toggle a class on the parent.
Print styles. Background images are usually not printed by default; no deferral concerns there.
SPA route changes. Re-run observer setup after client-side navigations, or use a framework directive.
Very small images. Icons and patterns under a few kilobytes are better inlined or left alone; deferral overhead outweighs the savings.
FAQ
Does loading="lazy" work on divs?
No. The attribute only applies to <img> and <iframe>. Background images need a class toggle, content-visibility, or conversion to an <img>.
Can a CSS background image be the LCP element?
Yes. Browsers report background images as LCP candidates. They are often slow because they are discovered only after the CSS has loaded.
Is image-set() supported?
Modern browsers support image-set() with type() for format selection. Keep a plain url() fallback declaration before it for older browsers.
Does content-visibility delay background images?
Skipped content is not rendered, so its backgrounds are not fetched until the section is about to be rendered. Behaviour can vary, so verify in the Network panel.
What root margin should I use?
Between 300 and 600px works for most pages. Use larger margins for fast-scrolling feeds and smaller ones for long pages with heavy images that many users never reach.
Will search engines see deferred background images?
Decorative backgrounds are generally not indexed as content anyway. Images that matter for search should be <img> elements with alt text.
Related
- Fixing lazy-loaded images that delay LCP — the same mistake with
<img>. - Native lazy loading vs IntersectionObserver — choosing the deferral mechanism.
- Using fetchpriority to prioritize the LCP image — making the hero fast.