Optimagio

Lazy Loading Images Without Breaking CLS: A Developer's Guide

Learn how to implement lazy loading techniques that prevent cumulative layout shift (CLS) by properly setting dimensions and using modern browser-native lazy loading with fallbacks.

Optimagio Team 5 min read
Lazy Loading Images Without Breaking CLS: A Developer's Guide

The CLS Problem with Lazy Loading

Lazy loading images is a powerful performance optimization technique that delays loading off-screen images until they're needed. However, when implemented incorrectly, it can severely impact Cumulative Layout Shift (CLS) - one of Google's Core Web Vitals metrics. CLS measures visual stability by quantifying how much visible content shifts during page load.

The primary cause of CLS with lazy loading is simple: when images load without reserved space, they push existing content around. This creates a jarring user experience where buttons move, text jumps, and users accidentally click wrong elements. The solution isn't to avoid lazy loading, but to implement it correctly with proper dimension management.

Setting Proper Image Dimensions

The foundation of CLS-free lazy loading is proper dimension management. Every image must have explicit width and height attributes that match its intrinsic dimensions. This allows browsers to reserve the correct space before the image loads, preventing layout shifts.

  1. 1Add width and height attributesAlways include width and height attributes that match the image's actual dimensions. Use the intrinsic size, not the styled size.
  2. 2Use CSS for responsive sizingSet max-width: 100% and height: auto in CSS to make images responsive while maintaining aspect ratio.
  3. 3Implement aspect ratio boxesFor responsive designs, use CSS aspect-ratio property or padding-top technique to maintain consistent proportions across breakpoints.
<img src="image.jpg" 
     width="800" 
     height="600" 
     alt="Description"
     style="max-width: 100%; height: auto;">

Native Lazy Loading Implementation

Modern browsers support native lazy loading through the loading='lazy' attribute. This is the preferred method as it requires no JavaScript, has excellent performance characteristics, and handles CLS prevention automatically when proper dimensions are set.

<img src="image.jpg" 
     width="800" 
     height="600" 
     loading="lazy"
     alt="Lazy loaded image">
Native Lazy Loading Pros
  • No JavaScript requiredWorks without any client-side code
  • Better performanceBrowser-native implementation is more efficient
  • Automatic CLS preventionRespects dimension attributes properly
Considerations
  • Browser supportNot supported in older browsers (needs fallback)
  • Less controlLimited customization compared to JavaScript solutions
  • Loading thresholdBrowser determines when to load based on its own heuristics

JavaScript Fallback Implementation

For browsers that don't support native lazy loading, you'll need a JavaScript fallback. The Intersection Observer API is the modern standard for this, providing efficient detection of when elements enter the viewport.

  1. 1Use data-src attributeStore the real image URL in data-src instead of src to prevent immediate loading
  2. 2Create Intersection ObserverSet up an observer to watch for images entering the viewport
  3. 3Swap attributes on intersectWhen an image becomes visible, move data-src to src to trigger loading
  4. 4Add loading class for stylingUse CSS classes to show loading states and handle errors gracefully
// Simple Intersection Observer implementation
const observer = new IntersectionObserver((entries) => {
  entries.forEach(entry => {
    if (entry.isIntersecting) {
      const img = entry.target;
      img.src = img.dataset.src;
      observer.unobserve(img);
    }
  });
}, { rootMargin: '200px 0px' });

document.querySelectorAll('img[data-src]').forEach(img => {
  observer.observe(img);
});

Testing and Debugging CLS Issues

Proper testing is crucial to ensure your lazy loading implementation doesn't harm CLS. Browser developer tools provide excellent visibility into layout shifts, allowing you to identify and fix issues before they impact real users.

  1. 1Use Chrome DevTools Layout Shift RegionsEnable Layout Shift Regions in Performance panel to visually see shifts during recording
  2. 2Check Web Vitals extensionInstall the Web Vitals extension for real-time CLS monitoring during development
  3. 3Test with throttled connectionSimulate slow networks to see how images load progressively and affect layout
  4. 4Verify across viewport sizesTest responsive behavior at different breakpoints to catch aspect ratio issues

Advanced Techniques and Best Practices

Beyond the basics, several advanced techniques can further optimize your lazy loading implementation. These include combining lazy loading with responsive images, implementing blur-up placeholders, and using modern CSS features for smoother loading experiences.

Responsive Images with srcsetCombine lazy loading with srcset and sizes attributes to serve appropriately sized images for each device, reducing bandwidth usage while maintaining quality.
Blur-Up PlaceholdersUse low-quality image placeholders (LQIP) or inline SVG placeholders that blur into the final image, providing better perceived performance.
CSS content-visibilityFor complex layouts, use content-visibility: auto on containers to defer rendering of off-screen content until needed.

Automate image optimization with Optimagio

Doing this by hand for every image does not scale. Optimagio optimizes and converts your images (WebP and AVIF) automatically across your API, web app, and CMS — so every page ships the smallest possible files without manual work. See plans and pricing →

FAQ

Frequently asked questions

What causes CLS with lazy loaded images?

CLS occurs when images load without reserved space, causing the page layout to shift unexpectedly. This happens when width and height attributes are missing or incorrect, forcing the browser to recalculate layout when images finally load.

Should I use native lazy loading or JavaScript?

Prefer native lazy loading (loading='lazy') for better performance and simplicity. Use JavaScript fallbacks only for older browsers that don't support the native attribute, typically with Intersection Observer API.

How do I set proper aspect ratios for responsive images?

Use CSS aspect-ratio property or padding-top technique with percentage-based values. Combine with width and height attributes to maintain consistent layout across different screen sizes.

Can lazy loading improve LCP as well as CLS?

Yes, when implemented correctly. Lazy loading non-critical images reduces resource competition, allowing critical images (like LCP candidates) to load faster while preventing layout shifts that hurt user experience.

What tools can I use to test CLS impact?

Use Chrome DevTools Layout Shift Regions, Web Vitals extension, PageSpeed Insights, and real user monitoring tools like CrUX to measure and debug cumulative layout shift issues.