Image & Media Delivery

How to Lazy Load Background Images in WordPress

If you have ever audited a WordPress site with PageSpeed Insights, you have probably seen a familiar pattern: the page itself is reasonably lean, the images have loading=”lazy” on them, and yet a handful of large images still download the moment the page opens. Open the Network tab and you will often find the culprits hiding in the stylesheet: background images.

Background images are a blind spot in most WordPress speed work. They are set in CSS, not in HTML, which means the browser’s native lazy loading simply does not apply to them. Every background image referenced by a visible element’s CSS loads eagerly, whether the visitor ever scrolls to it or not. On image-heavy themes — hero sliders, section dividers, card grids, parallax blocks — that can mean megabytes of images downloaded for sections nobody sees.

The good news is that lazy loading background images is not hard. It takes a small JavaScript swap, a bit of CSS discipline, and a clear rule about what should never be deferred. This guide walks through all three, plus the plugin route if you would rather not touch code.

Why background images ignore lazy loading

Native lazy loading works through the loading attribute on img and iframe elements. The browser reads that attribute, sees that the element is below the fold, and holds the request until the element is about to enter the viewport.

A CSS background-image has no such attribute. There is no HTML element carrying the image, only a style rule. When the browser matches that rule to an element on the page, it fetches the image immediately — even if the element sits three screens down, inside a collapsed accordion, or in a tab the visitor never opens.

This is worth understanding because it changes how you think about the problem. You cannot fix it by adding an attribute or toggling a checkbox in a caching plugin. You have to change when the style rule itself applies, or replace the background image with something the browser can defer.

There is one more wrinkle. Background images are invisible to some audit tools in the same way they are invisible to lazy loading. A tool may flag “properly size images” or “defer offscreen images” without clearly pointing at CSS backgrounds, so the real cost only shows up when you filter the Network tab by image requests and compare it against what is actually on screen.

What to lazy load — and what never to

Before writing any code, decide which backgrounds qualify. Lazy loading the wrong image hurts more than it helps.

Lazy load these:

  • Section backgrounds below the fold: full-width dividers, testimonial bands, call-to-action strips.
  • Card and grid backgrounds: portfolio tiles, service cards, team member panels.
  • Footer backgrounds and decorative textures that appear only after scrolling.
  • Images inside tabs, accordions, and sliders that are not visible on first paint.

Never lazy load these:

  • The hero background. If the first thing a visitor sees is a background image, deferring it will delay Largest Contentful Paint and make the top of the page look broken while it loads. Hero images should load eagerly, ideally preloaded.
  • Any background that is part of the initial viewport on common screen sizes. If you are unsure whether an element is above the fold on a phone, treat it as above the fold.
  • Backgrounds in the site header or navigation, which are visible from the first paint.

A useful rule of thumb: if a visitor would notice the image missing during the first second on the page, do not lazy load it. Everything else is fair game.

Method 1: The IntersectionObserver swap (recommended)

The most reliable technique is simple in concept. Instead of putting the image URL in the CSS that applies immediately, you store it in a data attribute on the element, and a small script swaps it into a real background-image only when the element approaches the viewport.

Start by changing the markup. Where you currently have a section styled with a background image, give it a class you control and move the URL into a data attribute:

<div class="lazy-bg" data-bg="https://example.com/wp-content/uploads/2026/10/section-texture.jpg"></div>

Then define the CSS so that the element has its layout — height, background size, background position — but no image yet:

.lazy-bg {
  background-size: cover;
  background-position: center;
  background-repeat: no-repeat;
}

The JavaScript does the rest. IntersectionObserver is built into every modern browser and is far cheaper than listening to scroll events, because the browser itself tells your script when an element is near the viewport:

document.addEventListener("DOMContentLoaded", function () {
  const lazyBackgrounds = document.querySelectorAll(".lazy-bg");
  if ("IntersectionObserver" in window) {
    const observer = new IntersectionObserver(function (entries, observer) {
      entries.forEach(function (entry) {
        if (entry.isIntersecting) {
          const el = entry.target;
          el.style.backgroundImage = "url(" + el.dataset.bg + ")";
          el.classList.add("lazy-bg-loaded");
          observer.unobserve(el);
        }
      });
    }, { rootMargin: "200px 0px" });
    lazyBackgrounds.forEach(function (el) {
      observer.observe(el);
    });
  } else {
    lazyBackgrounds.forEach(function (el) {
      el.style.backgroundImage = "url(" + el.dataset.bg + ")";
    });
  }
});

The rootMargin of 200px is deliberate. It starts loading the image slightly before the element scrolls into view, so by the time the visitor arrives, the image is usually already there. Increase it to 400px or so on image-heavy pages if you notice backgrounds popping in late; decrease it if you want to be stricter about saving bandwidth.

Where do you put this code? On most WordPress sites the cleanest options are a child theme’s JavaScript file, a code-snippets plugin, or your theme’s custom JavaScript panel if it has one. Avoid pasting it into individual posts — it belongs at theme level so it applies everywhere the lazy-bg class is used.

One practical note for page builders: Elementor, Divi, and similar builders set background images through their own controls, which output inline styles. The data-attribute technique still works, but you will usually need to add the class through the builder’s advanced settings and either remove the builder’s background setting or override it with CSS. If your builder fights you, the plugin route below is the easier path.

