Back to blog
PerformanceJanuary 1, 2024· 15 min read

Image Optimization and Format Detection: A Developer's Guide

A practical Fusebox guide to image optimization and format detection.

Image Optimization and Format Detection: A Developer's Guide

Images make up 50% of the average webpage's weight. A single unoptimized hero image can add 5MB to your page, causing users to abandon before they even see your content. This guide provides JavaScript tools to detect image formats, analyze optimization opportunities, and implement modern image delivery strategies.

Why Image Optimization Matters

In 2021, an e-commerce site reduced their bounce rate by 40% simply by optimizing product images. They discovered their photographers were uploading 10MB RAW files directly to the site. Pinterest increased their signup rate by 15% after implementing WebP with proper fallbacks. Image optimization isn't just about file size—it's about user experience and conversion rates.

Image Format Detector and Analyzer

Comprehensive image analysis tool:

// Image Format Detector and Analyzer
class ImageAnalyzer {
    constructor() {
        this.images = [];
        this.formats = {
            jpeg: { count: 0, totalSize: 0, signatures: [0xFF, 0xD8, 0xFF] },
            png: { count: 0, totalSize: 0, signatures: [0x89, 0x50, 0x4E, 0x47] },
            gif: { count: 0, totalSize: 0, signatures: [0x47, 0x49, 0x46] },
            webp: { count: 0, totalSize: 0, signatures: [0x52, 0x49, 0x46, 0x46] },
            avif: { count: 0, totalSize: 0, signatures: [] }, // Complex detection
            svg: { count: 0, totalSize: 0, signatures: [] }   // XML-based
        };
    }
    
    // Analyze all images on page
    async analyzePageImages() {
        const images = document.querySelectorAll('img, picture source');
        const imageData = [];
        
        for (const img of images) {
            const src = img.src || img.srcset?.split(' ')[0];
            if (!src) continue;
            
            try {
                const analysis = await this.analyzeImage(src);
                imageData.push(analysis);
            } catch (error) {
                console.error(`Failed to analyze ${src}:`, error);
            }
        }
        
        this.images = imageData;
        return this.generateReport();
    }
    
    // Analyze individual image
    async analyzeImage(url) {
        const startTime = performance.now();
        
        // Fetch image
        const response = await fetch(url);
        const blob = await response.blob();
        const arrayBuffer = await blob.arrayBuffer();
        const uint8Array = new Uint8Array(arrayBuffer);
        
        // Detect format
        const format = this.detectFormat(uint8Array, blob.type);
        
        // Get dimensions
        const dimensions = await this.getImageDimensions(url);
        
        // Analyze optimization
        const optimization = this.analyzeOptimization(uint8Array, format, dimensions);
        
        const analysis = {
            url,
            format,
            mimeType: blob.type,
            fileSize: blob.size,
            dimensions,
            loadTime: performance.now() - startTime,
            optimization,
            // Check if image is lazy loaded
            isLazyLoaded: this.checkLazyLoading(url),
            // Check responsive images
            responsive: this.checkResponsiveImages(url),
            // Estimate quality
            estimatedQuality: this.estimateQuality(uint8Array, format)
        };
        
        // Update format statistics
        if (this.formats[format]) {
            this.formats[format].count++;
            this.formats[format].totalSize += blob.size;
        }
        
        return analysis;
    }
    
    // Detect image format from bytes
    detectFormat(bytes, mimeType) {
        // Check JPEG
        if (bytes[0] === 0xFF && bytes[1] === 0xD8 && bytes[2] === 0xFF) {
            return 'jpeg';
        }
        
        // Check PNG
        if (bytes[0] === 0x89 && bytes[1] === 0x50 && bytes[2] === 0x4E && bytes[3] === 0x47) {
            return 'png';
        }
        
        // Check GIF
        if (bytes[0] === 0x47 && bytes[1] === 0x49 && bytes[2] === 0x46) {
            return 'gif';
        }
        
        // Check WebP
        if (bytes[0] === 0x52 && bytes[1] === 0x49 && bytes[2] === 0x46 && bytes[3] === 0x46) {
            if (bytes[8] === 0x57 && bytes[9] === 0x45 && bytes[10] === 0x42 && bytes[11] === 0x50) {
                return 'webp';
            }
        }
        
        // Check AVIF (more complex)
        if (mimeType === 'image/avif') {
            return 'avif';
        }
        
        // Check SVG
        if (mimeType === 'image/svg+xml') {
            return 'svg';
        }
        
        return 'unknown';
    }
    
    // Get image dimensions
    async getImageDimensions(url) {
        return new Promise((resolve) => {
            const img = new Image();
            img.onload = () => {
                resolve({
                    width: img.naturalWidth,
                    height: img.naturalHeight,
                    aspectRatio: (img.naturalWidth / img.naturalHeight).toFixed(2),
                    displayWidth: img.width || img.naturalWidth,
                    displayHeight: img.height || img.naturalHeight
                });
            };
            img.onerror = () => resolve({ width: 0, height: 0 });
            img.src = url;
        });
    }
    
    // Analyze optimization opportunities
    analyzeOptimization(bytes, format, dimensions) {
        const optimization = {
            canOptimize: false,
            suggestions: [],
            potentialSavings: 0,
            recommendedFormat: null
        };
        
        // Check if image is too large for display size
        if (dimensions.width > dimensions.displayWidth * 2) {
            optimization.canOptimize = true;
            optimization.suggestions.push({
                type: 'resize',
                message: `Image is ${dimensions.width}px wide but displayed at ${dimensions.displayWidth}px`,
                savings: Math.round(bytes.length * 0.75) // Rough estimate
            });
        }
        
        // Format-specific optimizations
        switch (format) {
            case 'png':
                // PNG for photos is usually suboptimal
                if (bytes.length > 100000) { // 100KB
                    optimization.canOptimize = true;
                    optimization.suggestions.push({
                        type: 'format',
                        message: 'Consider JPEG for photographic content',
                        savings: Math.round(bytes.length * 0.7)
                    });
                    optimization.recommendedFormat = 'jpeg';
                }
                break;
                
            case 'jpeg':
                // Check for progressive JPEG
                if (!this.isProgressiveJPEG(bytes)) {
                    optimization.suggestions.push({
                        type: 'progressive',
                        message: 'Convert to progressive JPEG for better perceived performance'
                    });
                }
                
                // Suggest WebP
                optimization.suggestions.push({
                    type: 'format',
                    message: 'WebP could save 25-35% file size',
                    savings: Math.round(bytes.length * 0.3)
                });
                optimization.recommendedFormat = 'webp';
                break;
                
            case 'gif':
                optimization.canOptimize = true;
                optimization.suggestions.push({
                    type: 'format',
                    message: 'Replace GIF with video (MP4) for 90% size reduction',
                    savings: Math.round(bytes.length * 0.9)
                });
                break;
        }
        
        // Calculate total potential savings
        optimization.potentialSavings = optimization.suggestions
            .reduce((total, s) => total + (s.savings || 0), 0);
        
        return optimization;
    }
    
    // Check if JPEG is progressive
    isProgressiveJPEG(bytes) {
        // Look for SOF2 marker (0xFFC2) which indicates progressive
        for (let i = 0; i < bytes.length - 1; i++) {
            if (bytes[i] === 0xFF && bytes[i + 1] === 0xC2) {
                return true;
            }
        }
        return false;
    }
    
    // Estimate image quality
    estimateQuality(bytes, format) {
        if (format === 'jpeg') {
            // Simple quality estimation based on quantization tables
            // This is a rough approximation
            const fileSize = bytes.length;
            for (let i = 0; i < bytes.length - 4; i++) {
                if (bytes[i] === 0xFF && bytes[i + 1] === 0xDB) {
                    // Found quantization table
                    const precision = bytes[i + 4] >> 4;
                    const tableData = bytes[i + 5];
                    
                    // Very rough quality estimate
                    if (tableData < 10) return 95;
                    if (tableData < 25) return 85;
                    if (tableData < 50) return 75;
                    if (tableData < 100) return 60;
                    return 50;
                }
            }
        }
        return null;
    }
    
    // Check lazy loading
    checkLazyLoading(url) {
        const img = document.querySelector(`img[src="${url}"]`);
        if (!img) return false;
        
        return img.loading === 'lazy' || 
               img.hasAttribute('data-lazy') ||
               img.classList.contains('lazyload');
    }
    
    // Check responsive images
    checkResponsiveImages(url) {
        const img = document.querySelector(`img[src="${url}"]`);
        if (!img) return { isResponsive: false };
        
        const picture = img.closest('picture');
        const srcset = img.srcset;
        
        return {
            isResponsive: !!(picture || srcset),
            hasPicture: !!picture,
            hasSrcset: !!srcset,
            sources: picture ? picture.querySelectorAll('source').length : 0
        };
    }
    
    // Generate comprehensive report
    generateReport() {
        const report = {
            summary: {
                totalImages: this.images.length,
                totalSize: this.images.reduce((sum, img) => sum + img.fileSize, 0),
                averageSize: Math.round(this.images.reduce((sum, img) => sum + img.fileSize, 0) / this.images.length),
                formats: this.formats,
                lazyLoaded: this.images.filter(img => img.isLazyLoaded).length,
                responsive: this.images.filter(img => img.responsive.isResponsive).length
            },
            optimizationOpportunities: {
                totalPotentialSavings: this.images.reduce((sum, img) => 
                    sum + img.optimization.potentialSavings, 0),
                count: this.images.filter(img => img.optimization.canOptimize).length
            },
            images: this.images.sort((a, b) => b.fileSize - a.fileSize),
            recommendations: this.generateRecommendations()
        };
        
        return report;
    }
    
    // Generate recommendations
    generateRecommendations() {
        const recommendations = [];
        
        // Check for missing modern formats
        if (this.formats.webp.count === 0 && this.formats.jpeg.count > 0) {
            recommendations.push({
                priority: 'high',
                type: 'format',
                message: 'Implement WebP format with JPEG fallback',
                impact: 'Save 25-35% on image file sizes',
                implementation: `<picture>
  <source srcset="image.webp" type="image/webp">
  <img src="image.jpg" alt="Description">
</picture>`
            });
        }
        
        // Check for missing lazy loading
        const lazyLoadPercentage = (this.images.filter(img => img.isLazyLoaded).length / this.images.length) * 100;
        if (lazyLoadPercentage < 50) {
            recommendations.push({
                priority: 'high',
                type: 'performance',
                message: 'Implement lazy loading for below-the-fold images',
                impact: 'Reduce initial page load by 50%+',
                implementation: '<img src="image.jpg" loading="lazy" alt="Description">'
            });
        }
        
        // Check for oversized images
        const oversizedImages = this.images.filter(img => img.fileSize > 500000); // 500KB
        if (oversizedImages.length > 0) {
            recommendations.push({
                priority: 'critical',
                type: 'optimization',
                message: `${oversizedImages.length} images are over 500KB`,
                impact: 'Significant performance impact on mobile',
                images: oversizedImages.map(img => ({
                    url: img.url,
                    size: img.fileSize,
                    format: img.format
                }))
            });
        }
        
        return recommendations;
    }
}

// Run analysis
const analyzer = new ImageAnalyzer();
analyzer.analyzePageImages().then(report => {
    console.log('Image Optimization Report:', report);
});

Modern Image Format Implementation

Implement next-gen image formats with fallbacks:

// Modern Image Format Manager
class ModernImageManager {
    constructor() {
        this.supportedFormats = this.detectFormatSupport();
        this.observer = null;
    }
    
    // Detect browser format support
    detectFormatSupport() {
        const formats = {
            webp: false,
            avif: false,
            jpeg2000: false,
            jpegxr: false
        };
        
        // Test WebP support
        const webpData = 'data:image/webp;base64,UklGRh4AAABXRUJQVlA4TBEAAAAvAAAAAAfQ//73v/+BiOh/AAA=';
        const webpImg = new Image();
        webpImg.onload = () => { formats.webp = true; };
        webpImg.src = webpData;
        
        // Test AVIF support
        const avifData = 'data:image/avif;base64,AAAAHGZ0eXBhdmlmAAAAAGF2aWZtaWYxbWlhZgAAAPBtZXRhAAAAAAAAABloZGxyAAAAAAAAAABwaWN0AAAAAAAAAAAAAAAAbGliYXZpZgAAAAAOcGl0bQAAAAAAAQAAAB5pbG9jAAAAAEQAAAEAAQAAAAEAAAEUAAAAJgAAAChpaW5mAAAAAAABAAAAGmluZmUCAAAAAAEAAGF2MDEAAAAAamlwcnAAAABLaXBjbwAAABNjb2xybmNseAABAA0AAIAAAAAMYXYxQ4EBDAAAAABtZGF0EgAKBzgABps9JaeBvww=';
        const avifImg = new Image();
        avifImg.onload = () => { formats.avif = true; };
        avifImg.src = avifData;
        
        return formats;
    }
    
