To bring you a fast-loading and photo-based home page, I’ve custom-made a responsive image system I call “ideal images.”
Whereas most sites serve one image to fit all screens, PhotoSecrets serves the optimum image for each screen. This minimizes bandwidth and load time, and gets full-resolution images to high-resolution devices.
On the PhotoSecrets home page, each image is drawn first as an empty box. The actual pixel dimensions (including pixel density) are calculated in JavaScript, then the nearest-sized image is requested from the server.
For each image and aspect ratio, up to 50 file sizes are available, ranging from 60px to 2560px wide. Now, each device gets fast-loading AND full resolution images.
HTML
The HTML code for each ideal image looks like this:
<div
class="img w50vw a1x1"
data-img="ideal shutterstock
This decodes as:
source: | shutterstock |
image: | 0127554743 |
aspect: | 1x1 (square) |
max: | 2560 pixels wide |
As you can see, this is not an <img> element, so no image file is loaded initially. The CSS draws a box according to the class — in this case at 50% of the viewport width, and as a square (aspect 1x1).
The CSS would be:
.img{
heigh
vertical-align
font-size
line-height
background-color
background-size
background-position
background-repeat
}
.img.w50vw.a1x1{
width:50vw;
padding:0 0 50vw 0;
}
JavaScript measures the rendered size, and picks the optimum width from an array, up to the maximum available which is 2560 pixels wide.
For example, if the rendered size is 720 pixels wide, the image file will be:
/images/
JavaScript adds this as a CSS background-image. Voilà, the image pops up in the browser.
The conundrum of responsive images
Serving appropriately sized images requires a technique known as “responsive images.” Although simple in theory, it is a conundrum in practice.
Over half the web is images, yet there is no direct way for a device to request a specific image size.
For each image on a HTML page, we know the relative size but not the rendered size. For example, a full-width image will be rendered at 320 pixels wide on an iPhone 3, and 1125 pixels wide on an iPhone X. In my tests with optimized and compressed images, a 320-pixel-wide square image is 21 KB in size, and a 1125-pixel-wide image is 205 KB. That’s ten times as large.
iPhone widths | |||
---|---|---|---|
Device | Width | Retina | Pixels |
iPhone 1–3 | 320 | 1 | 320 |
iPhone 4–5 | 320 | 2 | 640 |
iPhone 6–8 | 375 | 2 | 750 |
iPhone 6+–8+ | 414 | 2.6 | 1080 |
iPhone X | 375 | 3 | 1125 |
Notes: Width=CSS width (or points) |
Serving the large file to the small screen creates many problems:
- 90% of the data is wasted
- the transfer time is ten times longer than it should be
- the user pays ten times more for bandwidth
- the page render time is ten times longer
- the display memory is unnecessarily bloated and slower
Conversely, serving the small file to the large screen gives the user an image that is 1/10 the resolution it should be.
To serve fast websites with high quality images, some intelligence is required. There are several approaches available, but they all have drawbacks. Let’s take a look.
IMG srcset
In HTML5, an <img> (image) tag can include the attribute srcset. This is a list of one or more strings separated by commas indicating a set of possible image sources for the user agent to use. The accompanying <sizes> tag can use media queries.
Example:
<img src="clock-img-200.png"
alt="Clock"
srcset="clock-img-200.png 200w, clock-img-400.png 400w"
sizes="(min-width: 600px) 200px, 50vw">
Although this is generally what I wanted, I calculated that around 50 images sizes are required to cater for most devices. With one <srcset> and one <sizes> attribute per image, that is 100 lines of code — per image. That would result in bloated and ugly HTML code.
PICTURE srcset
A similar approach uses the HTML <picture> tag, which is a container used to specify multiple <source> elements for a specific <img> contained in it. The <source> element can use media queries.
Example:
<picture>
<source srcset="clock
<img src="clock
</picture>
However, this still still has the bloat problem above, and the <picture> tag is experimental technology.
CSS background
The above approach can be hidden inside the CSS by using the background property and media queries. However, the bloat problem still exists, and if this is a separate CSS file, the image file info is removed from the HTML, which impairs the code readability.
image-set
An extension of the CSS background property is the image-set() function. However, this only addresses pixel density and is an experimental technology.
HTACCESS sniffing
The server could detect the device type based on the HTTP_USER_AGENT string in the Apache .htaccess file, then serve the appropriate file size. However, this is straying too far from the HTML code for my liking.
Compressive images
A highly compressed high resolution image (HiDPI) can be sent, but this still provides high file size and imperfect images.
Progressive images
JPEG 2000 can store multiple sizes. In theory, the HTTP connection could be terminated when the appropriate size is received. However, site loading is faster when the connection is kept alive, so the browser ends up downloading a large file when only a small one may be needed. And JPEG 2000 is an experimental technology with little support.
Progressive JPEGs use interlaced display, like GIFs, to paint higher resolutions, but doesn’t address the rendered size problem.
JavaScript image replacement
This is the approach I took, where code analyzes the rendered size and downloads the optimum image.
“One big drawback to this approach is that using JavaScript means that you will delay image loading until at least the look-ahead parser has finished. This means that images won’t even start downloading until after the pageload event fires.”
— Pete LePage, Google Developer Advocate
Fortunately, for me, this is a benefit. My “Lightning Load” approach displays the layout quickly, using gray boxes for the images. Photos can be backfilled after page load using JavaScript. So I’m already doing JS image replacement, and decoding a responsive image is little additional work.
As HTML5Rocks notes, “there are approximately one million JavaScript libraries that do something like the above, and unfortunately none of them are particularly outstanding.”
Adaptive Images
The best off-the-shelf approach I found is Adaptive Images by Matt Wilcox. This uses cookies and htaccess to deliver optimum image file, automatically resized on the server with PHP.
Adaptive Images detects your visitor’s screen size and automatically creates, caches, and delivers device appropriate re-scaled versions of your web page’s embeded HTML images. No mark-up changes needed.”
— Matt Wilcox, Adaptive Images
My approach
I opted to write my own code as I wanted particular control. A PHP function finds images tagged as “ideal” and, if files are not already made, gets the original full-resolution image and downsamples it to an array of sizes. A matching JavaScript function measures the rendered size of the image box, chooses the optimum size from the same array, and adds the link as a CSS background-image.
Links
Here are interesting links if you’re looking into this:
- Images in Responsive Web Design, by Pete LePage on Google Developers
- Responsive images in CSS, by Chris Coyier, CSS Tricks
- High DPI Images for Variable Pixel Densities, by Boris Smus, HTML5 Rocks
- Retina graphics by George Morris, an open source script that makes it easy to serve high-resolution images to devices with retina displays
- Responsive images done right, by Eric Portis, Smashing Magazine
- Responsive Images in Practice, also by Eric Portis, A List Apart
- CSS3 media queries
- CSS4 image set
- Responsive Images and Context-Aware Image Sizing, by Craig Russell
- Adaptive images by Matt Wilcox