Add a fade-in so the swap is invisible

A hard swap from blank background to full image can look jarring, especially on slower connections. A short opacity transition on a wrapper or an overlay smooths it out. Keep it subtle — a 300 to 500 millisecond fade is plenty. Anything longer feels sluggish rather than polished, and on low-end phones long transitions can themselves cause jank.

Method 2: Let a plugin handle it

If editing theme code is not something you want to do, a few performance plugins handle background lazy loading for you:

  • Perfmatters includes a lazy loading option that covers background images, with exclusions for above-the-fold elements. This is the most straightforward option if you already use it for script management.
  • FlyingPress offers background image lazy loading as part of its image optimization settings.
  • Some image optimization plugins have added background support in recent versions, though coverage varies — check the changelog rather than assuming it works.

Whichever plugin you choose, verify two things after enabling it. First, that your hero background is excluded, either automatically or through the plugin’s exclusion list. Second, that the plugin actually defers the backgrounds you care about — open the Network tab, reload, and confirm that below-fold background images no longer appear in the initial request list. A setting that is switched on but not working is worse than no setting at all, because it stops you looking further.

Plugin lazy loading also has a limitation worth knowing: it typically works by rewriting inline styles or known builder output. Custom CSS in a child theme stylesheet, or backgrounds set through unusual markup, may slip past it. If you audit after enabling a plugin and some backgrounds still load eagerly, that is usually why — and it is a sign to handle those specific elements with the manual method above.

Method 3: Replace the background with a real image

Sometimes the best fix is not lazy loading at all, but changing the technique. If a “background” image is actually content — a product photo, a portfolio piece, a team portrait — it probably should not be a background image in the first place.

A regular img element with object-fit: cover gives you the same visual result as a background image, with three advantages: native lazy loading works, the browser can pick the right size from srcset, and the image gets an alt attribute for accessibility. If you are rebuilding a section anyway, this is usually the better choice. Reserve true CSS backgrounds for decorative textures and patterns, where the image adds atmosphere but carries no information.

This is also the fix to reach for when a background image keeps hurting your scores despite lazy loading. An img element is measurable, controllable, and understood by every audit tool. A background image is always a workaround by comparison.

For the general principles of lazy loading regular images — including the critical rule about never lazy loading the hero image — see our complete guide to lazy loading images in WordPress. The same above-the-fold logic applies here, just with a different mechanism.

Test that it actually worked

Lazy loading background images is one of those optimizations that feels done long before it is verified. Run through this short check after implementing any method:

  1. Open the page in a private browser window with DevTools open on the Network tab, filtered to images.
  2. Reload without scrolling. Note which images load. Below-fold backgrounds should be absent.
  3. Scroll slowly to the bottom. The deferred images should appear in the Network tab as their sections approach the viewport.
  4. Run PageSpeed Insights before and after, on the same URL, and compare total transferred bytes and the “defer offscreen images” audit — not just the headline score, which moves for many reasons.
  5. Check the page on a real phone over a throttled connection. Background swaps that look instant on desktop broadband can pop in visibly on a mid-range Android device, and that is where your rootMargin and fade-in choices get tested.

Also re-check your Largest Contentful Paint element after making changes. If your LCP element was previously a background image, lazy loading it by mistake will make LCP worse, not better. Our guide to fixing the “LCP image was lazily loaded” warning covers how to identify and protect the LCP element. If you are chasing layout shift at the same time, make sure lazy-loaded sections reserve their height in advance — an element with no fixed height that suddenly gains a background can shift content below it, which is exactly the kind of problem our CLS fix guide addresses.

Common mistakes to avoid

Lazy loading the hero. This is by far the most common error, and it directly hurts LCP. If a background sits in the first viewport, leave it alone — or better, preload it.

Forgetting mobile. An element that is below the fold on desktop may be the first thing visible on a phone, because mobile layouts stack vertically. Test your exclusions on a phone viewport, not just on desktop.

No reserved height. A section with no minimum height and no content will collapse to nothing until its background loads, then jump when the image arrives. Give lazy-loaded sections a min-height or aspect ratio so the layout is stable before and after the swap.

Lazy loading tiny textures. A small repeating pattern or gradient-like texture costs almost nothing and is often needed immediately for the design to look right. Deferring it saves negligible bytes and adds complexity. Focus on the large photographic backgrounds.

Assuming the plugin did it. As noted above, always verify in the Network tab. Plugins vary in what they catch, and custom theme CSS is the usual escape route.

A quick checklist

  • Audit with the Network tab and list every background image that loads on first paint.
  • Mark hero and above-the-fold backgrounds as do-not-defer, and preload the hero if it is large.
  • Move below-fold backgrounds to a data attribute and swap them in with IntersectionObserver, or enable background lazy loading in your performance plugin.
  • Add a short fade-in and reserve section heights so the swap is invisible.
  • Verify on desktop and on a real phone: no below-fold backgrounds in the initial load, no LCP regression, no layout shift.

Background images are easy to overlook precisely because they are not in your HTML. Once you start checking for them, you will find them on almost every WordPress site — and on image-heavy themes they are often the single biggest easy win left after regular images are already optimized. Fix them properly, protect the hero, and the bytes you save are bytes your visitors never had to download.

Leave a Reply

Your email address will not be published. Required fields are marked *