Use JavaScript to Detect and Optimize Slow Images on Your Website
Introduction
Large, unoptimized images can harm your website’s performance and drive away visitors. By using JavaScript, we can programmatically detect slow-loading images, measure their load times using browser APIs, and replace them dynamically with optimized versions. This post walks you through writing a complete front-end solution that improves image performance and enhances user experience.
1. Detecting Large and Slow Images
The first step in optimizing images is detecting which ones are causing performance issues. We can use the browser’s PerformanceObserver and PerformanceEntry APIs to log how long each image takes to load.
// Monitor images as they load
if ('PerformanceObserver' in window) {
const observer = new PerformanceObserver((list) => {
list.getEntriesByType('resource').forEach((entry) => {
if (entry.initiatorType === 'img' && entry.duration > 300) {
console.warn(`Slow image detected: ${entry.name} took ${entry.duration.toFixed(2)}ms`);
}
});
});
observer.observe({ entryTypes: ['resource'] });
}
This snippet listens for all image resources and flags those that take longer than 300 ms to load. You can adjust this threshold depending on your specific performance goals.
2. Calculating Image Size via the Network Information
Sometimes large image sizes—not just slow connections—are the problem. To identify oversized images, you can fetch their file size via a HEAD request.
async function getImageSize(url) {
try {
const response = await fetch(url, { method: 'HEAD' });
const contentLength = response.headers.get('Content-Length');
return contentLength ? parseInt(contentLength, 10) / 1024 : 0; // size in KB
} catch (err) {
console.error('Failed to fetch image size:', err);
return 0;
}
}
This approach helps you identify images that are too large to serve efficiently. You can then use this information to dynamically replace them with optimized variants.
3. Replacing Slow Images with Optimized Versions
Once we know which images are slow or too large, we can automatically swap them with optimized ones. For example, you might serve a smaller version or WebP alternative when load times exceed a threshold.
async function replaceSlowImages(threshold = 300, optimizedSuffix = '-optimized') {
const images = document.querySelectorAll('img');
for (const img of images) {
const entry = performance.getEntriesByName(img.src)[0];
const loadTime = entry ? entry.duration : 0;
if (loadTime > threshold) {
const newSrc = img.src.replace(/(\.\w+)$/, `${optimizedSuffix}$1`);
console.log(`Replacing slow image: ${img.src} => ${newSrc}`);
img.src = newSrc;
}
}
}
This function inspects existing image performance data and replaces any that exceed a load time threshold. The optimized file naming convention is up to you, but suffix-based approaches are simple and effective.
4. Using IntersectionObserver for On-Demand Optimization
Not all images must load at once. Lazy loading ensures that only visible images are fetched. We can combine lazy loading with our performance checks to replace slow images efficiently.
const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
const img = entry.target;
const src = img.getAttribute('data-src');
img.src = src;
observer.unobserve(img);
}
});
});
document.querySelectorAll('img[data-src]').forEach(img => observer.observe(img));
This ensures images load only when they are about to enter the viewport, reducing overall network load and improving page responsiveness. Combined with the earlier detection methods, this makes your frontend image loading strategy much smarter.
5. Real-World Optimization Workflow
Here’s a realistic workflow to integrate all these techniques:
- Use
PerformanceObserverto track image load times in real time. - Measure size using
fetchwithHEADrequests to find oversized files. - Replace slow or heavy images with optimized alternatives using
replaceSlowImages(). - Implement lazy loading with
IntersectionObserver. - Log insights to your analytics backend for continuous optimization.
window.addEventListener('load', () => {
replaceSlowImages(300);
console.log('Image performance optimization complete.');
});
This integrated approach ensures your website stays fast and responsive, even as new images are introduced. Continuous logging helps fine-tune your optimization thresholds over time.
Conclusion
Optimizing images with JavaScript gives developers direct control over performance without modifying the server. By combining real-time detection, performance APIs, and lazy loading, you can deliver visually rich content that’s also performance-friendly. Remember to store insights, automate optimization in your CI/CD pipeline, and always test on various devices and networks.
Useful links:

