Clean Markup

Zero JavaScript Image Deferral with the Native HTML loading Attribute

Zero JavaScript Image Deferral with the Native HTML loading Attribute

For years, deferring offscreen images required either scroll event listeners or IntersectionObserver scripts coupled with data-src attributes. With the native HTML loading attribute standardized and shipping across Chromium-based browsers and Firefox in early 2020, browsers can now defer below-the-fold <img> and <iframe> elements automatically without a single line of JavaScript.

To prevent Cumulative Layout Shift (CLS) as deferred images load into view, always pair loading="lazy" with explicit width and height attributes so the browser can reserve the exact aspect-ratio box ahead of time. Meanwhile, keep loading="eager" (or omit the attribute) on above-the-fold hero images so your Largest Contentful Paint (LCP) is never delayed.

Example

<!-- Above the fold hero image: load immediately -->
<img
  src="/img/hero-banner.webp"
  alt="Featured mountain landscape"
  width="1200"
  height="630"
  loading="eager"
/>

<!-- Below the fold gallery image: defer until near viewport -->
<img
  src="/img/gallery-item-01.webp"
  alt="Architectural detail of glass facade"
  width="667"
  height="518"
  loading="lazy"
/>

Input : Long article page containing 20 high-resolution photographs
Output : Initial page load fetches only above-the-fold assets while reserving layout space for offscreen images.

Implementation

<article class="post-content">
  <header class="post-hero">
    <!-- Never lazy-load the primary hero image -->
    <img
      class="responsive-img"
      src="/img/posts/featured-cover.webp"
      alt="Clean Markup editorial workspace"
      width="800"
      height="500"
      loading="eager"
    />
  </header>

  <section class="article-gallery">
    <figure class="gallery-card">
      <img
        class="responsive-img"
        src="/img/posts/diagram-architecture.webp"
        alt="Browser rendering pipeline diagram"
        width="667"
        height="518"
        loading="lazy"
      />
      <figcaption>Figure 1: Native lazy loading defers network requests until scroll proximity.</figcaption>
    </figure>

    <figure class="gallery-card">
      <img
        class="responsive-img"
        src="/img/posts/waterfall-benchmark.webp"
        alt="Network waterfall comparison chart"
        width="667"
        height="518"
        loading="lazy"
      />
      <figcaption>Figure 2: Reduced initial payload size on 3G connections.</figcaption>
    </figure>
  </section>
</article>
/* Allow the browser to compute intrinsic aspect ratio from width and height attributes */
.responsive-img {
  display: block;
  max-width: 100%;
  height: auto;
  border-radius: 6px;
  background-color: #f1f5f9;
}

.article-gallery {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
  gap: 24px;
  margin-top: 32px;
}
// Optional feature detection fallback for older browsers that require a lightweight polyfill
if ("loading" in HTMLImageElement.prototype) {
  console.log("Native lazy loading is supported by this browser.");
} else {
  // Dynamically load a fallback IntersectionObserver script only when needed
  const script = document.createElement("script");
  script.src = "/js/lazysizes.min.js";
  document.body.appendChild(script);
}

Main Point: Always apply loading="lazy" along with explicit width and height dimensions to below-the-fold images, and never lazy-load your primary above-the-fold hero image.

Credits

Photo by illahe

← Home