Introduction
Responsive web design has solved many problems related to building websites that work across devices—fluid grids adapt our layouts, media queries adjust our styles, and flexible images scale to fit their containers. But there's one aspect of responsive design that remains frustratingly unsolved: the responsive images problem.
The issue is straightforward: responsive websites need images that adapt not just visually but also technically. When you set max-width: 100% on an image, it scales down beautifully on mobile devices, but there's a hidden problem—that mobile device still downloaded the full-size desktop image. A user on a smartphone with a 320-pixel-wide screen doesn't need your 1200-pixel-wide hero image, yet they're downloading it anyway, wasting bandwidth and slowing down page load times.
This matters enormously. Mobile users are often on slower cellular connections with limited data plans. Making them download desktop-sized images is wasteful and creates poor user experiences. Yet we can't simply serve small images to everyone—desktop users need high-resolution images that look good on large displays, and with the new Retina displays on devices like the iPhone 4S and the new iPad, we need to consider serving even higher resolution images to devices with high pixel densities.
The web development community is actively working on this problem, with several competing approaches and a heated debate happening right now in the standards bodies. There's no perfect solution yet, but there are practical techniques you can use today to serve appropriate images to different devices.
The Responsive Images Problem
Let's clearly define what we're trying to solve. In responsive design, we typically handle images like this:
img {
max-width: 100%;
height: auto;
}
This makes images flexible—they scale down on small screens and scale up on large screens. The problem is that the HTML still looks like this:
<img src="large-image.jpg" alt="Description">
Every device downloads large-image.jpg, regardless of whether it's a smartphone with a 320px screen or a desktop with a 1920px screen. The small-screen device wastes bandwidth downloading pixels it will never display.
The Core Challenges
There are actually several interrelated problems:
1. Bandwidth and File Size
Mobile devices often have slower connections and limited data plans. Downloading large images wastes bandwidth, costs users money, and creates slower experiences.
2. Screen Resolution
Different devices have different screen resolutions. A smartphone might be 320px wide, a tablet 768px, and a desktop 1280px or larger. Ideally, we'd serve appropriately sized images for each.
3. Device Pixel Ratio
The new Retina displays on the iPhone 4S and iPad have twice the pixel density of standard displays. These devices need higher-resolution images to look sharp, but standard displays don't need those extra pixels.
4. Art Direction
Sometimes you don't just want different sizes of the same image—you want different crops or compositions for different screen sizes. A wide landscape photo might work on desktop but need a tighter crop to remain readable on mobile.
Why This Is Hard
You might think: "Just use JavaScript to detect screen size and load the appropriate image." The problem is timing. Browsers start downloading images as soon as they encounter the <img> tag in the HTML, long before JavaScript executes. By the time your JavaScript can run and determine the screen size, the browser has already started downloading the original image.
Any JavaScript-based solution needs to work around this fundamental browser behavior, and most workarounds involve some degree of hackery.
CSS-Based Approaches
The simplest responsive image techniques use CSS, specifically background images with media queries. While this doesn't solve all use cases, it works well for decorative images and certain design elements.
Basic Background Image Switching
You can use media queries to serve different background images at different screen sizes:
/* Mobile: small image */
.hero {
background-image: url('hero-small.jpg');
background-size: cover;
background-position: center;
height: 200px;
}
/* Tablet: medium image */
@media screen and (min-width: 481px) {
.hero {
background-image: url('hero-medium.jpg');
height: 300px;
}
}
/* Desktop: large image */
@media screen and (min-width: 769px) {
.hero {
background-image: url('hero-large.jpg');
height: 400px;
}
}
/* Retina displays: high-res images */
@media screen and (min-width: 769px) and (-webkit-min-device-pixel-ratio: 2) {
.hero {
background-image: url('[email protected]');
}
}
The browser only downloads the background image specified in the matching media query, so mobile devices get the small image and desktop users get the large one.
The HTML Structure
<div class="hero">
<div class="hero-content">
<h1>Welcome to Our Site</h1>
<p>Discover something amazing</p>
</div>
</div>
Advantages
- Actually responsive: Only the appropriate image is downloaded
- Retina-ready: Easy to target high-pixel-density displays
- Art direction: Can use completely different images at different sizes
- Progressive enhancement: Works across all browsers
Limitations
- Only for background images: Won't work for content images that need to be in the HTML
- No semantic meaning: Background images aren't part of the document structure
- Not accessible: Screen readers don't announce background images
- Extra markup: Need container divs instead of simple
<img>tags
This technique works well for hero images, decorative elements, and design flourishes, but it's not suitable for content images that convey meaning or information.
Handling Retina Displays
The media query for detecting Retina displays is vendor-prefixed and somewhat fragile, but it works:
/* Standard resolution */
.logo {
background-image: url('logo.png');
width: 200px;
height: 100px;
}
/* Retina displays */
@media (-webkit-min-device-pixel-ratio: 2),
(min-resolution: 192dpi) {
.logo {
background-image: url('[email protected]');
background-size: 200px 100px;
}
}
The @2x image is twice as large (400×200 pixels) but displayed at the same size using background-size. This ensures the logo looks sharp on Retina displays.
JavaScript Solutions
For content images that need to be in the HTML with proper <img> tags, we need JavaScript. Several techniques have emerged, each with different tradeoffs.
The Filament Group Technique
One of the most influential approaches comes from Scott Jehl and the Filament Group. Their technique, released in late 2011, uses a combination of JavaScript and server-side logic.
The basic approach:
1. Start with small images in HTML:
<img src="images/small/photo.jpg"
data-fullsrc="images/large/photo.jpg"
alt="Description">
The src points to a mobile-optimized image. The data-fullsrc attribute stores the path to the larger version.
2. Detect screen size with JavaScript:
(function() {
// Create cookie on first visit
if (!document.cookie.match(/resolution/)) {
// Get screen width and pixel ratio
var width = window.screen.width;
var ratio = window.devicePixelRatio || 1;
// Set cookie with screen resolution
document.cookie = 'resolution=' + (width * ratio) + '; path=/';
}
})();
This script runs immediately and sets a cookie with the device's resolution.
3. Insert a BASE element to redirect image requests:
(function() {
var head = document.getElementsByTagName('head')[0];
var base = document.createElement('base');
base.href = '/rwd-router/';
head.insertBefore(base, head.firstChild);
})();
This temporarily redirects all image requests through a routing path.
4. Server-side routing (via .htaccess):
# Redirect responsive images to 1px gif initially
RewriteEngine On
RewriteCond %{REQUEST_URI} ^/rwd-router/
RewriteRule ^(.*)$ /images/rwd.gif [L]
Images initially load as a 1px transparent gif placeholder.
5. After page load, replace with appropriate images:
window.addEventListener('load', function() {
// Remove the BASE element
var base = document.getElementsByTagName('base')[0];
if (base) {
base.parentNode.removeChild(base);
}
// Get resolution from cookie
var resolution = document.cookie.match(/resolution=(\d+)/);
var screenWidth = resolution ? resolution[1] : 0;
// Find all responsive images
var images = document.getElementsByTagName('img');
for (var i = 0; i < images.length; i++) {
var img = images[i];
var fullsrc = img.getAttribute('data-fullsrc');
if (fullsrc) {
// Use large image if screen is wide enough
if (screenWidth > 480) {
img.src = fullsrc;
}
// Otherwise stick with the small image already in src
}
}
});
How It Works
The genius of this approach is that it works around the browser's eager image loading:
- Small images are in the HTML
srcby default (mobile-first) - The BASE element temporarily redirects images to a tiny placeholder
- After JavaScript determines screen size, it removes the BASE and sets appropriate image sources
- Mobile devices get small images, desktops get large ones
- If JavaScript fails, users still get the small image
Implementation Example
Here's a simplified version you can implement:
HTML:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Responsive Images</title>
<!-- Resolution detection script (inline, runs immediately) -->
<script>
(function() {
if (!document.cookie.match(/resolution/)) {
var width = screen.width;
var ratio = window.devicePixelRatio || 1;
document.cookie = 'resolution=' + (width * ratio);
}
})();
</script>
</head>
<body>
<h1>Responsive Image Example</h1>
<img src="images/small/beach.jpg"
data-fullsrc="images/large/beach.jpg"
alt="Beach sunset">
<img src="images/small/mountains.jpg"
data-fullsrc="images/large/mountains.jpg"
alt="Mountain landscape">
<script src="responsive-images.js"></script>
</body>
</html>
responsive-images.js:
(function() {
'use strict';
// Configuration
var config = {
breakpoint: 480, // Switch to large images above this width
retinaBreakpoint: 960 // For Retina displays, double the breakpoint
};
// Get resolution from cookie
function getResolution() {
var match = document.cookie.match(/resolution=(\d+)/);
return match ? parseInt(match[1], 10) : 0;
}
// Check if we should use large images
function shouldUseLargeImages(resolution) {
var devicePixelRatio = window.devicePixelRatio || 1;
var breakpoint = devicePixelRatio > 1 ?
config.retinaBreakpoint :
config.breakpoint;
return resolution > breakpoint;
}
// Replace image sources
function replaceImageSources() {
var resolution = getResolution();
if (!resolution || !shouldUseLargeImages(resolution)) {
// Stick with small images
return;
}
// Find all images with data-fullsrc
var images = document.querySelectorAll('img[data-fullsrc]');
for (var i = 0; i < images.length; i++) {
var img = images[i];
var fullsrc = img.getAttribute('data-fullsrc');
if (fullsrc) {
// Create a new image to preload
var newImg = new Image();
// Closure to capture the current img element
(function(imgElement, src) {
newImg.onload = function() {
imgElement.src = src;
};
})(img, fullsrc);
newImg.src = fullsrc;
}
}
}
// Run after DOM is ready
if (document.readyState === 'complete') {
replaceImageSources();
} else {
window.addEventListener('load', replaceImageSources);
}
})();
Advantages
- Works with standard
<img>tags - Mobile-first: defaults to small images
- Graceful degradation: if JavaScript fails, users get small images
- Considers both screen size and pixel ratio
Disadvantages
- Requires JavaScript
- Some complexity in implementation
- Brief flash as images swap (can be mitigated with CSS opacity transitions)
- Doesn't prevent downloading both images on orientation change or resize
The Debate Over srcset
Right now, there's an active and sometimes heated debate in the web standards community about how to solve responsive images at the specification level. Two main proposals are competing for attention.
The Picture Element Proposal
The W3C Responsive Images Community Group, led by developers from the community, has proposed a new <picture> element that works similarly to the HTML5 <video> element:
<picture>
<source src="images/large.jpg" media="(min-width: 768px)">
<source src="images/medium.jpg" media="(min-width: 480px)">
<source src="images/small.jpg">
<!-- Fallback for browsers that don't support picture -->
<img src="images/small.jpg" alt="Description">
</picture>
The browser would evaluate the media queries and load the appropriate source. This approach is familiar to developers, offers full control, and handles art direction use cases.
The srcset Attribute Proposal
In May 2012, just last month, Apple's Theresa O'Connor proposed an alternative: a new srcset attribute that extends the existing <img> tag:
<img src="fallback.jpg"
alt="Description"
srcset="small.jpg 320w,
medium.jpg 768w,
large.jpg 1024w">
The srcset attribute provides a list of image sources with their widths. The browser automatically chooses the most appropriate image based on the device's screen size and pixel density.
The Controversy
The srcset proposal was added to the WHATWG HTML specification very quickly, which upset many in the developer community who felt the <picture> element was being dismissed without proper consideration. There's currently vigorous debate about which approach is better.
Arguments for <picture>:
- More explicit control over which images load
- Easier for developers to understand (uses familiar media query syntax)
- Better handles art direction (different crops at different sizes)
- More flexible for complex scenarios
Arguments for srcset:
- Simpler syntax
- Extends existing
<img>element rather than adding new markup - Lets browsers be smart about image selection (could consider bandwidth, user preferences)
- Easier for browser vendors to implement
The likely outcome: Some combination of both approaches. There's talk of merging the ideas—perhaps using srcset for simple resolution switching and <picture> for art direction cases.
What This Means for Developers
These proposals aren't implemented in browsers yet. They're specifications being debated. It will likely be a year or more before we see browser support, and longer before support is widespread enough for production use.
For now, we need to use JavaScript-based solutions or CSS techniques. But it's encouraging that the standards bodies are taking this problem seriously, and we'll likely have a native solution in the future.
Practical Implementation Strategies
Given the current state of things, what should you actually do on your projects today?
Strategy 1: CSS for Decorative Images
Use media queries with background images for hero images, banners, and decorative elements:
.banner {
background-image: url('banner-small.jpg');
background-size: cover;
height: 200px;
}
@media screen and (min-width: 769px) {
.banner {
background-image: url('banner-large.jpg');
height: 400px;
}
}
This works well and doesn't require JavaScript.
Strategy 2: JavaScript for Content Images
For content images that need to be in HTML, use a JavaScript solution like the Filament Group technique:
- Start with small images in
src - Store large image paths in
data-fullsrc - Use JavaScript to swap sources based on screen size
- Set cookies to remember resolution for future page loads
Strategy 3: Server-Side Detection
For projects where you control the server, consider server-side device detection:
<?php
// Simplified example
$width = 'small';
if (isset($_COOKIE['resolution'])) {
$resolution = $_COOKIE['resolution'];
if ($resolution > 768) {
$width = 'large';
} elseif ($resolution > 480) {
$width = 'medium';
}
}
echo '<img src="images/' . $width . '/photo.jpg" alt="Photo">';
?>
This requires server-side logic but can be more reliable than pure JavaScript approaches.
Strategy 4: Accept Some Redundancy
For simpler projects, you might accept that mobile users download slightly larger images than necessary. Optimize your images aggressively, use appropriate compression, and don't serve absurdly large files.
This isn't ideal, but it's pragmatic. Focus your optimization efforts where they matter most—hero images, galleries, and image-heavy pages.
Image Optimization Best Practices
Regardless of which responsive image technique you use, proper image optimization is essential:
Choose the Right Format
- JPEG: Photographs and complex images with many colors
- PNG: Graphics, logos, images requiring transparency, images with text
- GIF: Simple animations, very simple graphics (though PNG is usually better)
Compress Aggressively
Use tools like:
- Photoshop's "Save for Web": Good balance of quality and file size
- ImageOptim (Mac): Lossless compression, removes metadata
- PNGCrush: Optimizes PNG files
- JPEGtran: Optimizes JPEG files without quality loss
Appropriate Dimensions
Don't make your "small" images too small. A 320px-wide image is appropriate for small phones, but it will look pixelated on tablets. Consider:
- Small (mobile): 320-480px wide
- Medium (tablet): 768-1024px wide
- Large (desktop): 1200-1600px wide
Quality Settings
You can often use lower quality settings than you think:
- JPEG: 60-80% quality is usually sufficient
- PNG: Use PNG-8 instead of PNG-24 when possible
Test your images on actual devices to find the sweet spot between file size and visual quality.
Leverage Caching
Set appropriate cache headers so images are cached by browsers:
# .htaccess example
<FilesMatch "\.(jpg|jpeg|png|gif)$">
Header set Cache-Control "max-age=2592000, public"
</FilesMatch>
This ensures images are downloaded once, not on every page load.
Testing Your Implementation
Test Across Devices
Test on actual devices, not just desktop browsers:
- iPhone 4S (Retina display)
- iPad (both standard and Retina)
- Android phones (various screen sizes)
- Android tablets
- Desktop browsers at various sizes
Test Network Conditions
Mobile users often have slow connections. Test your site over 3G or throttled connections to see how it performs with limited bandwidth.
Desktop browsers with developer tools can throttle network speed. In Chrome, open DevTools, go to the Network tab, and select a throttle option like "Regular 3G" or "Good 3G."
Monitor File Sizes
Use browser developer tools to track:
- Which images are being loaded
- How large each image is
- Total page weight
- Load time
Your mobile page weight should be significantly smaller than desktop if your responsive images implementation is working.
Check for Double Downloads
Watch the Network panel in developer tools when testing. Make sure your JavaScript solution isn't downloading both small and large images. Some implementations have bugs where images get downloaded multiple times.
Looking Ahead
Responsive images remain one of the unsolved problems of responsive web design. The good news is that the web community recognizes this and is actively working on solutions.
What's Coming
Over the next year, we'll likely see:
- Continued debate and refinement of the
<picture>andsrcsetproposals - Possible compromise solutions that combine approaches
- Browser vendors starting to implement solutions
- More JavaScript libraries and tools to help in the meantime
- Better server-side solutions and CDN support
What to Watch
Keep an eye on:
- The W3C Responsive Images Community Group discussions
- The WHATWG HTML specification updates
- Browser vendor positions and implementation plans
- Tools and libraries from the development community
Preparing for the Future
Build your sites with the assumption that native responsive images support is coming:
- Use semantic HTML with proper
<img>tags - Keep your JavaScript polyfills modular and easy to remove
- Structure your image storage so different sizes are easy to manage
- Document your approach so it's easy to migrate later
When native browser support arrives, you'll want to remove your JavaScript hacks and use the standard solution. Build with that migration in mind.
Moving Forward
Responsive images represent one of the thorniest challenges in responsive web design. Unlike fluid grids or media queries, there's no clean CSS-only solution for content images, and JavaScript workarounds all involve some degree of hackery.
Despite these challenges, you can't ignore the problem. Mobile users shouldn't have to download desktop-sized images, and Retina display users deserve crisp, high-resolution visuals. The performance implications are too significant to brush aside.
Start by understanding your site's image needs. Which images are decorative (candidates for CSS background image techniques) and which are content (requiring HTML <img> tags)? How much traffic comes from mobile devices? How image-heavy is your site?
For many sites, a pragmatic approach works best: use CSS techniques for decorative images, implement a JavaScript solution for critical content images, optimize aggressively, and accept that the solution isn't perfect. When native browser support arrives, you can migrate to the standard approach.
The responsive images problem is being actively solved, both through ingenious JavaScript solutions and through standards discussions. While we don't have the perfect answer yet, we have workable techniques that significantly improve the mobile experience. And the future looks promising—native browser support for responsive images is likely just a year or two away.
The mobile web isn't going away. It's only growing. Building responsive sites that serve appropriate images to different devices isn't just about optimization—it's about respect for your users, their bandwidth, and their experience. Start implementing responsive images today, even if the solutions feel imperfect. Your mobile users will thank you.