Skip to content

Lazy Loading

The loading attribute provides native lazy loading for images and iframes without JavaScript libraries.

<img data-src="image.jpg" class="lazy" alt="Lazy image">
<script>
const lazyImages = document.querySelectorAll('.lazy');
const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
const img = entry.target;
img.src = img.dataset.src;
img.classList.remove('lazy');
observer.unobserve(img);
}
});
});
lazyImages.forEach(img => observer.observe(img));
</script>

Or with a library:

<script src="lazysizes.min.js"></script>
<img data-src="image.jpg" class="lazyload" alt="Image">

Problems:

  • Requires JavaScript to function
  • Additional library adds weight
  • Flash of empty space before load
  • Must manage placeholder images
<img src="image.jpg" alt="Description" loading="lazy">

That’s it. The browser handles everything.

Benefits:

  • Zero JavaScript required
  • No external dependencies
  • Browser optimizes load timing
  • Works automatically with srcset
ValueBehavior
lazyDefer loading until near viewport
eagerLoad immediately (default behavior)
<!-- Lazy load offscreen images -->
<img src="photo.jpg" alt="Photo" loading="lazy">
<!-- Eager load above-the-fold images -->
<img src="hero.jpg" alt="Hero" loading="eager">
<!-- Works with srcset -->
<img
src="photo-800.jpg"
srcset="photo-400.jpg 400w, photo-800.jpg 800w, photo-1200.jpg 1200w"
sizes="(max-width: 600px) 100vw, 50vw"
alt="Photo"
loading="lazy"
>
<!-- Lazy load embedded content -->
<iframe
src="https://www.youtube.com/embed/VIDEO_ID"
loading="lazy"
title="Video title"
></iframe>
<!-- Lazy load maps -->
<iframe
src="https://maps.google.com/..."
loading="lazy"
title="Location map"
></iframe>
<!-- Hero image - load immediately -->
<img src="hero.jpg" alt="Hero" loading="eager" fetchpriority="high">
<!-- Below fold - lazy load -->
<img src="gallery-1.jpg" alt="Gallery" loading="lazy">
<img src="gallery-2.jpg" alt="Gallery" loading="lazy">

Prevent layout shift by specifying dimensions:

<img
src="photo.jpg"
alt="Photo"
loading="lazy"
width="800"
height="600"
>

Or use CSS aspect-ratio:

img {
aspect-ratio: 4 / 3;
width: 100%;
height: auto;
}
<img
src="photo.jpg"
alt="Photo"
loading="lazy"
decoding="async"
>
  • decoding="async" - Decode image off main thread
  • decoding="sync" - Decode immediately (default)
  • decoding="auto" - Browser decides

Browsers start loading lazy images before they enter the viewport. The exact distance varies by browser and connection speed. Typically:

  • Fast connection: ~1250px before viewport
  • Slow connection: ~2500px before viewport

You cannot customize this threshold with native lazy loading.

if ('loading' in HTMLImageElement.prototype) {
// Native lazy loading supported
} else {
// Fallback to Intersection Observer or library
}

For browsers without support (rare now):

<img
src="placeholder.jpg"
data-src="actual.jpg"
loading="lazy"
class="lazy"
alt="Description"
>
<script>
if (!('loading' in HTMLImageElement.prototype)) {
// Load lazysizes or use IntersectionObserver
const script = document.createElement('script');
script.src = 'lazysizes.min.js';
document.body.appendChild(script);
}
</script>

Supported in all modern browsers. See caniuse.com/loading-lazy-attr.

  • Lazy loading doesn’t affect accessibility
  • Screen readers announce images normally
  • Alt text works the same way
  • No ARIA attributes needed
<!-- Optimal image loading -->
<img
src="photo.jpg"
alt="Description"
loading="lazy"
decoding="async"
width="800"
height="600"
>

For critical above-fold images:

<img
src="hero.jpg"
alt="Hero"
loading="eager"
decoding="async"
fetchpriority="high"
>

Use loading="lazy" for:

  • Images below the fold
  • Image galleries
  • Long pages with many images
  • Embedded iframes (videos, maps)

Use loading="eager" (or omit) for:

  • Hero images
  • Above-the-fold content
  • Critical LCP (Largest Contentful Paint) images
  • Small, critical icons

Consider JavaScript alternatives when:

  • You need custom loading thresholds
  • You want loading animations/placeholders
  • Supporting very old browsers