Images can significantly enhance a website’s attractiveness, but they also carry a performance cost. While <img>
elements have long benefited from built-in lazy loading via the loading="lazy"
attribute, background images applied through CSS do not enjoy this convenience.
Lazy loading background images provides several key benefits for improving web performance, including reduced page load times, lower data usage, and improved overall user experience, especially on mobile and low-bandwidth networks.
In this article, you’ll learn how to implement lazy loading for background images, and how to enhance user experience with placeholders, as well as how to optimize background images by combining CSS capabilities with modern solutions like Cloudinary.
In this article:
- Why Would You Lazy Load Background Images?
- Implementing Lazy Loading with the Intersection Observer API
- Enhancing User Experience with Low-Quality Image Placeholders
- Optimizing Background Images with Cloudinary
Why Would You Lazy Load Background Images?
Images are the most common digital assets in modern web development. In fact, in a 2024 report, of the more than 10 million scanned and parsed pages, 99.9% requested at least one image, according to data by the Web Almanac. Despite their popularity, images can quickly take up a website’s bandwidth quickly if they are not used correctly.
Lazy loading is a performance optimization strategy where images are only loaded when they’re needed, typically when they’re about to enter the viewport. This technique offers several benefits for background images, including:
- Faster Page Load Time: Without lazy loading, every single image attempts to load when a user first visits the page, regardless of whether they scroll down to see them all. By lazy loading, images only fetch when they enter the user’s viewport, speeding up the initial page load while saving on bandwidth and storage.
- Improved Core Web Vitals: Lazy loading positively impacts key performance metrics, particularly Largest Contentful Paint (LCP), which measures how long it takes for the largest content element to become visible, and First Input Delay (FID), which measures the time from when a user first interacts with a page to the time when the browser can actually respond to that interaction.
- Better Mobile Performance: This is especially important for users with slower internet speeds or those on metered data plans. Lazy loading conserves data, making your site more accessible and enjoyable on low-end mobile devices.
When You Shouldn’t Lazy Load Background Images
Lazy loading is usually beneficial, but some situations call for different methods. You should avoid lazy loading background images when:
- The Image Appears in the Initial Viewport: Lazy loading an image that appears in the initial viewport or landing page can lead to visual flickering or content shifting that can negatively impact user experience and harm SEO. Lazy loading should only be applied to images that do not immediately appear in the initial viewport or landing page.
- Small or Optimized Images: If your background images are already highly compressed or lightweight, lazy loading might not be necessary. In some cases, the overhead introduced by lazy loading could outweigh any potential performance benefits.
- Critical or Contextual Images: When background images are used for important visual elements, such as branding or storytelling with overlaid text, lazy loading may cause delays that disrupt the visual context or user experience. In such scenarios, it’s often better to load these images immediately to preserve the intended impact.
Implementing Lazy Loading with the Intersection Observer API
One of the oldest methods developers use to implement element visibility detection on websites is by measuring scroll positions using event listeners. However, most of these methods were inefficient and often lead to performance problems. To address these issues, the Intersection Observer API was introduced.
According to MDN, “the Intersection Observer API provides a way to asynchronously observe changes in the intersection of a target element with an ancestor element or with a top-level document’s viewport.” In other words, it lets you know when an element enters or leaves the visible part of a webpage (or another element), and you can use this information to trigger actions like loading images or animations.
The intersection observer API requires three important components:
- Root Element (or Container): This is the ancestor element or the browser’s viewport that the target element will be observed against.
- Target Element: This is the specific element you want to observe for intersection changes.
- Callback Function: This function is automatically executed whenever the target element’s intersection status with the root element changes. It’s where you’ll define the actions to take when an intersection occurs.
Some of the applications of the intersection observer API include tasks like lazy-loading images, implementing infinite scrolling, or triggering animations when elements become visible. In the next sections, we’ll show how you can implement lazy loading using the intersection observer API.
Set Up HTML and CSS
To get started, we need to first set up the HTML and CSS for the layout. We’ll use a data-bg
attribute to store the image URL instead of applying it directly in CSS. We’ll also add some placeholder content to make sure the image is not in the initial viewport:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>Document</title> </head> <style> body { margin: 0; padding: 0; font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; color: white;; } .above-fold-content { padding: 20px; text-align: center; min-height: 100vh; display: flex; place-items: center; place-content: center; background-color: #0b663e; } .above-fold-content2 { padding: 20px; text-align: center; min-height: 100vh; display: flex; place-items: center; place-content: center; background-color: #151163; } .lazy-bg { position: relative; background-color: #858484; background-size: cover; background-position: center; min-height: 500px; transition: background-image 0.3s ease; } .lazy-bg.loaded { background-color: transparent; } .text-overlay { position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); color: #fff; text-align: center; padding: 20px; } .text-overlay h2 { margin: 0 0 10px; font-size: 2rem; } .text-overlay p { margin: 0; font-size: 1rem; } </style> <body> <section class="above-fold-content"> <h1>Section One</h1> </section> <section class="above-fold-content2"> <h1>Section Two</h1> </section> <div class="lazy-bg" data-bg="https://images.unsplash.com/photo-1517685352821-92cf88aee5a5" > <div class="text-overlay"> <h2>Welcome to Our Site</h2> <p>Discover our latest offerings with stunning visuals.</p> </div> </div> </body> </html>
Add JavaScript
Next, add the following in a script
tag just before the closing body
tag in the HTML:
<script> document.addEventListener("DOMContentLoaded", () => { const images = document.querySelectorAll(".lazy-bg"); // Define observer options const observerOptions = { rootMargin: "100px", // Load the image 100px before they enter viewport threshold: 0.1, // Trigger when 10% of the element is visible }; // Callback function function handleIntersection(entries, observer) { entries.forEach((entry) => { if (entry.isIntersecting) { console.log("Image is intersecting:", entry.target); const element = entry.target; const bgImage = element.getAttribute("data-bg"); if (bgImage) { element.style.backgroundImage = `url('${bgImage}')`; element.classList.add("loaded"); } } }); } // Create IntersectionObserver with callback and options const observer = new IntersectionObserver( handleIntersection, observerOptions ); images.forEach((bg) => observer.observe(bg)); }); </script>
Here’s what the above code does:
- First, the script finds the target element by selecting all elements with the
.lazy-bg
class that need lazy-loaded background images. - Next, we configure the observer to trigger when 10% of an element is visible, starting 100px before it enters the viewport.
- Finally, when an element becomes visible, the script reads the image URL from the
data-bg
attribute and sets it as the background image.
To confirm that the background image is lazy loading correctly, you can use your browser’s developer tools. Most modern browsers, such as Chrome, Firefox, and Edge, provide tools to inspect network activity and element behavior.
Here’s how to verify lazy loading in Chrome DevTools:
- Navigate to the Network tab and ensure the “Disable cache” option is checked to simulate a fresh page load.
- Observe the list of resources loaded by selecting “All” in the resources type tab. The background image with the
data-bg
attribute should not appear in the initial network requests if they are below the fold.
- Next, slowly scroll down the page toward the lazy-loaded background image and watch the Network tab for new image requests. When an element with the
lazy-bg
class enters the viewport, you should see thedata-bg
URL:https://images.unsplash.com/photo-1517685352821-92cf88aee5a5
appear in the network log.
Enhancing User Experience with Low-Quality Image Placeholders
In the example above, before the image was fully loaded, you’d notice a blank space while the background image loads. This behaviour can create an unpleasant experience for users, especially those with a poor internet connection.
To prevent this, you can use Low-Quality Image Placeholders (LQIP) to create a smooth user experience. This technique involves presenting a low-resolution, often blurred or miniaturized version of the image as a temporary substitute until the full-quality image has completely loaded.
While LQIP has its advantages, there are also drawbacks to consider. For instance, if your LQIPs are hosted as separate external URLs instead of being embedded inline, they introduce additional HTTP requests during the initial page load. This can offset some benefits of lazy loading by increasing the page’s load time.
To address these drawbacks, you might consider alternatives like skeleton loaders. These are animated placeholders that mimic the layout of a page while content is loading. Unlike LQIP, skeleton loaders are lightweight, performant, and do not introduce the same overhead.
Optimizing Background Images with Cloudinary
Cloudinary is a cloud-based media management platform that simplifies image optimization and delivery. Cloudinary’s particularly useful for managing background images, offering responsive sizing, format conversion, and dynamic transformations to ensure images look great and optimized for each user.
Cloudinary allows you to optimize images through transformation parameters via URLs or its programmable SDKs, eliminating the need for placeholder images. For instance, here’s how you can append parameters to image URLs to dynamically adjust their size, format, and quality:
https://res.cloudinary.com/your-cloud-namr/image/upload/q_auto,f_webp/your-image.jpg
In the snippet above:
q_auto
automatically optimizes quality for a balance of size and clarity.f_webp
converts to WebP format for smaller file sizes.
In the previous example, you can simply replace data-bg
URL with the Cloudinary URL of your image and Cloudinary will apply its intelligent algorithms to optimize, resize and deliver the background image based on each user’s device capabilities:
<div class="lazy-bg" data-bg="https://res.cloudinary.com/your-cloud-namr/image/upload/q_auto,f_webp/your-image.jpg"></div>
Streamlining Web Performance with Lazy Loaded Background Images
Lazy loading background images is a powerful technique to optimize web performance without sacrificing visual quality. This article explored techniques for implementing lazy loading for improved web performance and enjoyable user experience. By leveraging the intersection observer API, LQIP, and Cloudinary’s intelligent media optimization, you can build fast, responsive, and visually engaging websites.
Reduce your website’s load time and improve SEO with Cloudinary’s optimization features. Sign up for free today!