Developing a responsive, Retina-friendly site (Part 2)
This is the last part in my three-part series about the design and development of a responsive and so-called Retina-friendly website. While I have covered my development process, media queries for responsive development, and various JS libraries I use, I'm going to talk about images in much more depth in this article. In particular, I'll show how to use HiDPI image assets as well as cover responsive images and some history around that.
Going HiDPISo you want to do the easiest possible thing to get your website's header logo to look good on HiDPI devices and computers? Doing this in CSS is similar to loading an
imgtwice as large as the height and width attributes of that tag.
From here on out I refer to all "retina" displays & related ilk with the device-agnostic HiDPI moniker. Oh and a fun tidbit: the iPhone 4/5 is no longer the highest DPI mobile device. The HTC One's 1920x1080 4.7-inch display gives it a lusty 468ppi.
I wanted icons and my large header avatar to be HiDPI display friendly. Load a background image that is twice as large as you want to display it and use
background-size to constrain it to half of the image's width and height. Easy. But we don't want to load the HiDPI image all the time. There's no need to waste bandwidth for regular displays.1
That's where media queries come in again. Below is a Sass mixin to do just that, based on this post by 37signals, but modified to remove
min--moz-device-pixel-ratio in favor of resolution as recommended by Mozilla. The W3C folks say the same thing about
What about icons?That mixin only works for individual images so if you have more than a few items to show you'll probably want to avoid that many HTTP requests. For example, the 18 icons I use throughout the site. I could have created my own icon webfont with a nice tool like Font Custom, but decided against it for a few reasons.
While an icon webfont would be retina-friendly right out of the gate, browsers render fonts differently and it may not look the same everywhere. Notably, icons from webfonts may appear blurry or have color fringing. And if you wanted a gradient on that icon (but why?!), you will need to resort to the typical
text-fill-color: transparent; mayhem.2
However, if you are really interested in getting icon fonts working and don't mind the labor of hinting them for the sizes you'll be using them at, then you can get some pretty impressive results like this.3
But I already know how to make PNG sprites so I went that route. Here's how I hooked up two of them:
I'm not actually using any media queries here to conditionally load a different sprite. I'm always loading the double-sized icon sprite. I wouldn't normally do this but the icons were small enough for bandwidth to not be a concern and the HiDPI version looked good enough on non-HiDPI displays.
At least that's what I thought. Even with the correct background-size halving and only using pixel-fitted 2x icons were sized in even numbers (ie 48px by 48px should result in a 24px x 24px 1x icon), it still resulted in blurry icons. I could seemingly get around this by adjusting the background-size 1px smaller than it should need, but that felt wrong and that would make some icons crisp and turn others blurry.4
So back to Photoshop to create two separate sprites: a 2x version and a 1x version. Working with sprites is usually a pain in the ass and working with two would prove to be a nightmare if you don't have a system for keeping it organized. Fortunately, Twitter Spritemaster™ Dave Bedingfield shared a few tips with me:
- Create a set of dividing lines for each icon and group related icons and icons of the same size/utility.
- Both the @2x HiDPI version and 1x version will live in the same cell and be aligned how you will end up using them in CSS (most likely top left, unless you use something like background-position: 50% 50%; in which case they should be centered). Proximity doesn't matter if you end up accessing each icon in it's own individual element (for example, an
iwith a width and height).
- The maximum width and height of the cell depends on the size of the larger @2x version.
- Even numbers are you friend. In my example below, icon coordinates are even numbers and icon widths and heights are as well.
- When saving both sprites, hide everything and only show 1x icons, save and then 2x icons.
I do it a bit differently from Twitter, they always load the @2x sprite. If you poke around on Twitter's mobile website you'll see there are different sprites for mobile and tablet. The main benefit of this approach is that every icon uses the exact same coordinates!
After saving and optimizing the sprites, it came time for the chore of writing the CSS and identifying all the icon coordinates. Fortunately a little website called Sprite Cow makes this a bit easier than going around measuring things in Photoshop. Just upload the sprite, select each icon (no need to be accurate, it auto-selects) and it outputs the CSS.
And now for some Sass mixins of course.. here's a simple one to just identify high-DPI devices. You'll see it's similar to the image-2x() mixin from earlier but doesn't concern itself with the background image.
Then I created a two Sass variables to keep track of the sprite URLs so I didn't have to keep pasting that around for every icon.
Finally, here's how this all ties together to display some of the icons in my header:
That's it! HiDPI devices will load the @2x sprite and accordingly set the proper background-position to load the right icon. Regular devices will load the 1x sprite and respective icons.
As you read in Developing a responsive, Retina-friendly site (Part 1), the side effect of using Sass @media bubbling and @content blocks like this retina mixin is you get compiled CSS with lots of extra media queries. The snippet above will end up compiling to five of the same retina media query. I'll leave it as an exercise to the reader about how to rewrite it to save bandwidth.
Don't forget the Retina favicon!And of course, we can make a HiDPI favicon.ico. Create both a 16x16 and a 32x32 png and import the into x-icon editor. It will let you export a single .ico with all size variants baked in.
There's a lot more to cover with HiDPI image assets, but I'd rather point you to Thomas Fuchs' excellent Retinafy ebook than trying to summarize it here.
Responsive ImagesResponsive design doesn't just stop at adjusting layout. It also applies to dealing with images. Up until now most web developers would add
max-width: 100%;to their images and call it a day (so-called "fluid/flexible images"). But now with more devices with higher PPIs in use, we can do better.
My interest in responsive images began when I was reading through Medium.com's source, as some nerds are wont to do when bored. Despite the fact that they use
font-smoothing: antialiased; (read this) and
text-rendering: optimizeLegibility; (a performance pitfall on mobile; only use for headers), I kept poking around5.
I noticed their header image had a unique URL that changed when I resized my browser and reloaded the page. Looking at the image response headers showed nginx as the server and a
Via of Amazon Cloudfront. Looks like they're using nginx's image filter module to do the on-the-fly resizing and utilizing it as an origin for Cloudfront, but I digress.
Why should mobile visitors still waste bandwidth loading the same 1000px image you display for desktop visitors? What about desktop visitors tethered to a slow connection? They shouldn't have to wait for the 2x HiDPI version of that large image to load. As you can imagine there are other edge cases mixed in here too. We should loading HiDPI assets for mobile devices that support it and everything in between (depending on the breakpoints) to provide properly sized images and forgoing the old manner of simply downsizing images with CSS.
Technically this is simply adaptive images and that's what you'll find most often when researching this — providing several versions (for example 4 sizes) of the same image and then having the browser select the proper one to load depending on conditions like media query breakpoints6. A true responsive images solution would serve up the exact size needed with the help of server-side image resizing and caching. More on that later.
I want photos on my photos pages to be crisp all the time — something that should be possible given the user's bandwidth, browser width/screen resolution and device pixel ratio. If we know they are on a slow connection, we can load a lower quality version.
So I read pretty much everything the Internet has to offer about responsive images, including W3C drafts, W3C respimg Community Group discussions et cetera. There are several ways to get it working right now but there is no great solution. The
Brief History of Responsive Images
<img>tag wasn't built with this in mind.
It all started with folks in 2010 using HTML5 data attributes like
There-in lies the entire problem with getting responsive images to work today:
How do we reconcile a pre-parser that wants to know what size image to download ahead of time with an image technique that wants to respond to its environment once the page layout has been calculated?Jason Grigsby
Where are we now?Based on those trials and tribulations, developers started coming up with other ways to solve this with the
Unfortunately that has SEO implications on its own (no src), can cause ugly page reflowing as the images begin loading, and results in a slower page load from lack of prefetching. Matt Cutts has said that Google tends not to look at content inside of noscript tags due to noscript abuse from spammers.
Then developers started thinking, well what if we extend
img to support a
That seems to have evolved into the W3C-proposed
srcset attribute for
srcset attribute contains up to three optional URLs and accompanying descriptors for different versions of the image to load. In this example, the 2x descriptor denotes a "maximum pixel density of 2 device pixels per CSS pixel" and 100w means "maximum viewport width of 100 CSS pixels."
Another suggestion around this time was creating a new image format entirely; one that points to alternative resolutions. Googler Ilya Grigorik recently wrote about this kind of solution (content negotiation) at length. It turns out this is also something the W3C seems to be okay with in their recently updated Use Cases and Requirements for Standardizing Responsive Images draft:
The solution MUST NOT require server-side processing to work. However, if required, server-side adaptation can still occur through content negotiation or similar techniques
If all this wasn't confusing enough, Apple decided that people were having too much fun without them and decided to put their hat in the ring with a proposal for CSS4
image-set. It's pretty much the same concept as
srcset but in CSS. The problem there is that folks will just start converting their
img tags to
image-set for background images in CSS, thereby removing
alt text (
divs can't have
alt text) and throwing all accessibility out the window.
It seems like developers on the W3C and W3C Responsive Images Community Group are leaning towards the proposed
<picture> the future of responsive images
pictureelement. In a W3C Working Draft for the picture element, syntax utilizes a simplified
srcsettag restricted to only carrying information about images for devices with different pixel densities per
This seems to be the current state of responsive images. I probably got the order of some of these thing wrong, so don't hold me to it exactly. The
picture element looks promising but has a few limitations in my eyes:
- It's rather verbose and will be rather confusing for new developers.
- Media queries have no sense for bandwidth available to the browser so this will hypothetically still load a large 2x image on a HiDPI-capable device even if it's on a slow connection.
Media queries can bring some interesting affordances including device pixel ratios and the upcoming luminosity media feature, but bandwidth is not one of them. Aside from a manual bandwidth test, the only hope is either waiting for browsers to get smarter about picking the right image source given what it knows about the available bandwidth or letting sites access something like navigator.connection7.
I'm using foresight.js for nowWhile I'm waiting for
pictureto get implemented in all major browsers 8, I decided to use one of the more popular responsive image solutions:
Foresight.js gives webpages the ability to tell if the user's device is capable of viewing high-resolution images (such as the 3rd generation iPad) before the image has been requested from the server. Additionally, it runs a bandwidth test to see if the user's device should load high-resolution images. Depending on the display and network connectivity, foresight will request the appropriate image for the webpage.
Foresight can be configured for a number of different responsive image setups ranging from simply swapping out an image with a higher resolution version all the way to requesting an image the exact width of the client's browser. I went with the latter.
Here's the markup for the image itself. Notice how the
img tag doesn't have a
src specified. It gets added after foresight evaluates the environment and decides what image version to load. (The curly braces are just my Jekyll liquid tags.)
Now here's where it gets a little hacky. All foresight images are assigned a CSS class that uses
font-family to store an
The value I have for
image-set() is a little cryptic on its own too. It makes use of foresight's URI Find/Replace functionality. It looks for the string "w=1400" in my image
data-src and replaces that with the actual width of the client (this also takes into account HiDPI resolutions).
But what is that query string param in my image URL? Well here's the other hacky part — I'm using a service to on-the-fly resize and cache the image for exactly the request. For example, if your browser is 837px wide, it will change that width param in the URL to 837. If you're on a Retina MacBook Pro, that request will be for a 1674px wide image. I just dump a fairly large image on the service and it does the rest for me. And if I make my browser wider, foresight will automatically request a larger version of the image to swap in!9
On the photos index page, I use foresight differently and only have it swap in 2x high-res images for HiDPI devices. I'll discuss the query string parameters at the end of all those image urls below.
I do feel a bit guilty about this solution, especially as having any server-side processing is one of the no no's mentioned in the W3C
picture proposal. It's just introducing another point of failure. It works pretty well right now and it's not like I can use
I'm not using it in blog post articles just yet. As an FYI, if you do decide to use a similar solution for your jekyll posts, you'll need to write a filter for your RSS feed otherwise the images won't load for RSS readers since there's no
On-the-fly image resizing and caching with imgixAfter I poked around and found out how Medium started doing some of this, I started brainstorming about how I could do it. A number of solutions surfaced from the same nginx image filter setup with Cloudfront to something utilizing the dragonfly gem but at the end of the day I don't want to manage yet another thing.
I'm using imgix, a service from a fellow Y Combinator batchmate. Setup was ridiculously easy. I just had to create AWS IAM user credentials with read-only access for the Amazon S3 bucket where I store my images. Something like this:
I gave that and its accompanying AWS access key and secret to imgix. Then all I had to do was replace the host for my images to my newly created
stammy.imgix.net. The rest of the path remains the same.
With imgix setup, I can now easily manipulate the image by adding various query string params for their image URL API like w for width, q for quality, dpr for device pixel ratio and so on.
Speeding up my photo posting flowOne asterisk when it comes to using foresight is that it needs to know the image aspect ratio for each photo so you must provide it with
data-heightattributes in the markup. This can be rather annoying to manually add every time I post a new photograph with Jekyll. I was already manually adding several other attributes for my photo layout, like camera type and post processing software used.
I decided to write up a quick rake task to automate the creation of each new photo post — upload the image to S3, grab any relevant EXIF data, fetch an address if the EXIF has GPS coordinates and finally create the file and open it up in Sublime Text 2 for me to begin writing.10
What's next with images?As I mentioned above, I'm only using responsive images on my photos pages for now. I'd rather not end up with hundreds of blog posts to change when a particular solution to responsive images becomes a standard. That being said, I do use focal-point CSS in some of my blog posts and pages. Focal Point lets you add classes to your images that crop the image when triggered by a media query breakpoint.
This is an attempt to address "art direction" outlined by the W3C in responsive images:
In a responsive design, it is typical to change an image so it can be targeted towards the features of a particular display (or set of displays). Sometimes this means cropping an image.
It's just CSS that hides part of the image so that as it gets smaller, it crops in on the area you specify as being the focal point (get it?) of the image. One side effect of this particular implementation is that it hides captions below the image when the media query gets triggered. Focal point is only an aesthetic solution, a true one would address bandwidth concerns as well and load a different, cropped image for smaller displays. Now you can see much more there is to go with this whole "responsive" thing huh? It's just getting interesting.
Let me know what you think!Comments and feedback keep me going and writing articles like this. Drop me a comment below, share this post if you enjoyed it and shoot me a tweet. I'd love to hear from you.
Note: Be sure to check out part 1 of the development track of this series and the initial design post that started it all.
1 This works because browsers only load images referenced in a CSS file when the appropriate class/id is used in markup on that page. In other words, there's no harm (aside from extra request/latency/etc) in having multiple versions of an image referenced in CSS.
2 Or if SVG is your thing...
3 Related: An interested A List Apart read, The Era of Symbol Fonts with some interesting ways to use ligatures to your advantage when thinking about icon webfonts.
4 Though in hindsight I didn't really investigate the cause of the 2x being blurry on a regular display too much because I knew I wanted to do it the proper two sprite, media query way regardless. I'd say that I probably could have tweaked the icons a bit more and fidget with the insides to make them work at the sizes I wanted -- the equivalent of font hinting I guess you could say.
5 I'm a total hypocrite and use antialiased font-smoothing on this site for the time being.
6 Though quite confusingly there is a responsive images solution named Adaptive Images.
7 which some Android mobile browsers have
8 without the need for a polyfill like picturefill, which isn't quite seamless as it requires different markup conventions.. that you'd have to end up fixing in a few years when picture is fully supported.
9 Definitely overkill and a more reasonable solution would be to round up to the nearest 100px and request that size, increasing likelihood that size has already been requested by other visitors before and is cached by the resizer. This is what Medium.com does.
10 Though in hindsight I might need to edit this to take the EXIF data and then use something like pngcrush to optimize the image for posting to the web (even though I use imgix for resizing, it'll save me storage costs in the longrun).