Loading
Find above the Fold Lazy Loaded Images

Find Above The Fold Lazy Loaded Images

Detect images with lazy loading that are incorrectly placed above the fold (in the viewport). Lazy loading above-the-fold images is a common performance anti-pattern that can significantly harm your Largest Contentful Paint (LCP) score.

The snippet identifies images with loading="lazy" or [data-src] in the viewport, detects the LCP candidate, and provides detailed performance metrics.

Looking for images that should have lazy loading? Use the Find non Lazy Loaded Images outside of the viewport snippet instead.

Snippet

// Detect lazy loaded images above the fold
// https://webperf-snippets.nucliweb.net
 
(function() {
  function getSelector(node) {
    let sel = "";
    try {
      while (node && node.nodeType !== 9) {
        const el = node;
        const name = el.nodeName.toLowerCase();
        const part = el.id
          ? "#" + el.id
          : name +
            (el.classList && el.classList.value && el.classList.value.trim()
              ? "." + el.classList.value.trim().split(/\s+/).slice(0, 2).join(".")
              : "");
        if (sel.length + part.length > 80) return sel || part;
        sel = sel ? part + ">" + sel : part;
        if (el.id) break;
        node = el.parentNode;
      }
    } catch (err) {}
    return sel;
  }
 
  function isInViewport(element) {
    const rect = element.getBoundingClientRect();
    return (
      rect.top < window.innerHeight &&
      rect.bottom > 0 &&
      rect.left < window.innerWidth &&
      rect.right > 0 &&
      rect.width > 0 &&
      rect.height > 0
    );
  }
 
  function getDistanceFromViewportEdge(element) {
    const rect = element.getBoundingClientRect();
    const distances = [
      rect.top,
      window.innerHeight - rect.bottom,
      rect.left,
      window.innerWidth - rect.right
    ];
    return Math.min(...distances.map(d => Math.abs(d)));
  }
 
  function getFileSize(src) {
    if (!src) return null;
    try {
      const entries = performance.getEntriesByType("resource");
      const entry = entries.find(e => e.name === src || e.name.includes(src.split("/").pop()));
      if (entry && entry.transferSize) {
        return (entry.transferSize / 1024).toFixed(1) + " KB";
      }
    } catch (err) {}
    return null;
  }
 
  function getLazyType(element) {
    const hasLoadingLazy = element.getAttribute("loading") === "lazy";
    const hasDataSrc = element.hasAttribute("data-src") || element.hasAttribute("data-lazy");
 
    if (hasLoadingLazy && hasDataSrc) return "both";
    if (hasLoadingLazy) return "loading-lazy";
    if (hasDataSrc) return "data-src";
    return null;
  }
 
  function getElementInfo(img) {
    const rect = img.getBoundingClientRect();
    const area = rect.width * rect.height;
 
    return {
      selector: getSelector(img),
      lazyType: getLazyType(img),
      dimensions: `${Math.round(rect.width)}×${Math.round(rect.height)}`,
      position: {
        top: Math.round(rect.top + window.scrollY),
        left: Math.round(rect.left + window.scrollX)
      },
      distanceFromEdge: Math.round(getDistanceFromViewportEdge(img)) + "px",
      src: img.currentSrc || img.src || img.getAttribute("data-src") || "",
      srcset: img.srcset || null,
      sizes: img.sizes || null,
      alt: img.alt || "(no alt)",
      fetchPriority: img.fetchPriority || "auto",
      fileSize: getFileSize(img.currentSrc || img.src),
      area: area,
      element: img
    };
  }
 
  const results = {
    lazyImages: [],
    lcpCandidate: null,
    nodeArray: [],
    summary: {
      total: 0,
      withLoadingLazy: 0,
      withDataSrc: 0,
      lcpAffected: false
    }
  };
 
  // Find all images with lazy loading
  const lazySelectors = '[loading="lazy"], [data-src], [data-lazy]';
  const lazyElements = document.querySelectorAll(`img${lazySelectors}, picture source${lazySelectors}`);
 
  // Also check all images in viewport to find LCP candidate
  const allViewportImages = Array.from(document.querySelectorAll("img"))
    .filter(img => isInViewport(img) && img.getBoundingClientRect().width > 0);
 
  // Find LCP candidate (largest visible image)
  let lcpCandidate = null;
  let maxArea = 0;
 
  allViewportImages.forEach(img => {
    const rect = img.getBoundingClientRect();
    const area = rect.width * rect.height;
    if (area > maxArea) {
      maxArea = area;
      lcpCandidate = img;
    }
  });
 
  // Process lazy loaded images in viewport
  lazyElements.forEach(element => {
    const img = element.tagName === "SOURCE"
      ? element.closest("picture")?.querySelector("img")
      : element;
 
    if (!img || !isInViewport(img)) return;
 
    const info = getElementInfo(img);
    info.isLcpCandidate = img === lcpCandidate;
 
    results.lazyImages.push(info);
    results.nodeArray.push(img);
 
    // Update summary
    results.summary.total++;
    if (info.lazyType === "loading-lazy" || info.lazyType === "both") {
      results.summary.withLoadingLazy++;
    }
    if (info.lazyType === "data-src" || info.lazyType === "both") {
      results.summary.withDataSrc++;
    }
    if (info.isLcpCandidate) {
      results.summary.lcpAffected = true;
      results.lcpCandidate = info;
    }
  });
 
  // Display results
  console.group("🔍 Lazy Loading Detection - Above The Fold");
 
  if (results.lazyImages.length === 0) {
    console.log(
      "%c✅ Good job! No lazily loaded images found in the viewport.",
      "background: #222; color: #22c55e; padding: 0.5ch 1ch; font-weight: bold;"
    );
  } else {
    // Summary
    console.log(
      `%c⚠️ Found ${results.summary.total} lazily loaded image(s) above the fold`,
      "color: #f59e0b; font-weight: bold; font-size: 14px;"
    );
    console.log("");
 
    // LCP Warning
    if (results.summary.lcpAffected) {
      console.group("🚨 Critical: LCP Candidate Has Lazy Loading");
      console.log(
        "%cThe largest image in your viewport has lazy loading enabled!",
        "color: #ef4444; font-weight: bold;"
      );
      console.log("This can significantly delay your Largest Contentful Paint.");
      console.log("");
      console.log("LCP Candidate:", results.lcpCandidate.selector);
      console.log("Dimensions:", results.lcpCandidate.dimensions);
      console.log("File size:", results.lcpCandidate.fileSize || "unknown");
      console.log("");
      console.log("%cFix: Remove loading=\"lazy\" and add fetchpriority=\"high\"", "color: #22c55e;");
      console.groupEnd();
      console.log("");
    }
 
    // Table of all problematic images
    console.group("📋 Lazy Loaded Images in Viewport");
    const tableData = results.lazyImages.map(({ element, area, position, ...rest }) => ({
      ...rest,
      top: position.top + "px",
      isLcpCandidate: rest.isLcpCandidate ? "⚠️ YES" : "no"
    }));
    console.table(tableData);
    console.groupEnd();
 
    // Elements for inspection
    console.group("🔎 Elements for inspection");
    console.log("Click to expand and inspect in Elements panel:");
    results.nodeArray.forEach((node, i) => {
      const marker = node === lcpCandidate ? " 🚨 LCP" : "";
      console.log(`${i + 1}.${marker}`, node);
    });
    console.groupEnd();
 
    // Quick fix suggestion
    console.log("");
    console.group("📝 Suggested Fix");
    console.log("For above-the-fold images, use:");
    console.log("");
    console.log(
      '%c<img src="hero.jpg" fetchpriority="high" alt="...">',
      "font-family: monospace; background: #1e1e1e; color: #9cdcfe; padding: 8px; border-radius: 4px;"
    );
    console.log("");
    console.log("Remove: loading=\"lazy\", data-src, data-lazy");
    console.log("Add: fetchpriority=\"high\" for LCP candidate");
    console.groupEnd();
  }
 
  console.groupEnd();
})();

Understanding the Results

The snippet scans the viewport for images that have lazy loading but shouldn't.

Success Case

If no lazy-loaded images are found in the viewport:

✅ Good job! No lazily loaded images found in the viewport.

Issues Found

When lazy-loaded images are detected in the viewport, you'll see:

Summary: Total count of problematic images

LCP Warning (if applicable): Critical alert when the largest image has lazy loading

Image Table with details:

FieldDescription
selectorCSS selector path to the element
lazyTypeType of lazy loading: "loading-lazy", "data-src", or "both"
dimensionsImage dimensions as "width×height"
srcCurrent image source URL
fetchPriorityCurrent fetch priority ("high", "low", "auto")
fileSizeTransfer size in KB (if available via Performance API)
isLcpCandidateWhether this is the largest image (potential LCP)

Elements for inspection: Direct DOM references you can click to inspect in DevTools

Why This Matters: Impact on Core Web Vitals

Lazy loading is a powerful optimization technique, but incorrect usage can severely harm your Largest Contentful Paint (LCP) score.

The Problem:

When you add loading="lazy" to an above-the-fold image:

  1. Browser defers loading until image is "near" the viewport
  2. For above-fold images, this adds unnecessary delay
  3. The image loads later than it would have naturally
  4. LCP score increases (worse performance)

