Find non Lazy Loaded Images outside of the viewport
Identifies images that are loaded eagerly but not visible in the initial viewport, representing wasted bandwidth and parsing time that delays page interactivity. The snippet analyzes all <img> elements to find optimization opportunities for lazy loading.
Images outside the viewport that load immediately:
- Waste bandwidth by downloading resources users may never see
- Block the main thread during decoding and rendering
- Delay LCP by competing for network and CPU resources
- Increase memory usage unnecessarily
This script detects images without loading="lazy" or [data-src] attributes that are positioned outside the initial viewport, providing actionable data to optimize loading performance.
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)
);
}
function getImageSize(imgElement) {
const src = imgElement.currentSrc || imgElement.src;
if (!src || src.startsWith("data:")) {
return 0;
}
// Try to get size from Performance API (more reliable)
const perfEntries = performance.getEntriesByType("resource");
const imgEntry = perfEntries.find((entry) => entry.name === src);
if (imgEntry) {
// transferSize includes headers, encodedBodySize is the actual compressed size
// decodedBodySize is the uncompressed size
return imgEntry.transferSize || imgEntry.encodedBodySize || 0;
}
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];
}
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 imagesData = notLazyImagesOutOfViewport.map((img) => {
const src = img.currentSrc || img.src;
const size = getImageSize(img);
const width = img.naturalWidth;
const height = img.naturalHeight;
return { element: img, src, size, width, height };
});
const totalSize = imagesData.reduce((sum, img) => sum + img.size, 0);
const imagesWithSize = imagesData.filter((img) => img.size > 0).length;
const imagesWithoutSize = imagesData.length - imagesWithSize;
// 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)}`);
if (imagesWithoutSize > 0) {
console.log(` ⚠️ ${imagesWithoutSize} image(s) not found in Performance API (size unknown)`);
}
// 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();Understanding the Results
The snippet provides different outputs depending on what it finds:
Success Case: All Images Optimized
If all images outside the viewport already use lazy loading:
✅ Good job, the site has all the images out of the viewport with lazyloading.This means your page is already optimized and no action is needed.
Optimization Opportunities Found
When images without lazy loading are detected outside the viewport, you'll see:
Summary Section:
- Total images: Number of images without lazy loading that are outside the viewport
- Total size: Combined file size of all non-lazy-loaded images (obtained from Performance API)
- Warning (if applicable): Number of images not found in Performance API (usually recently added images or those loaded after measurement)
Image URLs Section:
Each image is listed with:
- Index number: For easy reference
- URL: Full image source URL (from
currentSrcorsrcattribute) - Resolution: Natural dimensions in
width x heightformat - Size: File size in human-readable format (Bytes, KB, MB)
- Shows "size unknown" if image data is not in Performance API
Image Elements Section:
Direct references to the DOM elements for inspection in DevTools. Click any element to:
- Inspect it in the Elements panel
- See its computed styles
- View its position in the DOM tree
How Size Detection Works
The script uses the Performance Resource Timing API to get accurate file sizes:
transferSize: Actual bytes transferred over the network (includes headers)encodedBodySize: Compressed response body size (fallback if transferSize unavailable)
Why this is more reliable than fetch:
- ✅ No CORS issues (data is already available in the browser)
- ✅ No additional network requests needed
- ✅ Reflects actual transfer size, including compression
- ✅ Works with all image sources (same-origin and cross-origin)
Note: Images must be loaded before running the script for size detection to work. Execute it after page load is complete.
What to Do With This Information
When the script identifies non-lazy-loaded images:
1. Add Native Lazy Loading
For modern browsers, simply add the loading attribute:
<img src="image.jpg" alt="Description" loading="lazy" />2. Prioritize by Impact
Focus on images with:
- Largest file sizes (biggest bandwidth savings)
- Lowest position on page (least likely to be seen immediately)
- High resolution (more CPU-intensive to decode)
3. Measure Performance Impact
Before and after implementing lazy loading:
- Check Largest Contentful Paint (LCP) - should improve if LCP wasn't an above-fold image
- Measure Total Blocking Time (TBT) - should reduce with fewer images to decode
- Track Network usage - compare total bytes transferred on initial load
Best Practices
When to use lazy loading:
- ✅ Images below the fold (outside initial viewport)
- ✅ Images in long articles or infinite scroll
- ✅ Images in carousels (except the first visible slide)
- ✅ Images in tabs/accordions (hidden content)
When NOT to use lazy loading:
- ❌ The LCP (Largest Contentful Paint) element
- ❌ Above-the-fold hero images
- ❌ Small images that are part of initial UI (
<10KB) - ❌ Images critical for First Contentful Paint
Important Considerations:
- LCP Images: Never lazy-load your LCP image. This will delay it and hurt Core Web Vitals.
- Layout Shift: Always specify
widthandheightattributes to prevent CLS when images load. - Loading attribute browser support:
loading="lazy"has excellent browser support (opens in a new tab) (96%+ globally as of 2024). - SEO: Search engines can crawl lazy-loaded images, but ensure proper alt text and semantic markup.
Example Output Interpretation
⚠️ Found 55 images without lazy loading
📊 Summary:
• Total images: 55
• Total size: 2.35 MB
📋 Image URLs:
1. https://example.com/hero-carousel-slide-2.jpg (1920x1080, 245.67 KB)
2. https://example.com/product-gallery-3.jpg (800x600, 89.45 KB)
...What this tells you:
- 55 images are loading eagerly but aren't visible initially
- You're wasting 2.35 MB of bandwidth on initial page load
- These images compete for network/CPU resources with critical content
- Adding
loading="lazy"to these images would save mobile users significant data - On slow connections, this could improve TTI by several seconds
Further Reading
For comprehensive guides on image optimization and lazy loading:
- 📖 Browser-level image lazy-loading for the web (opens in a new tab) - Chrome Developers
- 📖 Lazy loading images (opens in a new tab) - web.dev
- 📖 The Complete Guide to Lazy Loading Images (opens in a new tab) - CSS-Tricks