    // Convert image URLs to modern format
    getModernImageUrl(originalUrl, preferredFormat = null) {
        const extension = originalUrl.split('.').pop().toLowerCase();
        const basePath = originalUrl.substring(0, originalUrl.lastIndexOf('.'));
        
        // Determine best format
        let modernFormat = preferredFormat;
        if (!modernFormat) {
            if (this.supportedFormats.avif) {
                modernFormat = 'avif';
            } else if (this.supportedFormats.webp) {
                modernFormat = 'webp';
            }
        }
        
        if (!modernFormat) return originalUrl;
        
        // Return modern format URL
        return `${basePath}.${modernFormat}`;
    }
    
    // Generate picture element with fallbacks
    generatePictureElement(src, alt, sizes = null) {
        const sources = [];
        const basePath = src.substring(0, src.lastIndexOf('.'));
        const extension = src.split('.').pop().toLowerCase();
        
        // Add AVIF source
        if (this.supportedFormats.avif) {
            sources.push({
                srcset: `${basePath}.avif`,
                type: 'image/avif'
            });
        }
        
        // Add WebP source
        if (this.supportedFormats.webp) {
            sources.push({
                srcset: `${basePath}.webp`,
                type: 'image/webp'
            });
        }
        
        // Generate HTML
        let html = '<picture>\n';
        
        sources.forEach(source => {
            html += `  <source srcset="${source.srcset}" type="${source.type}"`;
            if (sizes) html += ` sizes="${sizes}"`;
            html += '>\n';
        });
        
        html += `  <img src="${src}" alt="${alt}"`;
        if (sizes) html += ` sizes="${sizes}"`;
        html += ' loading="lazy">\n';
        html += '</picture>';
        
        return html;
    }
    
    // Implement responsive images
    generateResponsiveImage(src, alt, breakpoints = {}) {
        const defaultBreakpoints = {
            small: { width: 640, maxWidth: '640px' },
            medium: { width: 1024, maxWidth: '1024px' },
            large: { width: 1920, maxWidth: '1920px' }
        };
        
        const bp = { ...defaultBreakpoints, ...breakpoints };
        const sources = [];
        
        // Generate sources for each format and breakpoint
        ['avif', 'webp', 'jpg'].forEach(format => {
            if (format === 'jpg' || this.supportedFormats[format]) {
                const srcset = Object.entries(bp)
                    .map(([size, config]) => 
                        `${src.replace(/\.[^.]+$/, '')}-${config.width}w.${format} ${config.width}w`
                    )
                    .join(', ');
                
                sources.push({
                    srcset,
                    type: format === 'jpg' ? 'image/jpeg' : `image/${format}`
                });
            }
        });
        
        // Generate sizes attribute
        const sizes = Object.entries(bp)
            .map(([size, config]) => `(max-width: ${config.maxWidth}) ${config.width}px`)
            .join(', ') + ', 100vw';
        
        return this.generatePictureElementFromSources(sources, src, alt, sizes);
    }
    
    // Generate picture element from sources
    generatePictureElementFromSources(sources, fallbackSrc, alt, sizes) {
        let html = '<picture>\n';
        
        sources.forEach(source => {
            html += `  <source\n`;
            html += `    srcset="${source.srcset}"\n`;
            html += `    type="${source.type}"\n`;
            if (sizes) html += `    sizes="${sizes}"\n`;
            html += '  >\n';
        });
        
        html += `  <img\n`;
        html += `    src="${fallbackSrc}"\n`;
        html += `    alt="${alt}"\n`;
        if (sizes) html += `    sizes="${sizes}"\n`;
        html += '    loading="lazy"\n';
        html += '    decoding="async"\n';
        html += '  >\n';
        html += '</picture>';
        
        return html;
    }
    
    // Setup lazy loading with Intersection Observer
    setupLazyLoading() {
        if ('IntersectionObserver' in window) {
            this.observer = new IntersectionObserver((entries) => {
                entries.forEach(entry => {
                    if (entry.isIntersecting) {
                        this.loadImage(entry.target);
                        this.observer.unobserve(entry.target);
                    }
                });
            }, {
                rootMargin: '50px 0px',
                threshold: 0.01
            });
            
            // Observe all lazy images
            document.querySelectorAll('img[data-src], picture[data-src]').forEach(img => {
                this.observer.observe(img);
            });
        } else {
            // Fallback for browsers without Intersection Observer
            this.loadAllImages();
        }
    }
    