Performance Impact:

ScenarioImpact on LCP
LCP image with loading="lazy"+200-500ms delay
Hero image with loading="lazy"+100-300ms delay
Above-fold gallery with lazy loading+150-400ms cumulative
Correctly lazy-loaded below-fold images-100-500ms improvement

The Solution:

Image LocationRecommendation
LCP candidate (largest above-fold image)fetchpriority="high", NO lazy loading
Other above-fold imagesNO lazy loading, default priority
Below-fold imagesloading="lazy"
Images in hidden containersloading="lazy"

Best Practices

When to Use loading="lazy"

Good candidates:

  • Images below the fold
  • Images in carousels (non-active slides)
  • Images in tabs (non-active panels)
  • Images in accordions (collapsed sections)
  • Images in modals
  • Footer images
  • Blog post images after the first paragraph

Never lazy load:

  • The LCP candidate (largest above-fold image)
  • Hero images
  • Logo images in viewport
  • Critical product images
  • Any image visible on initial page load

Prioritizing Critical Images

For your LCP candidate, use fetchpriority="high":

<!-- LCP candidate - highest priority, no lazy loading -->
<img
  src="hero-image.jpg"
  fetchpriority="high"
  alt="Hero image description"
>
 
<!-- Other above-fold images - no lazy loading -->
<img src="product.jpg" alt="Product image">
 
<!-- Below-fold images - lazy loading -->
<img
  src="related-product.jpg"
  loading="lazy"
  alt="Related product"
>

Combining with Responsive Images

<!-- LCP candidate with responsive images -->
<img
  src="hero-800.jpg"
  srcset="hero-400.jpg 400w, hero-800.jpg 800w, hero-1200.jpg 1200w"
  sizes="(max-width: 600px) 400px, (max-width: 1000px) 800px, 1200px"
  fetchpriority="high"
  alt="Hero image"
>
 
<!-- Below-fold responsive image with lazy loading -->
<img
  src="content-800.jpg"
  srcset="content-400.jpg 400w, content-800.jpg 800w"
  sizes="(max-width: 600px) 400px, 800px"
  loading="lazy"
  alt="Content image"
>

Implementation Examples

Fixing an LCP image:

<!-- ❌ Before: Lazy loaded LCP candidate -->
<img
  src="hero.jpg"
  loading="lazy"
  alt="Hero"
>
 
<!-- ✅ After: Prioritized LCP candidate -->
<img
  src="hero.jpg"
  fetchpriority="high"
  alt="Hero"
>

Picture element with proper loading:

<!-- Above-fold picture element -->
<picture>
  <source srcset="hero.avif" type="image/avif">
  <source srcset="hero.webp" type="image/webp">
  <img
    src="hero.jpg"
    fetchpriority="high"
    alt="Hero image"
  >
</picture>
 
<!-- Below-fold picture element -->
<picture>
  <source srcset="content.avif" type="image/avif">
  <source srcset="content.webp" type="image/webp">
  <img
    src="content.jpg"
    loading="lazy"
    alt="Content image"
  >
</picture>

JavaScript lazy loading libraries:

If using a JS library (lazysizes, vanilla-lazyload, etc.), configure it to exclude above-fold images:

// Example: Exclude images with data-priority attribute
document.querySelectorAll('img[data-src]').forEach(img => {
  const rect = img.getBoundingClientRect();
  if (rect.top < window.innerHeight) {
    // Above fold - load immediately
    img.src = img.dataset.src;
    img.removeAttribute('data-src');
  }
});

Browser Support

Native lazy loading (loading="lazy") is supported in:

  • Chrome 77+
  • Edge 79+
  • Firefox 75+
  • Safari 15.4+
  • Opera 64+

The fetchpriority attribute is supported in:

  • Chrome 101+
  • Edge 101+
  • Opera 87+

Not yet in Firefox and Safari, but they ignore the attribute safely.

Further Reading

Note

Run this snippet after the page has fully loaded without scrolling for the most accurate viewport detection. The Performance API is used to retrieve file sizes when available.