Skip to main content

JavaScript Lazy Loading

Introduction

Have you ever visited a website that took forever to load? Chances are it was loading all of its resources at once, regardless of whether they were immediately needed. This is where lazy loading comes in. Lazy loading is a design pattern that defers the loading of non-critical resources until they are actually needed, which can significantly improve your website's initial load time and performance.

In this guide, we'll explore how JavaScript lazy loading works, why it's important, and how you can implement it in your web applications to create faster, more efficient websites.

Why Lazy Loading Matters

Before diving into implementation, let's understand the benefits:

  • Faster initial page load: Only load what's needed immediately
  • Reduced resource usage: Conserve bandwidth for users
  • Better user experience: Users see content faster
  • Improved performance metrics: Better Core Web Vitals scores
  • Reduced server load: Fewer simultaneous resource requests

Basic Concepts

What Can Be Lazy Loaded?

Almost any resource can be lazy loaded, including:

  • Images
  • Videos
  • JavaScript files
  • CSS files
  • Web fonts
  • HTML content (via AJAX)

When to Lazy Load

You should consider lazy loading resources when:

  • They are below the fold (not visible in the initial viewport)
  • They are not critical for the initial rendering
  • They are heavy resources (large images, videos, etc.)

Implementing Lazy Loading for Images

Using the loading Attribute (Native Approach)

Modern browsers now support native lazy loading for images and iframes using the loading="lazy" attribute:

html
<img src="image.jpg" loading="lazy" alt="Lazy loaded image" width="800" height="600" />

This is the simplest approach, requiring no JavaScript, but it doesn't work in older browsers.

Using Intersection Observer API

For more control and better browser support, we can use the Intersection Observer API:

javascript
document.addEventListener("DOMContentLoaded", function() {
const lazyImages = document.querySelectorAll('img[data-src]');

const imageObserver = new IntersectionObserver((entries, observer) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
const img = entry.target;
img.src = img.dataset.src;
img.removeAttribute('data-src');
observer.unobserve(img);
console.log('Image loaded:', img.src);
}
});
});

lazyImages.forEach(img => {
imageObserver.observe(img);
});
});

Then in your HTML:

html
<img data-src="image1.jpg" src="placeholder.jpg" alt="Lazy loaded image" />
<img data-src="image2.jpg" src="placeholder.jpg" alt="Lazy loaded image" />

Before and After

Without lazy loading:

  • All 10 images (5MB total) would download immediately
  • Page load time: 3.5 seconds

With lazy loading:

  • Initial load: 2 visible images (1MB)
  • Page load time: 1.2 seconds
  • Other images load as user scrolls

Lazy Loading JavaScript

Dynamic Importing

ES2020 introduced dynamic imports that let you load JavaScript modules when needed:

javascript
// Instead of importing at the top level
// import { heavyFunction } from './heavyModule.js';

document.getElementById('specialButton').addEventListener('click', async () => {
// Import the module only when the button is clicked
const { heavyFunction } = await import('./heavyModule.js');

// Now use the function
const result = heavyFunction();
console.log('The heavy calculation result:', result);
});

Deferred Loading with setTimeout

For non-critical scripts, we can defer loading using setTimeout:

javascript
// At the bottom of your page
window.addEventListener('load', () => {
setTimeout(() => {
const analyticsScript = document.createElement('script');
analyticsScript.src = 'https://analytics-provider.com/analytics.js';
document.body.appendChild(analyticsScript);
console.log('Analytics script loaded after page load');
}, 2000); // Load after 2 seconds
});

Lazy Loading Components in Web Frameworks

React Example

Using React's React.lazy() and Suspense:

jsx
import React, { Suspense, lazy } from 'react';

// Instead of: import HeavyComponent from './HeavyComponent';
const HeavyComponent = lazy(() => import('./HeavyComponent'));

function App() {
return (
<div>
<h1>My App</h1>
<p>This content loads immediately</p>

<Suspense fallback={<div>Loading...</div>}>
<HeavyComponent />
</Suspense>
</div>
);
}

Let's create a simple image gallery that lazy loads images as the user scrolls:

html
<div id="image-gallery" class="gallery">
<!-- Images will be loaded here -->
</div>
javascript
document.addEventListener("DOMContentLoaded", function() {
const gallery = document.getElementById('image-gallery');
const imageUrls = [
'https://source.unsplash.com/random/800x600?1',
'https://source.unsplash.com/random/800x600?2',
'https://source.unsplash.com/random/800x600?3',
'https://source.unsplash.com/random/800x600?4',
'https://source.unsplash.com/random/800x600?5',
'https://source.unsplash.com/random/800x600?6',
'https://source.unsplash.com/random/800x600?7',
'https://source.unsplash.com/random/800x600?8',
'https://source.unsplash.com/random/800x600?9',
'https://source.unsplash.com/random/800x600?10'
];

// Create image containers with placeholders
imageUrls.forEach(url => {
const imgContainer = document.createElement('div');
imgContainer.className = 'image-container';

const img = document.createElement('img');
img.dataset.src = url; // The real image URL
img.src = 'placeholder.jpg'; // Low-res placeholder
img.alt = 'Gallery image';
img.className = 'lazy-image';

imgContainer.appendChild(img);
gallery.appendChild(imgContainer);
});

// Set up intersection observer
if ('IntersectionObserver' in window) {
const lazyImageObserver = new IntersectionObserver((entries, observer) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
const lazyImage = entry.target;
lazyImage.src = lazyImage.dataset.src;
lazyImage.classList.remove('lazy-image');
lazyImage.classList.add('loaded');
lazyImageObserver.unobserve(lazyImage);
console.log('Loaded image:', lazyImage.src);
}
});
});

const lazyImages = document.querySelectorAll('.lazy-image');
lazyImages.forEach(image => {
lazyImageObserver.observe(image);
});
} else {
// Fallback for browsers that don't support IntersectionObserver
// This would load all images at once
const lazyImages = document.querySelectorAll('.lazy-image');
lazyImages.forEach(img => {
img.src = img.dataset.src;
img.classList.remove('lazy-image');
img.classList.add('loaded');
});
console.log('Intersection Observer not supported, loaded all images at once');
}
});

Add some CSS to make it look nice:

css
.gallery {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
grid-gap: 15px;
}

.image-container {
height: 200px;
background-color: #f0f0f0;
overflow: hidden;
}

.lazy-image {
width: 100%;
height: 100%;
object-fit: cover;
opacity: 0.2;
transition: opacity 0.3s;
}

.loaded {
opacity: 1;
}

Best Practices for Lazy Loading

  1. Always provide fallbacks: For browsers that don't support your lazy loading technique
  2. Use placeholder content: Show low-resolution images or content skeletons while loading
  3. Consider the fold: Immediately load what's visible in the viewport
  4. Add buffer zone: Start loading slightly before elements enter the viewport for a smoother experience
  5. Measure performance impact: Use tools like Lighthouse to verify improvements
  6. Be mindful of layout shifts: Set proper dimensions for images to avoid layout shifts when they load
  7. Use native solutions when possible: They're typically more optimized

Common Pitfalls to Avoid

  • Over-lazy loading: Don't lazy load critical content that users need immediately
  • No loading indicators: Always show users that content is loading
  • Browser compatibility issues: Test across different browsers
  • SEO implications: Ensure crawlers can access your content

Summary

Lazy loading is a powerful technique that can significantly improve your website's performance by deferring the loading of non-critical resources until they're needed. We've covered:

  • The fundamentals and benefits of lazy loading
  • How to implement lazy loading for images using native attributes and the Intersection Observer API
  • Methods for lazy loading JavaScript modules and third-party scripts
  • Framework-specific approaches for component lazy loading
  • A practical image gallery implementation

By implementing these techniques, you'll create faster, more efficient websites that provide a better user experience and improved performance metrics.

Additional Resources

Exercises

  1. Implement lazy loading for a series of images on a webpage
  2. Create a "load more" button that uses lazy loading to fetch additional content
  3. Convert an existing website to use lazy loading and measure the performance improvements
  4. Build a tabbed interface where each tab's content is lazy loaded when activated
  5. Implement lazy loading for a third-party widget like a social media feed or comments section

Happy coding!



If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)