loading

Detecting a retina display

[Fun with code is a multi-part series. Read the rest.]

A few weeks ago, we announced that oxidedesign.com was fully retina optimized. For those of you who do not know, a retina display is a monitor or device that has more pixels per inch of space than a normal display. This basically means it is harder to tell where the edges of pixels are, allowing for an extremely crisp and clean appearance. This works great on the web for things like text, but a problem arises when displaying images. Since a typical image is built for standard displays, a retina display essentially has to size-up the image to display it. This usually doesn’t end great, especially next to super crisp text.

The solution for retina displays is to serve a larger image, commonly an image that is twice the resolution of the original image, in place of the original image. The real problem arises when you start to look at file sizes, bandwidth requirements, and page load times. When you double the resolution of an image, you quadruple its file size. This means big problems for oxidedesign.com. I already spent quite a good long time getting our image-heavy site to be relatively efficient when it comes to loading and bandwidth, and I wasn’t about to throw that all away.

I need to be able to detect a retina display and I need full control of the content that is being served.

Ideally, I would detect a retina display from the server-side, unfortunately there isn’t any way for a server to know what type of display you are using yet. What you can do is detect a retina display from the front-end using JavaScript. Turns out that every modern browser will return a variable called the Device Pixel Ratio. If the Device Pixel Ratio is 2 or larger, then the website is being viewed using a retina display.

A small amount of research will reveal that there is a whole host of JavaScript plugins and libraries that will do this for you, and through one method or another they will serve the high resolution images in place of the original images. I am here to tell you that the methods that those scripts use are fundamentally flawed and you should not use them if you don’t have to. We’re already loading larger images, so loading them in after the original images have already loaded and swapping them out after the page has loaded is just adding insult to injury.

Knowing that swapping images out on the front-end is absolutely not good enough, I needed to find a way to send the Device Pixel Ratio variable to the server. That way, I would have control over what image is being loaded in the first place. Server-side detection also allows me to tell the site to serve fewer posts at a time to counteract the larger load times and save you from burning up your phone’s entire data plan by visiting our homepage a couple times. So what could I use to send a variable from your browser to our server? That’s right, kids, we’re going to use a Cookie!

Time to dive into the code

In order to detect the Device Pixel Ratio and write it into a Cookie, we only need two very short and simple lines of code:

var retina = 'retina='+ window.devicePixelRatio +';'+ retina;
document.cookie = retina;

All that code does is retrieve the Device Pixel Ratio and saves it into a simple variable, then applies it to the page as a Cookie. After a cookie has been written in this fashion, the next time a page is requested, we can tell the server to look for that cookie and serve our retina-optimized content. Alone, the aforementioned line of code would write the Cookie as expected, but since the server has already sent the entire page to the browser, we wouldn’t get a retina-optimized page on the first shot.

In order for this to really work, we need to force a page refresh after the Cookie is written:

var retina = 'retina='+ window.devicePixelRatio +';'+ retina;
document.cookie = retina;
document.location.reload(true);

It isn’t the most ideal situation, but it is a whole lot better than loading a bunch of unnecessary images, and since a browser will fire JavaScript as soon as it is loaded, if we put this script at the very top of the document, the page will refresh before anything else is loaded. Adding this creates a new problem, though, because if it is just sitting up at the top of the page, firing every time the page loads, we’ve just created a really terrible refresh loop which will constantly re-write the same cookie over and over again and never let our page actually load. The trick to this whole thing is using that code in the appropriate spot, and making it smarter by utilizing some server-side conditions.

Detecting the presence of a cookie in PHP is very simple. Since you can get a Cookie using the superglobal variable $_COOKIE, we can get our retina Cookie by asking for $_COOKIE['retina'].

Using that, we can set up a condition that tests whether the cookie is set, and if it is, do whatever we want (in my case, simply set a variable to “true”, which I then use later throughout the template), but if it isn’t, output the JavaScript which sets the cookie and refreshes the page:

<?php if ( isset( $_COOKIE['retina'] ) ) {
	$ratio = $_COOKIE['retina']; if( $ratio >= 2 ) { $retina = true; }
} else { ?>
<script type="text/javascript">
	var retina = 'retina='+ window.devicePixelRatio +';'+ retina;
	document.cookie = retina;
	document.location.reload(true);
</script>
<?php } ?>

But what about the weirdos who disable Cookies?

Yes, this means that if a user has Cookies disabled, they won’t get retina content. This is an edge-case that I am comfortable with, since almost nobody browses with Cookies disabled these days. One little bug which needs to be addressed does indeed arise if the user has Cookies disabled, though. Since the cookie is never written, we run into the refresh loop issue again, and while I’m comfortable with users who have Cookies disabled not getting retina-optimized images, I’m certainly not comfortable with them being unable to view our site all-together. Fortunately for us, modifying the line of code which refreshes the page with a simple condition that checks for the existence of the cookie that we just attempted to write will spare us here.

var retina = 'retina='+ window.devicePixelRatio +';'+ retina;
document.cookie = retina;
if ( document.cookie.length !== 0 ) { document.location.reload(true); }

There you have it!

This, to date, is the best way I can come up with to detect a retina display and serve appropriate content in the most efficient fashion possible. Hopefully in the future as the technology is adopted and matures there will be a better way to do this, but for now this method will work really well and has proven to work not only for our website, but is running on several of our clients’ sites.



5 Comments


5 Oct 2012

Reply

Love the solution, great job Drew!

I do have a question…wouldn’t you say retina images are flawed to begin with right now since the majority of retina displays right now are mobile? Meaning you’re grabbing the images that are 4 times the size and serving on a smaller screen to a slower/limited data plan, while crippling the mobile device for a sharper images that in my opinion most people won’t even notice. Thoughts?



. . .

Good insight, Mike.

It is certainly a balance you have to find for yourself based on your needs. For us, it was worth it – the increased detail that you can get from a higher resolution image greatly increases the quality of so many of our portfolio pieces. This is also one of the core reasons that I wanted control from the server-side. If I can reduce the amount of posts requested "per-page" then I can reduce load times enough to make the site pretty snappy even on mobile bandwidths (even if it is still loading larger images). For some, it certainly may not be worth it, but using this method, the level of control is so much greater, you could serve key elements as retina, and leave other bulky images alone.

We are currently operating on the assumption that, at least for Apple products, high-resolution displays will be making a break into the non-mobile market, as they already have with the new MacBook Pro.

I’d rather be ahead of the curve either way.



. . .

Reply

Well here is a crazy idea…what if you have 1 set of images at 1.5 times the size across the board? This gives the mobile devices a little more breathing room without hardly sparing the retina quality, especially if you have to pinch to zoom. Also doing this creates a lot less work by supplying only one set of images from the designer all the while knowing the desktop can handle the slightly bigger file size using css to bring back down to 1.0 ratio size. Since the retina devices are still like 98% mobile with the slower/limited data plan it’s hard to keep in mind the current 2% that have MacBook Pro’s. Yeah, I know its going to change but when is to soon?

It is a challenge on what is best solution, it is case by case like you said. On top of all of that, we’re not even talking about how a fluid grid responsive site naturally shrinks down an image from 1000px wide down to 500px wide, creating a retina image for the mobile device. Madness I tell ye.



. . .

That makes a lot of sense, and is perfectly viable. I have noticed that 1.5 times the size on most images is adequate. In fact, some of our images can only go up to about 1.5 times based on our available source files.

Since we use WordPress, I was able to use this server-side detection to build a system where WordPress’ image sizing features will automatically output a double-sized image or the best available size based on what is called in the theme.

Otherwise, yes, the up-front time in creating all those images would have been horrible!



18 Aug 2013

Reply

If you’re using JPEGs serving the images at 2x the size with a compression of 75% will make them look sharp on just about every display. The compression completely destroys the the filesize issue, which will be close to the same.


24 Nov 2014

‘Enter’ to submit



SVG is a thing now;
you should use it.

With all this talk about resolution independence and responsive design, how many times have you built something for the internet and thought, hey, it sure  would be nice if I could have a vector graphic here instead of a series of pre-saved bulky images switching in and out some way or another?

For example: every single logo ever, social media buttons, line art of any type. If your concern is responsiveness and you’re using images, then you’re in for trouble. As we’ve seen in a previous post, just detecting a high-resolution display can be tricky, let alone all of the work up to that point getting images sized and saved for every case.



The multiply effect is a lie

This year’s fabulous Big Omaha website is showcasing a little visual trick. Early on, when Nathan and I were discussing the intended visuals on the site, I failed to notice he was using the multiply effect in Photoshop to achieve the appearance of the red overlays — and he went forward working that into the design. To my dismay, when I went to add the red layers by simply overlaying a slightly transparent layer of red over the top of the cow-skin background, it wasn’t even close.



Fun with code at Meet The Pros

Last week I spoke at Meet The Pros and, not surprisingly, my presentation was titled Fun with code. When I had volunteered to speak way back when, I envisioned myself talking about a bunch of code stuff and making it really interesting and fun. If you’re a regular reader of the Fun with code series here, you probably don’t remember any of it being terribly interesting or fun. It may be really helpful and useful, but it’s certainly too specialized to be a crowd-pleaser. As I was writing my presentation notes, I realized that I needed to speak to the students as a group — not just the few that may be interested in code — and I needed to make it relevant to them. I thought it would be nice to say something that I hadn’t heard in my years of attending Meet The Pros, but perhaps wished I had.





Access Restriction for
Live Staging Sites

We have a lot of active, ongoing projects here at Oxide, especially when it comes to the web. In order to build and make changes to sites which can still be viewable for internal or client review, we run live staging versions of them on subdomains of oxidedesign.com. Don’t bother digging around looking for a secret gem though, because you can’t get in. At least, not anymore.



Post association with taxonomies

It’s been a little while since the last entry to Fun with code, mostly because I’ve been having quite a bit of fun with code. Alas, this tutorial has a third and final part which will wrap everything up.



Extend post types with
custom taxonomies

For part two of this series, I want to show how I extended the default functionality of WordPress custom post types and taxonomies in order to build a user-friendly way to associate posts from one type to another.

To recap, in part one, we set up a couple of custom post types. To start this tutorial we’ll be using a very similar function to set up the taxonomy which we will leverage for post-to-post association.



Using custom post
types for WordPress

Over the past few years, I’ve written quite a bit of code and I’ve had a lot of fun doing it. I’ve got tons of useful snippets in my back pocket and I’m just itching to share them with the world. I’m going to take this opportunity to start a new series here on the Oxide blog, Fun with code. To get things started, let’s outline a problem and uncover a situation which creative code can solve.