Skip to content

Native loading Attribute — Using loading=lazy for Images and Iframes

DodaTech Updated 2026-06-28 5 min read

In this tutorial, you will learn about Native loading Attribute. We cover key concepts, practical examples, and best practices to help you master this topic.

The native loading attribute lets browsers defer offscreen images and iframes without JavaScript, improving Core Web Vitals with zero library dependencies.

What You'll Learn

By the end of this tutorial, you'll understand how the native loading attribute works, how to use it for images and iframes, how to prioritize critical resources with eager loading, and how to handle browsers without support.

Why It Matters

Native Lazy Loading requires zero JavaScript, zero libraries, and zero configuration. Adding a single HTML attribute defers non-critical resources and improves performance — the simplest optimization you can make.

Real-World Use

A documentation site with 50 screenshots adds loading=lazy to all images below the fold. Initial page weight drops from 1.8MB to 0.3MB. The change was a single find-and-replace in the template. No JavaScript, no libraries.

Native Loading Behavior

graph TD
    A[Browser parses
loading attribute] --> B{loading value?} B -->|eager| C[Load image
immediately] B -->|'lazy'| D{Image within
viewport?} D -->|Yes| C D -->|No| E[Image is
offscreen] E --> F[Browser tracks
scroll position] F --> G{Image approaching
viewport?} G -->|No| H[Keep deferred] G -->|Yes| I[Load image
~300-500px before viewport] I --> J[Image appears
fully loaded] style C fill:#27ae60,color:#fff style E fill:#f39c12,color:#fff style I fill:#4a90d9,color:#fff style J fill:#27ae60,color:#fff

Image Lazy Loading

<!-- Standard lazy loaded image -->
<img src="photo.jpg" alt="Description" loading="lazy" width="800" height="600">

<!-- With responsive images -->
<img src="photo-400.jpg"
     srcset="photo-400.jpg 400w, photo-800.jpg 800w, photo-1200.jpg 1200w"
     sizes="(max-width: 600px) 400px, (max-width: 1000px) 800px, 1200px"
     alt="Responsive lazy image"
     loading="lazy"
     width="1200" height="800">

<!-- Critical hero image — never lazy -->
<img src="hero.jpg"
     alt="Hero banner"
     loading="eager"
     fetchpriority="high"
     width="1600" height="900">

<!-- Progressive enhancement with data-src fallback -->
<img src="placeholder.jpg"
     data-src="actual-photo.jpg"
     alt="Progressive enhancement"
     loading="lazy"
     width="800" height="600"
     onerror="this.src=this.dataset.src">

Iframe Lazy Loading

<!-- Lazy loaded YouTube embed -->
<iframe src="https://www.youtube.com/embed/dQw4w9WgXcQ"
        title="YouTube video"
        loading="lazy"
        width="560" height="315"
        allowfullscreen>
</iframe>

<!-- Lazy loaded analytics widget -->
<iframe src="https://widget.analytics.com/dashboard"
        loading="lazy"
        width="100%" height="400"
        title="Analytics Dashboard">
</iframe>

<!-- Lazy loaded social media embed -->
<iframe src="https://platform.twitter.com/widgets/tweet.html"
        loading="lazy"
        width="550" height="300"
        title="Tweet embed">
</iframe>

<!-- Critical iframe (above fold) — load eagerly -->
<iframe src="https://maps.google.com/maps?..."
        title="Store location map"
        loading="eager"
        width="100%" height="400">
</iframe>

Browser Detection and Fallback

// Fallback for browsers without native lazy loading
document.addEventListener('DOMContentLoaded', function () {
    // Check if native lazy loading is supported
    const supportsLazy = 'loading' in HTMLImageElement.prototype;

    if (!supportsLazy) {
        console.log('Native lazy loading not supported, using Intersection Observer fallback');

        // Intersection Observer fallback
        const lazyImages = document.querySelectorAll('img[loading="lazy"]');
        const lazyIframes = document.querySelectorAll('iframe[loading="lazy"]');

        const observer = new IntersectionObserver((entries) => {
            entries.forEach(entry => {
                if (entry.isIntersecting) {
                    const element = entry.target;

                    if (element.tagName === 'IMG') {
                        element.src = element.src || element.dataset.src;
                    } else if (element.tagName === 'IFRAME') {
                        element.src = element.src || element.dataset.src;
                    }

                    element.removeAttribute('loading');
                    observer.unobserve(element);
                }
            });
        }, {
            rootMargin: '200px 0px',
            threshold: 0.01
        });

        lazyImages.forEach(img => observer.observe(img));
        lazyIframes.forEach(iframe => observer.observe(iframe));
    }
});

