Responsive Images (srcset, picture)
Serve the perfect image for every device and screen resolution. Master srcset, sizes, and the picture element for optimal performance and visual quality.
The Responsive Image Problem
Traditional <img src="..."> serves the same image to all devices - wasting bandwidth on mobile and delivering low-quality images on high-DPI displays.
4x
data wasted serving desktop images to mobile
2-3x
pixel density on modern screens (Retina/HDPI)
60%
of web traffic is mobile (2024)
⌠Problem: One Size For All
<img src="image-4000px.jpg" alt="Photo">
<!-- Issues: -->
<!-- - Mobile downloads 4MB file for 400px display -->
<!-- - High-DPI screens show blurry image -->
<!-- - Waste of bandwidth and loading time -->✅ Solution: Responsive Images
<img src="image-400.jpg"
srcset="image-400.jpg 400w,
image-800.jpg 800w,
image-1600.jpg 1600w"
sizes="(max-width: 600px) 400px, 800px"
alt="Photo">
<!-- Browser downloads the right size! -->The srcset Attribute
srcset provides multiple image sources. Browser chooses based on device capabilities.
1. Resolution Switching (w descriptor)
<img src="photo-400.jpg"
srcset="photo-400.jpg 400w,
photo-800.jpg 800w,
photo-1200.jpg 1200w,
photo-1600.jpg 1600w"
sizes="100vw"
alt="Landscape photo"
width="1600"
height="900">
<!-- How it works: -->
<!-- - w = actual width of image file in pixels -->
<!-- - 400w means image is 400 pixels wide -->
<!-- - Browser picks based on screen size & DPI -->Understanding 'w' Descriptor:
400w= "This image file is 400 pixels wide"- NOT a media query
- Browser calculates: needed_width / dpr
- Then selects closest match from srcset
Real-World Example:
Device: iPhone with 375px viewport, 3x DPR
Calculation: 375px × 3 = 1125px needed
Browser selects: photo-1200.jpg (closest larger)
2. Pixel Density Switching (x descriptor)
<img src="icon.png"
srcset="icon-1x.png 1x,
icon-2x.png 2x,
icon-3x.png 3x"
alt="Icon"
width="100"
height="100">
<!-- Use for: -->
<!-- - Fixed-size images (icons, logos) -->
<!-- - When you know exact display size -->
<!-- Examples: -->
<!-- 1x = Standard displays (72-96 DPI) -->
<!-- 2x = Retina/HD displays (144-192 DPI) -->
<!-- 3x = Super Retina (216+ DPI) -->The sizes Attribute
sizes tells the browser how much space the image will take in the layout.
<img src="photo.jpg"
srcset="photo-400.jpg 400w,
photo-800.jpg 800w,
photo-1200.jpg 1200w"
sizes="(max-width: 600px) 400px,
(max-width: 1000px) 800px,
1200px"
alt="Photo">
<!-- Reads as: -->
<!-- "If viewport ≤ 600px, image will be 400px wide" -->
<!-- "If viewport ≤ 1000px, image will be 800px wide" -->
<!-- "Otherwise, image will be 1200px wide" -->Common sizes Patterns:
<img src="hero.jpg"
srcset="hero-800.jpg 800w, hero-1600.jpg 1600w"
sizes="100vw"
alt="Hero image"><img src="photo.jpg"
srcset="photo-400.jpg 400w, photo-800.jpg 800w"
sizes="(min-width: 800px) 50vw, 100vw"
alt="Photo"><img src="thumbnail.jpg"
srcset="thumb-200.jpg 200w, thumb-400.jpg 400w"
sizes="(min-width: 1200px) 25vw,
(min-width: 800px) 33vw,
(min-width: 500px) 50vw,
100vw"
alt="Thumbnail">
<!-- 4 columns on large screens (25%) -->
<!-- 3 columns on medium screens (33%) -->
<!-- 2 columns on small screens (50%) -->
<!-- 1 column on mobile (100%) --><img src="content.jpg"
srcset="content-600.jpg 600w, content-800.jpg 800w"
sizes="(min-width: 1000px) 800px, 90vw"
alt="Content image">
<!-- Fixed 800px on wide screens -->
<!-- 90% of viewport on smaller screens -->The <picture> Element
<picture> provides full control: different images for different scenarios.
1. Art Direction
Different crops/compositions for different screen sizes:
<picture>
<!-- Wide landscape for desktop -->
<source media="(min-width: 1000px)"
srcset="hero-wide.jpg">
<!-- Square crop for tablets -->
<source media="(min-width: 600px)"
srcset="hero-square.jpg">
<!-- Vertical crop for mobile (portrait-focused) -->
<img src="hero-vertical.jpg"
alt="Hero image">
</picture>
<!-- Use when: -->
<!-- - Focal point changes (portrait vs landscape) -->
<!-- - Different aspect ratios needed -->
<!-- - Important content must stay visible -->2. Format Switching
Serve modern formats with fallbacks:
<picture>
<!-- Try AVIF first (best compression) -->
<source type="image/avif"
srcset="photo.avif">
<!-- Try WebP (good compression) -->
<source type="image/webp"
srcset="photo.webp">
<!-- Fallback to JPEG (universal) -->
<img src="photo.jpg"
alt="Photo">
</picture>3. Combining Everything
<picture>
<!-- Desktop: AVIF, multiple resolutions -->
<source media="(min-width: 1000px)"
type="image/avif"
srcset="desktop-800.avif 800w,
desktop-1600.avif 1600w"
sizes="1000px">
<!-- Desktop: WebP fallback -->
<source media="(min-width: 1000px)"
type="image/webp"
srcset="desktop-800.webp 800w,
desktop-1600.webp 1600w"
sizes="1000px">
<!-- Mobile: AVIF, smaller images -->
<source type="image/avif"
srcset="mobile-400.avif 400w,
mobile-800.avif 800w"
sizes="100vw">
<!-- Mobile: WebP fallback -->
<source type="image/webp"
srcset="mobile-400.webp 400w,
mobile-800.webp 800w"
sizes="100vw">
<!-- Universal fallback -->
<img src="mobile-400.jpg"
srcset="mobile-400.jpg 400w,
mobile-800.jpg 800w"
sizes="100vw"
alt="Photo"
width="800"
height="600"
loading="lazy">
</picture>Loading Strategies
1. Lazy Loading
<!-- Lazy load below-the-fold images -->
<img src="photo.jpg"
srcset="photo-400.jpg 400w, photo-800.jpg 800w"
sizes="100vw"
loading="lazy"
alt="Photo">
<!-- loading attribute: -->
<!-- - lazy: Load when near viewport (default for most browsers) -->
<!-- - eager: Load immediately -->
<!-- - auto: Browser decides -->2. Preload Critical Images
<head>
<!-- Preload hero image for instant display -->
<link rel="preload"
as="image"
href="hero.jpg"
imagesrcset="hero-800.jpg 800w, hero-1600.jpg 1600w"
imagesizes="100vw">
</head>
<body>
<img src="hero.jpg"
srcset="hero-800.jpg 800w, hero-1600.jpg 1600w"
sizes="100vw"
alt="Hero"
loading="eager">
</body>3. Decoding Strategy
<!-- Async decoding (don't block rendering) -->
<img src="large-photo.jpg"
decoding="async"
alt="Photo">
<!-- sync: Block rendering until decoded -->
<!-- async: Decode asynchronously (recommended for large images) -->
<!-- auto: Browser decides -->Practical Examples
Blog Post Featured Image
<picture>
<source type="image/webp"
srcset="featured-400.webp 400w,
featured-800.webp 800w,
featured-1200.webp 1200w"
sizes="(min-width: 1000px) 800px, 90vw">
<img src="featured-800.jpg"
srcset="featured-400.jpg 400w,
featured-800.jpg 800w,
featured-1200.jpg 1200w"
sizes="(min-width: 1000px) 800px, 90vw"
alt="Article featured image"
width="1200"
height="675"
loading="lazy">
</picture>Product Thumbnail Grid
<img src="product-thumb.jpg"
srcset="product-thumb-200.jpg 200w,
product-thumb-400.jpg 400w"
sizes="(min-width: 1200px) 20vw,
(min-width: 800px) 25vw,
(min-width: 500px) 33vw,
50vw"
alt="Product name"
width="400"
height="400"
loading="lazy">Hero Banner with Art Direction
<picture>
<!-- Desktop: wide 21:9 crop -->
<source media="(min-width: 1200px)"
type="image/webp"
srcset="hero-wide-1920.webp 1920w,
hero-wide-2560.webp 2560w"
sizes="100vw">
<!-- Tablet: 16:9 crop -->
<source media="(min-width: 768px)"
type="image/webp"
srcset="hero-tablet-1024.webp 1024w,
hero-tablet-1536.webp 1536w"
sizes="100vw">
<!-- Mobile: 4:5 portrait crop -->
<img src="hero-mobile-750.jpg"
srcset="hero-mobile-750.jpg 750w,
hero-mobile-1125.jpg 1125w"
sizes="100vw"
alt="Hero image"
width="750"
height="937"
loading="eager">
</picture>Best Practices
✅ Do:
- Always specify
widthandheight(prevents layout shift) - Use
loading="lazy"for below-the-fold images - Provide at least 3-4 size variants (small, medium, large, xlarge)
- Use WebP/AVIF with JPEG/PNG fallback
- Set
sizesto match your actual layout - Compress all images before upload
- Use CDN for automatic image optimization
- Test on real devices with throttled connection
⌠Don't:
- Forget the fallback
srcattribute - Use srcset without sizes (except for x descriptor)
- Provide only 1-2 image sizes (too limited)
- Set sizes to incorrect values
- Lazy load above-the-fold images
- Skip width/height attributes
- Serve unoptimized original images
Testing & Debugging
- Chrome DevTools: Network tab shows which image was downloaded
- Responsive Mode: Test different viewports and DPR
- Lighthouse: Audits image sizing and format
- Real Devices: Test on actual mobile/tablet
- Network Throttling: Simulate slow connections
// Check which image was loaded
const img = document.querySelector('img');
console.log('Current src:', img.currentSrc);
console.log('Natural size:', img.naturalWidth, 'x', img.naturalHeight);
// Check device pixel ratio
console.log('Device pixel ratio:', window.devicePixelRatio);Automation Tools
- Cloudinary: Automatic responsive images via URL parameters
- imgix: Real-time image processing and optimization
- Sharp (Node.js): Generate multiple sizes programmatically
- Responsive Images Breakpoints Generator: Calculate optimal breakpoints
- Webpack/Vite plugins: Automatic image optimization in build