Mobile Image Optimization 2025: Complete Guide for Fast Loading

Mobile Performance Published: January 25, 2025 Reading time: 12 minutes

Optimize images for mobile devices in 2025. Master responsive images, adaptive serving, lazy loading, and advanced techniques for superior mobile performance and user experience.

Mobile Image Challenges 2025

Mobile devices present unique challenges for image optimization, from varying screen densities to limited bandwidth and battery constraints. Understanding these challenges is crucial for effective optimization.

Key Mobile Constraints

📶 Network Limitations
  • Variable bandwidth: 3G to 5G speeds
  • Data costs: Expensive mobile data plans
  • Latency issues: Higher round-trip times
  • Connection instability: Frequent disconnections
🔋 Device Constraints
  • Battery life: Processing power affects battery
  • Memory limits: Limited RAM for image processing
  • CPU power: Slower processors on budget devices
  • Storage space: Limited cache capacity

Screen Density Variations

Device Category Pixel Density (DPR) Common Resolutions Optimization Strategy
Budget Phones 1x - 1.5x 720p, 1080p Standard resolution, aggressive compression
Mid-range Phones 2x - 2.5x 1080p, 1440p 2x images with moderate compression
Flagship Phones 3x - 4x 1440p, 4K High-resolution with quality optimization
Tablets 1x - 2x 1080p, 1440p, 4K Larger images, balanced compression

Mobile Usage Patterns

⚡ Quick Sessions
2-3 min

Average mobile session duration

📱 Portrait Mode
94%

Time spent in portrait orientation

👆 Touch First
100%

Touch-based interaction

Responsive Image Techniques

Responsive images adapt to different screen sizes and densities, ensuring optimal quality and performance across all mobile devices.

Srcset and Sizes Implementation

<!-- Basic responsive image -->
<img src="image-400.jpg"
     srcset="image-400.jpg 400w,
             image-800.jpg 800w,
             image-1200.jpg 1200w,
             image-1600.jpg 1600w"
     sizes="(max-width: 480px) 100vw,
            (max-width: 768px) 50vw,
            33vw"
     alt="Responsive image">

<!-- High DPI support -->
<img src="image.jpg"
     srcset="image.jpg 1x,
             image@2x.jpg 2x,
             image@3x.jpg 3x"
     alt="High DPI image">

<!-- Combined width and density descriptors -->
<img src="image-400.jpg"
     srcset="image-400.jpg 400w,
             image-400@2x.jpg 800w,
             image-800.jpg 800w,
             image-800@2x.jpg 1600w"
     sizes="(max-width: 480px) 100vw, 50vw"
     alt="Combined responsive image">

Picture Element for Art Direction

<picture>
  <!-- Mobile: cropped square image -->
  <source media="(max-width: 480px)"
          srcset="hero-mobile-400.webp 400w,
                  hero-mobile-800.webp 800w"
          sizes="100vw"
          type="image/webp">
  
  <!-- Tablet: 16:9 aspect ratio -->
  <source media="(max-width: 1024px)"
          srcset="hero-tablet-800.webp 800w,
                  hero-tablet-1200.webp 1200w"
          sizes="100vw"
          type="image/webp">
  
  <!-- Desktop: full width hero -->
  <source srcset="hero-desktop-1200.webp 1200w,
                  hero-desktop-1800.webp 1800w,
                  hero-desktop-2400.webp 2400w"
          sizes="100vw"
          type="image/webp">
  
  <!-- Fallback -->
  <img src="hero-desktop-1200.jpg"
       srcset="hero-desktop-1200.jpg 1200w,
               hero-desktop-1800.jpg 1800w"
       sizes="100vw"
       alt="Hero image"
       loading="lazy">
</picture>

Responsive Image Breakpoints

Breakpoint Screen Width Image Width Recommended Sizes
Small Mobile 320px - 480px 320px - 480px 320w, 480w, 640w (2x)
Large Mobile 481px - 768px 481px - 768px 480w, 768w, 1024w (2x)
Tablet 769px - 1024px 400px - 800px 400w, 800w, 1200w (2x)
Desktop 1025px+ 600px - 1200px 600w, 1200w, 1800w (2x)

Adaptive Image Serving

Adaptive serving delivers optimized images based on device capabilities, network conditions, and user preferences for the best possible experience.

Client Hints Implementation

<!-- Enable client hints -->
<meta http-equiv="Accept-CH" content="DPR, Width, Viewport-Width, Device-Memory, RTT, Downlink">

<!-- Server-side adaptation (PHP example) -->
<?php
// Get client hints
$dpr = $_SERVER['HTTP_DPR'] ?? 1;
$width = $_SERVER['HTTP_WIDTH'] ?? 800;
$viewportWidth = $_SERVER['HTTP_VIEWPORT_WIDTH'] ?? 1024;
$deviceMemory = $_SERVER['HTTP_DEVICE_MEMORY'] ?? 4;
$rtt = $_SERVER['HTTP_RTT'] ?? 100;
$downlink = $_SERVER['HTTP_DOWNLINK'] ?? 10;

// Determine optimal image
function getOptimalImage($baseImage, $dpr, $width, $deviceMemory, $downlink) {
    // Low-end device or slow connection
    if ($deviceMemory < 2 || $downlink < 1) {
        $quality = 60;
        $format = 'jpg';
    }
    // High-end device with fast connection
    elseif ($deviceMemory >= 4 && $downlink >= 5) {
        $quality = 85;
        $format = 'avif';
    }
    // Medium device/connection
    else {
        $quality = 75;
        $format = 'webp';
    }
    
    $targetWidth = min($width * $dpr, 2400);
    return "images/{$baseImage}-{$targetWidth}q{$quality}.{$format}";
}

$optimizedImage = getOptimalImage('hero', $dpr, $width, $deviceMemory, $downlink);
?>

<img src="<?= $optimizedImage ?>" alt="Adaptive image">

Network-Aware Loading

// JavaScript network detection
class NetworkAwareImageLoader {
    constructor() {
        this.connection = navigator.connection || navigator.mozConnection || navigator.webkitConnection;
        this.isSlowConnection = this.detectSlowConnection();
    }
    
    detectSlowConnection() {
        if (!this.connection) return false;
        
        // Check for slow connection types
        const slowConnections = ['slow-2g', '2g', '3g'];
        if (slowConnections.includes(this.connection.effectiveType)) {
            return true;
        }
        
        // Check for data saver mode
        if (this.connection.saveData) {
            return true;
        }
        
        // Check for low bandwidth
        if (this.connection.downlink && this.connection.downlink < 1.5) {
            return true;
        }
        
        return false;
    }
    
    getImageSrc(baseSrc, sizes) {
        if (this.isSlowConnection) {
            // Serve smaller, more compressed images
            return sizes.small || baseSrc;
        }
        
        // Serve high-quality images
        return sizes.large || baseSrc;
    }
    
    loadImage(element) {
        const baseSrc = element.dataset.src;
        const sizes = {
            small: element.dataset.srcSmall,
            large: element.dataset.srcLarge
        };
        
        element.src = this.getImageSrc(baseSrc, sizes);
    }
}

// Usage
const loader = new NetworkAwareImageLoader();
document.querySelectorAll('[data-src]').forEach(img => {
    loader.loadImage(img);
});

Device Memory Optimization

// Memory-aware image loading
function getMemoryAwareImageQuality() {
    const memory = navigator.deviceMemory || 4; // Default to 4GB
    
    if (memory <= 1) {
        return {
            quality: 50,
            maxWidth: 800,
            format: 'jpg'
        };
    } else if (memory <= 2) {
        return {
            quality: 65,
            maxWidth: 1200,
            format: 'webp'
        };
    } else if (memory <= 4) {
        return {
            quality: 75,
            maxWidth: 1600,
            format: 'webp'
        };
    } else {
        return {
            quality: 85,
            maxWidth: 2400,
            format: 'avif'
        };
    }
}

// Apply memory-aware settings
const settings = getMemoryAwareImageQuality();
document.documentElement.style.setProperty('--max-image-width', settings.maxWidth + 'px');
document.documentElement.classList.add(`memory-${Math.floor(navigator.deviceMemory || 4)}gb`);

Advanced Lazy Loading

Modern lazy loading techniques go beyond basic viewport detection to provide smooth, performance-optimized image loading experiences.

Native Lazy Loading with Enhancements

<!-- Basic native lazy loading -->
<img src="image.jpg" alt="Description" loading="lazy">

<!-- Enhanced with intersection observer fallback -->
<img src="placeholder.jpg"
     data-src="image.jpg"
     data-srcset="image-400.jpg 400w, image-800.jpg 800w"
     alt="Description"
     loading="lazy"
     class="lazy-image">

<script>
// Enhanced lazy loading with fallback
class EnhancedLazyLoader {
    constructor() {
        this.images = document.querySelectorAll('.lazy-image');
        this.imageObserver = null;
        this.init();
    }
    
