Use JavaScript to Detect and Optimize Slow Images on Your Website

Use JavaScript to Detect and Optimize Slow Images on Your Website

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:

  1. Use PerformanceObserver to track image load times in real time.
  2. Measure size using fetch with HEAD requests to find oversized files.
  3. Replace slow or heavy images with optimized alternatives using replaceSlowImages().
  4. Implement lazy loading with IntersectionObserver.
  5. 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: