Loading
Find Non Lazy Loaded Images Outside of the Viewport

Find non Lazy Loaded Images outside of the viewport

List all images that don't have loading="lazy" or [data-src] (lazy loading via JS) and are not in the viewport when the page loads. This script will help you find candidates for lazy loading by showing:

  • Total number of images without lazy loading
  • Total file size of all images
  • Individual image details: URL, resolution (naturalWidth x naturalHeight), and file size

Snippet

// Execute it after the page has loaded without any user interaction (Scroll, click, etc)
function isInViewport(tag) {
  let rect = tag.getBoundingClientRect();
  return (
    rect.bottom >= 0 &&
    rect.right >= 0 &&
    rect.top <= (window.innerHeight || document.documentElement.clientHeight) &&
    rect.left <= (window.innerWidth || document.documentElement.clientWidth)
  );
}
 
async function getImageSize(imgElement) {
  const src = imgElement.currentSrc || imgElement.src;
 
  if (!src || src.startsWith("data:")) {
    return 0;
  }
 
  try {
    const response = await fetch(src, { method: "HEAD" });
    const contentLength = response.headers.get("content-length");
    return contentLength ? parseInt(contentLength, 10) : 0;
  } catch (error) {
    console.warn(`Could not fetch size for: ${src}`, error);
    return 0;
  }
}
 
function formatBytes(bytes) {
  if (bytes === 0) return "0 Bytes";
  const k = 1024;
  const sizes = ["Bytes", "KB", "MB", "GB"];
  const i = Math.floor(Math.log(bytes) / Math.log(k));
  return Math.round((bytes / Math.pow(k, i)) * 100) / 100 + " " + sizes[i];
}
 
async function findImgCandidatesForLazyLoading() {
  let notLazyImages = document.querySelectorAll('img:not([data-src]):not([loading="lazy"])');
  const notLazyImagesOutOfViewport = Array.from(notLazyImages).filter((tag) => !isInViewport(tag));
 
  if (notLazyImagesOutOfViewport.length === 0) {
    console.log(
      `%c Good job, the site has all the images out of the viewport with lazyloading.`,
      "background: #222; color: lightgreen; padding: 0.5ch",
    );
    return;
  }
 
  console.log(
    `%c ⚠️ Found ${notLazyImagesOutOfViewport.length} images without lazy loading`,
    "background: #222; color: lightcoral; padding: 0.5ch; margin-top: 1em; font-weight: bold",
  );
 
  // Collect information from images
  const imagePromises = notLazyImagesOutOfViewport.map(async (img) => {
    const src = img.currentSrc || img.src;
    const size = await getImageSize(img);
    const width = img.naturalWidth;
    const height = img.naturalHeight;
    return { element: img, src, size, width, height };
  });
 
  const imagesData = await Promise.all(imagePromises);
  const totalSize = imagesData.reduce((sum, img) => sum + img.size, 0);
 
  // Mostrar resumen
  console.log(
    `%c 📊 Summary:`,
    "background: #222; color: #58a6ff; padding: 0.5ch; margin-top: 1em; font-weight: bold",
  );
  console.log(`   • Total images: ${notLazyImagesOutOfViewport.length}`);
  console.log(`   • Total size: ${formatBytes(totalSize)}`);
 
  // Display list of URLs with sizes and resolution
  console.log(
    `%c 📋 Image URLs:`,
    "background: #222; color: #58a6ff; padding: 0.5ch; margin-top: 1em; font-weight: bold",
  );
 
  imagesData.forEach((img, index) => {
    const sizeInfo = img.size > 0 ? `${formatBytes(img.size)}` : "size unknown";
    const resolution = `${img.width}x${img.height}`;
    console.log(`   ${index + 1}. ${img.src} (${resolution}, ${sizeInfo})`);
  });
 
  // Display items in console (for inspection)
  console.log(
    `%c 🔍 Image elements (click or hover to inspect):`,
    "background: #222; color: #58a6ff; padding: 0.5ch; margin-top: 1em; font-weight: bold",
  );
  notLazyImagesOutOfViewport.forEach((img) => console.log(img));
}
 
findImgCandidatesForLazyLoading();