    init() {
        // Check for native lazy loading support
        if ('loading' in HTMLImageElement.prototype) {
            this.loadImagesNatively();
        } else {
            this.loadImagesWithObserver();
        }
    }
    
    loadImagesNatively() {
        this.images.forEach(img => {
            if (img.dataset.src) {
                img.src = img.dataset.src;
            }
            if (img.dataset.srcset) {
                img.srcset = img.dataset.srcset;
            }
        });
    }
    
    loadImagesWithObserver() {
        this.imageObserver = new IntersectionObserver((entries, observer) => {
            entries.forEach(entry => {
                if (entry.isIntersecting) {
                    this.loadImage(entry.target);
                    observer.unobserve(entry.target);
                }
            });
        }, {
            rootMargin: '50px 0px',
            threshold: 0.01
        });
        
        this.images.forEach(img => this.imageObserver.observe(img));
    }
    
    loadImage(img) {
        return new Promise((resolve, reject) => {
            const imageLoader = new Image();
            
            imageLoader.onload = () => {
                img.src = img.dataset.src;
                if (img.dataset.srcset) {
                    img.srcset = img.dataset.srcset;
                }
                img.classList.add('loaded');
                resolve(img);
            };
            
            imageLoader.onerror = reject;
            imageLoader.src = img.dataset.src;
        });
    }
}

new EnhancedLazyLoader();
</script>

Progressive Image Loading

// Progressive image loading with blur effect
class ProgressiveImageLoader {
    constructor() {
        this.images = document.querySelectorAll('.progressive-image');
        this.init();
    }
    
    init() {
        this.images.forEach(container => {
            this.loadProgressiveImage(container);
        });
    }
    
    loadProgressiveImage(container) {
        const placeholder = container.querySelector('.placeholder');
        const fullImage = container.querySelector('.full-image');
        
        // Load low-quality placeholder first
        if (placeholder && !placeholder.complete) {
            placeholder.onload = () => {
                placeholder.classList.add('loaded');
                this.loadFullImage(container, fullImage);
            };
        } else {
            this.loadFullImage(container, fullImage);
        }
    }
    
    loadFullImage(container, fullImage) {
        const imageLoader = new Image();
        
        imageLoader.onload = () => {
            fullImage.src = imageLoader.src;
            fullImage.onload = () => {
                fullImage.classList.add('loaded');
                container.classList.add('image-loaded');
            };
        };
        
        imageLoader.src = fullImage.dataset.src;
    }
}

// CSS for progressive loading
.progressive-image {
    position: relative;
    overflow: hidden;
}

.progressive-image .placeholder {
    filter: blur(5px);
    transform: scale(1.05);
    transition: opacity 0.3s ease;
}

.progressive-image .full-image {
    position: absolute;
    top: 0;
    left: 0;
    opacity: 0;
    transition: opacity 0.3s ease;
}

.progressive-image .full-image.loaded {
    opacity: 1;
}

.progressive-image.image-loaded .placeholder {
    opacity: 0;
}

Smart Preloading

// Smart preloading based on user behavior
class SmartImagePreloader {
    constructor() {
        this.preloadQueue = [];
        this.isPreloading = false;
        this.maxConcurrentPreloads = 2;
        this.currentPreloads = 0;
        
        this.init();
    }
    
    init() {
        // Preload images on hover (desktop)
        document.addEventListener('mouseover', (e) => {
            if (e.target.matches('[data-preload]')) {
                this.queuePreload(e.target.dataset.preload);
            }
        });
        
        // Preload images on scroll approach (mobile)
        this.setupScrollPreloading();
        
        // Preload critical images immediately
        this.preloadCriticalImages();
    }
    
    setupScrollPreloading() {
        const observer = new IntersectionObserver((entries) => {
            entries.forEach(entry => {
                if (entry.isIntersecting) {
                    const preloadSrc = entry.target.dataset.preload;
                    if (preloadSrc) {
                        this.queuePreload(preloadSrc);
                    }
                }
            });
        }, {
            rootMargin: '200px 0px'
        });
        
        document.querySelectorAll('[data-preload]').forEach(el => {
            observer.observe(el);
        });
    }
    
    preloadCriticalImages() {
        document.querySelectorAll('[data-critical]').forEach(img => {
            this.queuePreload(img.dataset.critical, true);
        });
    }
    
    queuePreload(src, priority = false) {
        if (this.isAlreadyLoaded(src)) return;
        
        if (priority) {
            this.preloadQueue.unshift(src);
        } else {
            this.preloadQueue.push(src);
        }
        
        this.processQueue();
    }
    
    processQueue() {
        if (this.currentPreloads >= this.maxConcurrentPreloads || this.preloadQueue.length === 0) {
            return;
        }
        
        const src = this.preloadQueue.shift();
        this.preloadImage(src);
    }
    
    preloadImage(src) {
        this.currentPreloads++;
        
        const link = document.createElement('link');
        link.rel = 'preload';
        link.as = 'image';
        link.href = src;
        
        link.onload = link.onerror = () => {
            this.currentPreloads--;
            this.processQueue();
        };
        
        document.head.appendChild(link);
    }
    
    isAlreadyLoaded(src) {
        return document.querySelector(`img[src="${src}"], link[href="${src}"]`) !== null;
    }
}

new SmartImagePreloader();

Mobile Compression Strategies

Mobile devices require specialized compression strategies that balance quality, file size, and processing requirements.

Device-Specific Compression

Device Type JPEG Quality WebP Quality AVIF Quality Max Dimensions
Low-end Mobile 60-70% 50-60% 45-55% 800x600
Mid-range Mobile 70-80% 60-70% 55-65% 1200x900
High-end Mobile 80-90% 70-80% 65-75% 1600x1200
Tablet 85-95% 75-85% 70-80% 2048x1536

Automated Compression Pipeline

#!/bin/bash
# Mobile-optimized image processing script

process_for_mobile() {
    local input_image="$1"
    local base_name=$(basename "$input_image" .jpg)
    local output_dir="mobile"
    
    mkdir -p "$output_dir"
    
    # Generate multiple sizes for different devices
    declare -A sizes=(
        ["small"]="480"
        ["medium"]="800"
        ["large"]="1200"
        ["xlarge"]="1600"
    )
    
    for size_name in "${!sizes[@]}"; do
        local width="${sizes[$size_name]}"
        
        # JPEG for compatibility
        convert "$input_image" \
            -resize "${width}x>" \
            -quality 75 \
            -strip \
            -interlace Plane \
            "$output_dir/${base_name}-${size_name}.jpg"
        
        # WebP for modern browsers
        cwebp -q 65 -resize "$width" 0 \
            "$input_image" \
            -o "$output_dir/${base_name}-${size_name}.webp"
        
        # AVIF for cutting-edge browsers
        avifenc --min 0 --max 63 \
            -a end-usage=q -a cq-level=28 \
            --resize "$width"x0 \
            "$input_image" \
            "$output_dir/${base_name}-${size_name}.avif"
    done
    
    # Generate low-quality placeholder
    convert "$input_image" \
        -resize "50x>" \
        -quality 30 \
        -blur 0x0.5 \
        "$output_dir/${base_name}-placeholder.jpg"
}

# Process all images
for img in *.jpg; do
    process_for_mobile "$img"
done

Dynamic Quality Adjustment

// Dynamic quality based on viewport and connection
class DynamicQualityManager {
    constructor() {
        this.baseQuality = 75;
        this.connection = navigator.connection;
        this.init();
    }
    
    init() {
        this.adjustQualityForConnection();
        this.adjustQualityForViewport();
        this.monitorPerformance();
    }
    
    adjustQualityForConnection() {
        if (!this.connection) return;
        
        let qualityMultiplier = 1;
        
        switch (this.connection.effectiveType) {
            case 'slow-2g':
                qualityMultiplier = 0.5;
                break;
            case '2g':
                qualityMultiplier = 0.6;
                break;
            case '3g':
                qualityMultiplier = 0.8;
                break;
            case '4g':
                qualityMultiplier = 1.0;
                break;
        }
        
        if (this.connection.saveData) {
            qualityMultiplier *= 0.7;
        }
        
        this.currentQuality = Math.round(this.baseQuality * qualityMultiplier);
        this.updateImageUrls();
    }
    
    adjustQualityForViewport() {
        const viewportWidth = window.innerWidth;
        const devicePixelRatio = window.devicePixelRatio || 1;
        
        // Reduce quality for very small screens
        if (viewportWidth < 400) {
            this.currentQuality = Math.min(this.currentQuality, 60);
        }
        
        // Increase quality for high-DPI displays
        if (devicePixelRatio > 2) {
            this.currentQuality = Math.min(this.currentQuality * 1.1, 90);
        }
    }
    
    updateImageUrls() {
        document.querySelectorAll('[data-dynamic-quality]').forEach(img => {
            const baseSrc = img.dataset.baseSrc;
            const newSrc = this.buildQualityUrl(baseSrc, this.currentQuality);
            
            if (img.src !== newSrc) {
                img.src = newSrc;
            }
        });
    }
    
    buildQualityUrl(baseSrc, quality) {
        // Assuming a URL structure like: /images/photo-800-q75.jpg
        return baseSrc.replace(/q\d+/, `q${quality}`);
    }
    
    monitorPerformance() {
        // Monitor loading times and adjust quality accordingly
        const observer = new PerformanceObserver((list) => {
            const entries = list.getEntries();
            const imageEntries = entries.filter(entry => 
                entry.initiatorType === 'img' && entry.duration > 1000
            );
            
            if (imageEntries.length > 3) {
                // Too many slow-loading images, reduce quality
                this.currentQuality = Math.max(this.currentQuality * 0.9, 40);
                this.updateImageUrls();
            }
        });
        
        observer.observe({ entryTypes: ['resource'] });
    }
}

new DynamicQualityManager();

Performance Optimization

Optimize image loading performance through caching strategies, preloading techniques, and resource prioritization.

Service Worker Image Caching

// service-worker.js
const CACHE_NAME = 'images-v1';
const MAX_CACHE_SIZE = 50 * 1024 * 1024; // 50MB

self.addEventListener('fetch', event => {
    if (event.request.destination === 'image') {
        event.respondWith(handleImageRequest(event.request));
    }
});

async function handleImageRequest(request) {
    const cache = await caches.open(CACHE_NAME);
    const cachedResponse = await cache.match(request);
    
    if (cachedResponse) {
        // Update cache in background if image is older than 1 day
        const cacheDate = new Date(cachedResponse.headers.get('date'));
        const now = new Date();
        const dayInMs = 24 * 60 * 60 * 1000;
        
        if (now - cacheDate > dayInMs) {
            fetch(request).then(response => {
                if (response.ok) {
                    cache.put(request, response.clone());
                }
            }).catch(() => {});
        }
        
        return cachedResponse;
    }
    
    try {
        const response = await fetch(request);
        
        if (response.ok) {
            // Check cache size before adding
            const cacheSize = await getCacheSize(cache);
            if (cacheSize < MAX_CACHE_SIZE) {
                cache.put(request, response.clone());
            } else {
                await cleanupCache(cache);
                cache.put(request, response.clone());
            }
        }
        
        return response;
    } catch (error) {
        // Return offline fallback image
        return cache.match('/images/offline-placeholder.jpg');
    }
}

async function getCacheSize(cache) {
    const keys = await cache.keys();
    let size = 0;
    
    for (const key of keys) {
        const response = await cache.match(key);
        if (response) {
            const blob = await response.blob();
            size += blob.size;
        }
    }
    
    return size;
}

async function cleanupCache(cache) {
    const keys = await cache.keys();
    const responses = await Promise.all(
        keys.map(key => cache.match(key))
    );
    
    // Sort by last accessed (oldest first)
    const sortedEntries = keys
        .map((key, index) => ({ key, response: responses[index] }))
        .filter(entry => entry.response)
        .sort((a, b) => {
            const dateA = new Date(a.response.headers.get('date'));
            const dateB = new Date(b.response.headers.get('date'));
            return dateA - dateB;
        });
    
    // Remove oldest 25% of entries
    const toRemove = sortedEntries.slice(0, Math.floor(sortedEntries.length * 0.25));
    await Promise.all(toRemove.map(entry => cache.delete(entry.key)));
}

Critical Resource Prioritization

<!-- Prioritize critical images -->
<link rel="preload" as="image" href="hero-image.webp" fetchpriority="high">
<link rel="preload" as="image" href="logo.svg" fetchpriority="high">

<!-- Defer non-critical images -->
<img src="product-image.jpg" alt="Product" fetchpriority="low" loading="lazy">

<script>
// Dynamic priority adjustment
class ImagePriorityManager {
    constructor() {
        this.criticalImages = new Set();
        this.init();
    }
    
    init() {
        this.identifyCriticalImages();
        this.adjustPriorities();
        this.monitorViewport();
    }
    
    identifyCriticalImages() {
        // Images above the fold are critical
        const viewportHeight = window.innerHeight;
        
        document.querySelectorAll('img').forEach(img => {
            const rect = img.getBoundingClientRect();
            if (rect.top < viewportHeight) {
                this.criticalImages.add(img);
                img.setAttribute('fetchpriority', 'high');
            }
        });
    }
    
    adjustPriorities() {
        // Reduce priority for images that are not visible
        const observer = new IntersectionObserver((entries) => {
            entries.forEach(entry => {
                if (entry.isIntersecting) {
                    entry.target.setAttribute('fetchpriority', 'high');
                } else {
                    entry.target.setAttribute('fetchpriority', 'low');
                }
            });
        }, {
            rootMargin: '100px 0px'
        });
        
        document.querySelectorAll('img[loading="lazy"]').forEach(img => {
            observer.observe(img);
        });
    }
    
    monitorViewport() {
        // Adjust priorities on orientation change
        window.addEventListener('orientationchange', () => {
            setTimeout(() => {
                this.identifyCriticalImages();
            }, 100);
        });
    }
}

new ImagePriorityManager();
</script>

Performance Metrics

Metric Target (Mobile) Measurement Method Optimization Impact
Largest Contentful Paint (LCP) < 2.5s Web Vitals API Critical image optimization
First Contentful Paint (FCP) < 1.8s Performance API Above-fold image priority
Cumulative Layout Shift (CLS) < 0.1 Layout Shift API Image dimensions specification
Total Blocking Time (TBT) < 200ms Lighthouse Lazy loading implementation

Testing & Monitoring

Comprehensive testing and monitoring ensure your mobile image optimization strategies deliver real-world performance improvements.

Mobile Testing Checklist

📱 Device Testing
  • Test on actual devices, not just emulators
  • Include low-end devices (2GB RAM or less)
  • Test both portrait and landscape orientations
  • Verify touch interactions work properly
  • Check image quality on high-DPI screens
🌐 Network Testing
  • Test on 3G, 4G, and WiFi connections
  • Simulate slow and unstable connections
  • Enable data saver mode testing
  • Test offline scenarios
  • Verify progressive loading works

Automated Testing Script

// Mobile image optimization test suite
const puppeteer = require('puppeteer');

class MobileImageTester {
    constructor() {
        this.devices = [
            'iPhone 12',
            'Pixel 5',
            'Galaxy S21',
            'iPad'
        ];
        
        this.networkConditions = [
            { name: '3G', downloadThroughput: 1.5 * 1024 * 1024 / 8, uploadThroughput: 750 * 1024 / 8, latency: 40 },
            { name: '4G', downloadThroughput: 4 * 1024 * 1024 / 8, uploadThroughput: 3 * 1024 * 1024 / 8, latency: 20 }
        ];
    }
    
    async runTests(url) {
        const browser = await puppeteer.launch();
        const results = [];
        
        for (const deviceName of this.devices) {
            for (const network of this.networkConditions) {
                const result = await this.testDeviceNetwork(browser, url, deviceName, network);
                results.push(result);
            }
        }
        
        await browser.close();
        return results;
    }
    
    async testDeviceNetwork(browser, url, deviceName, network) {
        const page = await browser.newPage();
        
        // Emulate device
        await page.emulate(puppeteer.devices[deviceName]);
        
        // Set network conditions
        await page.emulateNetworkConditions(network);
        
        // Start performance monitoring
        await page.tracing.start({ screenshots: true, categories: ['devtools.timeline'] });
        
        const startTime = Date.now();
        await page.goto(url, { waitUntil: 'networkidle0' });
        const loadTime = Date.now() - startTime;
        
        // Measure image metrics
        const metrics = await page.evaluate(() => {
            const images = Array.from(document.querySelectorAll('img'));
            
            return {
                totalImages: images.length,
                lazyImages: images.filter(img => img.loading === 'lazy').length,
                imagesWithSrcset: images.filter(img => img.srcset).length,
                imagesWithDimensions: images.filter(img => img.width && img.height).length,
                webpImages: images.filter(img => img.src.includes('.webp')).length,
                avifImages: images.filter(img => img.src.includes('.avif')).length
            };
        });
        
        // Get Web Vitals
        const vitals = await page.evaluate(() => {
            return new Promise((resolve) => {
                new PerformanceObserver((list) => {
                    const entries = list.getEntries();
                    const lcp = entries.find(entry => entry.entryType === 'largest-contentful-paint');
                    const cls = entries.filter(entry => entry.entryType === 'layout-shift')
                        .reduce((sum, entry) => sum + entry.value, 0);
                    
                    resolve({ lcp: lcp?.startTime, cls });
                }).observe({ entryTypes: ['largest-contentful-paint', 'layout-shift'] });
                
                setTimeout(() => resolve({ lcp: null, cls: null }), 5000);
            });
        });
        
        await page.tracing.stop();
        await page.close();
        
        return {
            device: deviceName,
            network: network.name,
            loadTime,
            metrics,
            vitals
        };
    }
}

// Usage
const tester = new MobileImageTester();
tester.runTests('https://example.com').then(results => {
    console.log('Mobile Image Test Results:', results);
});

Real User Monitoring

// Real User Monitoring for mobile images
class MobileImageMonitor {
    constructor() {
        this.metrics = {
            imageLoadTimes: [],
            failedImages: [],
            formatUsage: {},
            deviceTypes: {}
        };
        
        this.init();
    }
    
    init() {
        this.monitorImageLoading();
        this.trackDeviceInfo();
        this.monitorWebVitals();
        this.sendMetricsPeriodically();
    }
    
    monitorImageLoading() {
        const observer = new PerformanceObserver((list) => {
            list.getEntries().forEach(entry => {
                if (entry.initiatorType === 'img') {
                    this.metrics.imageLoadTimes.push({
                        url: entry.name,
                        duration: entry.duration,
                        size: entry.transferSize,
                        timestamp: Date.now()
                    });
                    
                    // Track format usage
                    const format = this.getImageFormat(entry.name);
                    this.metrics.formatUsage[format] = (this.metrics.formatUsage[format] || 0) + 1;
                }
            });
        });
        
        observer.observe({ entryTypes: ['resource'] });
        
        // Monitor failed image loads
        document.addEventListener('error', (e) => {
            if (e.target.tagName === 'IMG') {
                this.metrics.failedImages.push({
                    src: e.target.src,
                    timestamp: Date.now()
                });
            }
        }, true);
    }
    
    trackDeviceInfo() {
        const deviceInfo = {
            userAgent: navigator.userAgent,
            screenWidth: screen.width,
            screenHeight: screen.height,
            devicePixelRatio: window.devicePixelRatio,
            connection: navigator.connection ? {
                effectiveType: navigator.connection.effectiveType,
                downlink: navigator.connection.downlink,
                saveData: navigator.connection.saveData
            } : null,
            memory: navigator.deviceMemory
        };
        
        this.metrics.deviceTypes[this.getDeviceCategory(deviceInfo)] = deviceInfo;
    }
    
    getDeviceCategory(deviceInfo) {
        const width = deviceInfo.screenWidth;
        if (width < 768) return 'mobile';
        if (width < 1024) return 'tablet';
        return 'desktop';
    }
    
    getImageFormat(url) {
        if (url.includes('.webp')) return 'webp';
        if (url.includes('.avif')) return 'avif';
        if (url.includes('.jpg') || url.includes('.jpeg')) return 'jpeg';
        if (url.includes('.png')) return 'png';
        return 'other';
    }
    
    monitorWebVitals() {
        new PerformanceObserver((list) => {
            list.getEntries().forEach(entry => {
                if (entry.entryType === 'largest-contentful-paint') {
                    this.metrics.lcp = entry.startTime;
                }
            });
        }).observe({ entryTypes: ['largest-contentful-paint'] });
        
        new PerformanceObserver((list) => {
            let cls = 0;
            list.getEntries().forEach(entry => {
                if (!entry.hadRecentInput) {
                    cls += entry.value;
                }
            });
            this.metrics.cls = cls;
        }).observe({ entryTypes: ['layout-shift'] });
    }
    
    sendMetricsPeriodically() {
        setInterval(() => {
            this.sendMetrics();
        }, 30000); // Send every 30 seconds
        
        // Send on page unload
        window.addEventListener('beforeunload', () => {
            this.sendMetrics(true);
        });
    }
    
    sendMetrics(isBeacon = false) {
        const payload = JSON.stringify({
            ...this.metrics,
            timestamp: Date.now(),
            url: window.location.href
        });
        
        if (isBeacon && navigator.sendBeacon) {
            navigator.sendBeacon('/api/mobile-image-metrics', payload);
        } else {
            fetch('/api/mobile-image-metrics', {
                method: 'POST',
                headers: { 'Content-Type': 'application/json' },
                body: payload
            }).catch(() => {}); // Ignore errors
        }
        
        // Reset metrics after sending
        this.metrics.imageLoadTimes = [];
        this.metrics.failedImages = [];
    }
}

// Initialize monitoring
if ('serviceWorker' in navigator) {
    new MobileImageMonitor();
}

Future Mobile Techniques

Emerging technologies and techniques will further revolutionize mobile image optimization in the coming years.

AI-Powered Optimization

🤖 Machine Learning
  • Content-aware compression: AI analyzes image content for optimal compression
  • Predictive loading: ML predicts which images users will view
  • Adaptive quality: Real-time quality adjustment based on user behavior
  • Smart cropping: AI-powered art direction for different screen sizes
🔮 Neural Networks
  • Super-resolution: Enhance low-res images on-device
  • Style transfer: Adapt images to user preferences
  • Noise reduction: AI-powered image enhancement
  • Format selection: Neural networks choose optimal formats

5G and Edge Computing

// Edge computing image optimization
class EdgeImageOptimizer {
    constructor() {
        this.edgeEndpoint = this.detectEdgeEndpoint();
        this.capabilities = this.detectDeviceCapabilities();
    }
    
    detectEdgeEndpoint() {
        // Detect nearest edge server based on geolocation
        return navigator.geolocation ? 
            this.getNearestEdgeServer() : 
            'https://global-edge.example.com';
    }
    
    async getNearestEdgeServer() {
        return new Promise((resolve) => {
            navigator.geolocation.getCurrentPosition(
                (position) => {
                    const { latitude, longitude } = position.coords;
                    resolve(`https://edge-${this.getRegion(latitude, longitude)}.example.com`);
                },
                () => resolve('https://global-edge.example.com')
            );
        });
    }
    
    getRegion(lat, lng) {
        // Simple region detection based on coordinates
        if (lat > 45 && lng < -60) return 'na'; // North America
        if (lat > 35 && lng > -10 && lng < 40) return 'eu'; // Europe
        if (lat > 10 && lng > 100) return 'ap'; // Asia Pacific
        return 'global';
    }
    
    async optimizeImage(src, options = {}) {
        const optimizationParams = {
            ...this.capabilities,
            ...options,
            src: encodeURIComponent(src)
        };
        
        const queryString = new URLSearchParams(optimizationParams).toString();
        return `${this.edgeEndpoint}/optimize?${queryString}`;
    }
    
    detectDeviceCapabilities() {
        return {
            width: window.innerWidth,
            height: window.innerHeight,
            dpr: window.devicePixelRatio || 1,
            connection: navigator.connection?.effectiveType || '4g',
            memory: navigator.deviceMemory || 4,
            saveData: navigator.connection?.saveData || false
        };
    }
}

// Usage
const edgeOptimizer = new EdgeImageOptimizer();
document.querySelectorAll('img[data-edge-optimize]').forEach(async img => {
    const optimizedSrc = await edgeOptimizer.optimizeImage(img.dataset.src);
    img.src = optimizedSrc;
});

WebAssembly Image Processing

// Client-side image processing with WebAssembly
class WasmImageProcessor {
    constructor() {
        this.wasmModule = null;
        this.init();
    }
    
    async init() {
        try {
            this.wasmModule = await import('./image-processor.wasm');
            await this.wasmModule.default();
        } catch (error) {
            console.warn('WebAssembly not supported, falling back to canvas');
        }
    }
    
    async processImage(imageData, options) {
        if (this.wasmModule) {
            return this.processWithWasm(imageData, options);
        } else {
            return this.processWithCanvas(imageData, options);
        }
    }
    
    processWithWasm(imageData, options) {
        // Use WebAssembly for high-performance image processing
        const { width, height, data } = imageData;
        const processed = this.wasmModule.process_image(
            data, width, height, options.quality || 75
        );
        
        return new ImageData(processed, width, height);
    }
    
    processWithCanvas(imageData, options) {
        // Fallback canvas processing
        const canvas = document.createElement('canvas');
        const ctx = canvas.getContext('2d');
        
        canvas.width = imageData.width;
        canvas.height = imageData.height;
        ctx.putImageData(imageData, 0, 0);
        
        // Apply basic optimizations
        if (options.blur) {
            ctx.filter = `blur(${options.blur}px)`;
            ctx.drawImage(canvas, 0, 0);
        }
        
        return ctx.getImageData(0, 0, canvas.width, canvas.height);
    }
}

const wasmProcessor = new WasmImageProcessor();

Conclusion

Mobile image optimization in 2025 requires a comprehensive approach combining responsive images, adaptive serving, intelligent lazy loading, and performance monitoring. The key is balancing image quality with loading speed while considering the diverse landscape of mobile devices and network conditions.

Start with the fundamentals: implement responsive images with proper srcset and sizes attributes, use modern formats like WebP and AVIF with fallbacks, and implement effective lazy loading. Then enhance with adaptive serving based on device capabilities and network conditions.

As mobile devices become more powerful and 5G networks expand, new opportunities for AI-powered optimization and edge computing will emerge. Stay ahead by monitoring performance metrics, testing on real devices, and gradually adopting new technologies as they mature.

Remember: the best mobile image optimization strategy is one that delivers the right image, in the right format, at the right quality, to the right device, at the right time.