    // Load individual image
    loadImage(element) {
        if (element.tagName === 'IMG') {
            element.src = element.dataset.src;
            element.removeAttribute('data-src');
        } else if (element.tagName === 'PICTURE') {
            const sources = element.querySelectorAll('source[data-srcset]');
            sources.forEach(source => {
                source.srcset = source.dataset.srcset;
                source.removeAttribute('data-srcset');
            });
            
            const img = element.querySelector('img[data-src]');
            if (img) {
                img.src = img.dataset.src;
                img.removeAttribute('data-src');
            }
        }
        
        element.classList.add('loaded');
    }
    
    // Load all images (fallback)
    loadAllImages() {
        document.querySelectorAll('img[data-src], picture[data-src]').forEach(element => {
            this.loadImage(element);
        });
    }
}

// Initialize
const imageManager = new ModernImageManager();
imageManager.setupLazyLoading();

Image Loading Performance Monitor

Monitor and optimize image loading:

// Image Performance Monitor
class ImagePerformanceMonitor {
    constructor() {
        this.metrics = {
            images: [],
            lcp: null,
            totalLoadTime: 0,
            totalSize: 0
        };
        this.setupObservers();
    }
    
    // Setup performance observers
    setupObservers() {
        // Monitor resource timing for images
        if ('PerformanceObserver' in window) {
            const resourceObserver = new PerformanceObserver((list) => {
                for (const entry of list.getEntries()) {
                    if (entry.initiatorType === 'img' || 
                        entry.initiatorType === 'css' || // Background images
                        entry.name.match(/\.(jpg|jpeg|png|gif|webp|avif|svg)/i)) {
                        this.trackImagePerformance(entry);
                    }
                }
            });
            
            resourceObserver.observe({ entryTypes: ['resource'] });
            
            // Monitor LCP
            const lcpObserver = new PerformanceObserver((list) => {
                const entries = list.getEntries();
                const lastEntry = entries[entries.length - 1];
                
                if (lastEntry.element && lastEntry.element.tagName === 'IMG') {
                    this.metrics.lcp = {
                        element: lastEntry.element,
                        url: lastEntry.url,
                        loadTime: lastEntry.loadTime,
                        renderTime: lastEntry.renderTime,
                        size: lastEntry.size
                    };
                }
            });
            
            try {
                lcpObserver.observe({ entryTypes: ['largest-contentful-paint'] });
            } catch (e) {
                console.log('LCP observation not supported');
            }
        }
    }
    
    // Track individual image performance
    trackImagePerformance(entry) {
        const imageMetrics = {
            url: entry.name,
            startTime: entry.startTime,
            duration: entry.duration,
            transferSize: entry.transferSize || 0,
            encodedBodySize: entry.encodedBodySize || 0,
            decodedBodySize: entry.decodedBodySize || 0,
            // Connection timing
            dns: entry.domainLookupEnd - entry.domainLookupStart,
            tcp: entry.connectEnd - entry.connectStart,
            ssl: entry.secureConnectionStart > 0 ? 
                 entry.connectEnd - entry.secureConnectionStart : 0,
            ttfb: entry.responseStart - entry.requestStart,
            download: entry.responseEnd - entry.responseStart,
            // Cache status
            fromCache: entry.transferSize === 0 && entry.decodedBodySize > 0,
            // Protocol
            protocol: entry.nextHopProtocol,
            // Priority hints
            priority: this.getImagePriority(entry.name)
        };
        
        this.metrics.images.push(imageMetrics);
        this.metrics.totalLoadTime += imageMetrics.duration;
        this.metrics.totalSize += imageMetrics.transferSize;
        
        // Check for performance issues
        this.checkPerformanceIssues(imageMetrics);
    }
    
    // Get image loading priority
    getImagePriority(url) {
        const img = document.querySelector(`img[src="${url}"]`);
        if (!img) return 'unknown';
        
        // Check fetchpriority attribute
        if (img.fetchpriority) return img.fetchpriority;
        
        // Check if in viewport
        const rect = img.getBoundingClientRect();
        const inViewport = rect.top < window.innerHeight && rect.bottom > 0;
        
        return inViewport ? 'high' : 'low';
    }
    
    // Check for performance issues
    checkPerformanceIssues(metrics) {
        const issues = [];
        
        // Slow TTFB
        if (metrics.ttfb > 600) {
            issues.push({
                type: 'slow-ttfb',
                message: `Slow server response: ${metrics.ttfb.toFixed(0)}ms`,
                severity: 'high'
            });
        }
        
        // Large transfer size
        if (metrics.transferSize > 500000) { // 500KB
            issues.push({
                type: 'large-size',
                message: `Large image: ${(metrics.transferSize / 1024 / 1024).toFixed(2)}MB`,
                severity: 'high'
            });
        }
        
        // Not cached
        if (!metrics.fromCache && metrics.url.includes(window.location.hostname)) {
            issues.push({
                type: 'not-cached',
                message: 'Image not served from cache',
                severity: 'medium'
            });
        }
        
        // Slow total load time
        if (metrics.duration > 3000) {
            issues.push({
                type: 'slow-load',
                message: `Slow load time: ${metrics.duration.toFixed(0)}ms`,
                severity: 'high'
            });
        }
        
        if (issues.length > 0) {
            metrics.issues = issues;
        }
    }
    
    // Generate performance report
    generateReport() {
        const report = {
            summary: {
                totalImages: this.metrics.images.length,
                totalSize: this.metrics.totalSize,
                totalLoadTime: this.metrics.totalLoadTime,
                averageLoadTime: this.metrics.totalLoadTime / this.metrics.images.length,
                cachedImages: this.metrics.images.filter(img => img.fromCache).length,
                lcpImage: this.metrics.lcp
            },
            byProtocol: this.groupByProtocol(),
            performanceIssues: this.getPerformanceIssues(),
            recommendations: this.generateOptimizationRecommendations(),
            timeline: this.generateLoadTimeline()
        };
        
        return report;
    }
    
    // Group images by protocol
    groupByProtocol() {
        const protocols = {};
        
        this.metrics.images.forEach(img => {
            const protocol = img.protocol || 'unknown';
            if (!protocols[protocol]) {
                protocols[protocol] = {
                    count: 0,
                    totalSize: 0,
                    averageLoadTime: 0
                };
            }
            
            protocols[protocol].count++;
            protocols[protocol].totalSize += img.transferSize;
            protocols[protocol].averageLoadTime += img.duration;
        });
        
        // Calculate averages
        Object.values(protocols).forEach(data => {
            data.averageLoadTime = data.averageLoadTime / data.count;
        });
        
        return protocols;
    }
    
    // Get all performance issues
    getPerformanceIssues() {
        return this.metrics.images
            .filter(img => img.issues && img.issues.length > 0)
            .map(img => ({
                url: img.url,
                issues: img.issues,
                impact: img.duration
            }))
            .sort((a, b) => b.impact - a.impact);
    }
    
    // Generate optimization recommendations
    generateOptimizationRecommendations() {
        const recommendations = [];
        
        // Check for missing HTTP/2
        const http1Images = this.metrics.images.filter(img => 
            img.protocol && img.protocol.includes('http/1'));
        
        if (http1Images.length > 0) {
            recommendations.push({
                type: 'protocol',
                priority: 'high',
                message: `${http1Images.length} images served over HTTP/1.1`,
                impact: 'Enable HTTP/2 for parallel loading',
                images: http1Images.map(img => img.url)
            });
        }
        
        // Check for missing lazy loading on large images
        const largeEagerImages = this.metrics.images
            .filter(img => img.transferSize > 100000 && img.priority === 'high')
            .filter(img => {
                const element = document.querySelector(`img[src="${img.url}"]`);
                return element && element.loading !== 'lazy';
            });
        
        if (largeEagerImages.length > 0) {
            recommendations.push({
                type: 'lazy-loading',
                priority: 'high',
                message: `${largeEagerImages.length} large images loaded eagerly`,
                impact: 'Add loading="lazy" to reduce initial load',
                savings: largeEagerImages.reduce((sum, img) => sum + img.transferSize, 0)
            });
        }
        
        // Check for opportunities to use srcset
        const nonResponsiveImages = this.metrics.images.filter(img => {
            const element = document.querySelector(`img[src="${img.url}"]`);
            return element && !element.srcset && !element.closest('picture');
        });
        
        if (nonResponsiveImages.length > 0) {
            recommendations.push({
                type: 'responsive',
                priority: 'medium',
                message: `${nonResponsiveImages.length} images without responsive alternatives`,
                impact: 'Implement srcset for optimal image delivery'
            });
        }
        
        return recommendations;
    }
    
    // Generate load timeline
    generateLoadTimeline() {
        return this.metrics.images
            .sort((a, b) => a.startTime - b.startTime)
            .map(img => ({
                url: img.url.split('/').pop(),
                start: img.startTime,
                end: img.startTime + img.duration,
                duration: img.duration,
                size: img.transferSize
            }));
    }
}

// Start monitoring
const performanceMonitor = new ImagePerformanceMonitor();

// Get report after page load
window.addEventListener('load', () => {
    setTimeout(() => {
        console.log('Image Performance Report:', performanceMonitor.generateReport());
    }, 2000);
});

Image Optimization Automation

Automate image optimization recommendations:

// Image Optimization Automator
class ImageOptimizationAutomator {
    constructor() {
        this.optimizations = [];
    }
    
    // Generate optimization script
    async generateOptimizationScript() {
        const images = await this.collectAllImages();
        const script = {
            bash: this.generateBashScript(images),
            node: this.generateNodeScript(images),
            html: this.generateHTMLUpdates(images)
        };
        
        return script;
    }
    
    // Collect all images
    async collectAllImages() {
        const images = [];
        const elements = document.querySelectorAll('img, picture source');
        
        for (const element of elements) {
            const src = element.src || element.srcset?.split(' ')[0];
            if (!src) continue;
            
            try {
                const response = await fetch(src);
                const blob = await response.blob();
                
                images.push({
                    url: src,
                    size: blob.size,
                    type: blob.type,
                    element: element.tagName.toLowerCase(),
                    hasAlt: element.alt ? true : false,
                    hasLazyLoading: element.loading === 'lazy',
                    hasSrcset: !!element.srcset
                });
            } catch (e) {
                console.error(`Failed to analyze ${src}`);
            }
        }
        
        return images;
    }
    
    // Generate bash optimization script
    generateBashScript(images) {
        let script = `#!/bin/bash
# Image Optimization Script
# Generated on ${new Date().toISOString()}

# Install required tools
echo "Installing image optimization tools..."
npm install -g imagemin-cli imagemin-webp imagemin-avif sharp-cli

# Create optimized directory
mkdir -p optimized/{webp,avif,compressed}

`;
        
        images.forEach(img => {
            const filename = img.url.split('/').pop();
            const name = filename.substring(0, filename.lastIndexOf('.'));
            const ext = filename.split('.').pop();
            
            script += `
# Optimize ${filename}
echo "Processing ${filename}..."

# Generate WebP
imagemin ${filename} --plugin=webp --out-dir=optimized/webp

# Generate AVIF
imagemin ${filename} --plugin=avif --out-dir=optimized/avif

# Compress original
imagemin ${filename} --out-dir=optimized/compressed

# Generate responsive sizes
for size in 320 640 1024 1920; do
    sharp resize \${size} --input ${filename} --output optimized/${name}-\${size}w.${ext}
    sharp resize \${size} --input ${filename} --output optimized/webp/${name}-\${size}w.webp
done
`;
        });
        
        script += `
echo "Optimization complete!"
echo "Results saved in ./optimized/"
`;
        
        return script;
    }
    
    // Generate Node.js optimization script
    generateNodeScript(images) {
        return `// Image Optimization Script
const sharp = require('sharp');
const imagemin = require('imagemin');
const imageminWebp = require('imagemin-webp');
const imageminAvif = require('imagemin-avif');
const fs = require('fs').promises;
const path = require('path');

async function optimizeImages() {
    const images = ${JSON.stringify(images, null, 2)};
    
    // Create output directories
    await fs.mkdir('optimized/webp', { recursive: true });
    await fs.mkdir('optimized/avif', { recursive: true });
    
    for (const img of images) {
        const filename = path.basename(img.url);
        const name = path.parse(filename).name;
        
        console.log(\`Processing \${filename}...\`);
        
        try {
            // Read original image
            const buffer = await fs.readFile(filename);
            
            // Generate WebP
            await imagemin.buffer(buffer, {
                plugins: [imageminWebp({ quality: 80 })]
            }).then(data => 
                fs.writeFile(\`optimized/webp/\${name}.webp\`, data)
            );
            
            // Generate AVIF
            await imagemin.buffer(buffer, {
                plugins: [imageminAvif({ quality: 80 })]
            }).then(data => 
                fs.writeFile(\`optimized/avif/\${name}.avif\`, data)
            );
            
            // Generate responsive sizes
            for (const width of [320, 640, 1024, 1920]) {
                await sharp(buffer)
                    .resize(width)
                    .toFile(\`optimized/\${name}-\${width}w.jpg\`);
                    
                await sharp(buffer)
                    .resize(width)
                    .webp({ quality: 80 })
                    .toFile(\`optimized/webp/\${name}-\${width}w.webp\`);
            }
            
        } catch (error) {
            console.error(\`Failed to process \${filename}:\`, error);
        }
    }
    
    console.log('Optimization complete!');
}

optimizeImages().catch(console.error);
`;
    }
    
    // Generate HTML updates
    generateHTMLUpdates(images) {
        let updates = `<!-- Image Optimization HTML Updates -->
<!-- Generated on ${new Date().toISOString()} -->

`;
        
        images.forEach(img => {
            const filename = img.url.split('/').pop();
            const name = filename.substring(0, filename.lastIndexOf('.'));
            
            // Skip if already optimized
            if (img.element === 'source') return;
            
            updates += `
<!-- Replace: <img src="${img.url}" ...> -->
<picture>
  <source 
    type="image/avif"
    srcset="${name}-320w.avif 320w,
            ${name}-640w.avif 640w,
            ${name}-1024w.avif 1024w,
            ${name}-1920w.avif 1920w"
    sizes="(max-width: 640px) 100vw, (max-width: 1024px) 50vw, 33vw"
  >
  <source 
    type="image/webp"
    srcset="${name}-320w.webp 320w,
            ${name}-640w.webp 640w,
            ${name}-1024w.webp 1024w,
            ${name}-1920w.webp 1920w"
    sizes="(max-width: 640px) 100vw, (max-width: 1024px) 50vw, 33vw"
  >
  <img 
    src="${img.url}"
    srcset="${name}-320w.jpg 320w,
            ${name}-640w.jpg 640w,
            ${name}-1024w.jpg 1024w,
            ${name}-1920w.jpg 1920w"
    sizes="(max-width: 640px) 100vw, (max-width: 1024px) 50vw, 33vw"
    loading="lazy"
    decoding="async"
    alt="${img.hasAlt ? 'YOUR_ALT_TEXT' : 'ADD_DESCRIPTIVE_ALT_TEXT'}"
  >
</picture>
`;
        });
        
        return updates;
    }
}

// Generate optimization scripts
const automator = new ImageOptimizationAutomator();
automator.generateOptimizationScript().then(scripts => {
    console.log('Bash Script:', scripts.bash);
    console.log('Node Script:', scripts.node);
    console.log('HTML Updates:', scripts.html);
});

Best Practices for Image Optimization

  1. Choose the Right Format: JPEG for photos, PNG for graphics with transparency
  2. Implement Modern Formats: Use WebP/AVIF with fallbacks
  3. Responsive Images: Serve different sizes for different screens
  4. Lazy Loading: Load images only when needed
  5. Optimize Compression: Balance quality vs file size
  6. Use CDN: Serve images from edge locations
  7. Enable Caching: Set proper cache headers
  8. Preload Critical Images: Use rel="preload" for hero images
  9. Monitor Performance: Track Core Web Vitals impact
  10. Automate Optimization: Include in build process

Common Image Optimization Mistakes

  • Original Resolution: Serving 4K images for thumbnails
  • Wrong Format: Using PNG for photographs
  • No Compression: Serving unoptimized images from CMS
  • Missing Alt Text: Accessibility and SEO impact
  • Eager Loading Everything: Not using lazy loading
  • Ignoring Modern Formats: Missing 30%+ size savings
  • No Responsive Images: Same image for mobile and desktop
  • Poor Caching: Re-downloading unchanged images
  • JavaScript-Dependent Loading: Images that require JS
  • Unoptimized Sprites: Huge sprite sheets with unused images

Remember: Images are often the largest performance bottleneck. A well-optimized image strategy can cut page load times in half and significantly improve user experience, especially on mobile devices.