Performance Monitoring

// Monitor lazy loading effectiveness
class LazyLoadingMonitor {
    constructor() {
        this.lazyElements = new Map();
        this.metrics = {
            total: 0,
            loaded: 0,
            avoidedBytes: 0
        };
    }

    track(element) {
        const id = `lazy-${this.metrics.total++}`;
        element.dataset.lazyId = id;

        this.lazyElements.set(id, {
            element,
            loaded: false,
            size: null
        });

        // Estimate size from element attributes
        const width = parseInt(element.getAttribute('width')) || 0;
        const height = parseInt(element.getAttribute('height')) || 0;
        const estimatedBytes = (width * height * 3) / 10; // rough JPEG estimate

        this.metrics.avoidedBytes += estimatedBytes;
    }

    markLoaded(element) {
        const id = element.dataset.lazyId;
        if (id && this.lazyElements.has(id)) {
            const entry = this.lazyElements.get(id);
            entry.loaded = true;
            this.metrics.loaded++;
        }
    }

    report() {
        const percentAvoided = ((this.metrics.avoidedBytes / (1024 * 1024))).toFixed(1);
        console.log(`Lazy Loading Report:`);
        console.log(`  Total lazy elements: ${this.metrics.total}`);
        console.log(`  Loaded so far: ${this.metrics.loaded}`);
        console.log(`  Estimated bandwidth avoided: ${percentAvoided} MB`);
    }
}

const monitor = new LazyLoadingMonitor();

Common Mistakes

  1. Using loading=lazy on the LCP image. The largest contentful paint image should load eagerly. Lazy loading it delays LCP and hurts performance scores.
  2. Forgetting dimensions on lazy images. Images without width/height cause Cumulative Layout Shift when they load. Always set dimensions.
  3. Lazy loading images that are always visible. Navigation logos, user avatars, and sidebar icons are always visible. Don't lazy load them.
  4. Using loading=lazy on print stylesheets. Print versions need all images. Use @media print to override lazy loading.
  5. Not testing on slow networks. Native lazy loading works, but on 3G, images may load too late. Test with network throttling.

Practice Questions

  1. What values does the loading attribute accept?
  2. How does the browser decide when to load a lazy image?
  3. Why should the LCP image never use loading=lazy?
  4. How do you provide a fallback for browsers without native lazy loading?
  5. What is the difference between loading and fetchpriority attributes?

Challenge: Create a comparison page that benchmarks native lazy loading vs eager loading: 30 images loaded with each approach, measure page weight, LCP, and load time using Performance API, and present results in a chart.

FAQ

Does loading=lazy block search engine crawling?

Googlebot respects loading=lazy and will load images for indexing. Other search engines may not. For critical images, consider using eager loading.

Can I use loading=lazy on background images?

No. CSS background images don't support the loading attribute. Use Intersection Observer for background image lazy loading.

What is the browser's threshold for loading lazy images?

Browsers typically load images when they're 300-500px from the viewport. This preloads them before the user scrolls into view.

Does loading=lazy work with JavaScript-generated images?

Yes. Images added dynamically still respect the loading attribute. Set loading=lazy when creating image elements.

How do I lazy load images in a CSS background?

Use Intersection Observer. When the element enters the viewport, add a CSS class that sets the background-image property.

Mini Project

Build a page that demonstrates native lazy loading with monitoring: 25 images with loading=lazy, 5 critical images with loading=eager, a real-time counter showing loaded/deferred images, a bandwidth savings estimator, and a benchmark comparison table.

What's Next

You've mastered native lazy loading. Now learn about Intersection Observer for custom lazy loading implementations.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro