Device State Detection with CSS Media Queries and JavaScript

Being able to detect device state at any given moment is important for any number of reasons and so it's important that web app CSS and JavaScript are in sync with each other. In working on the Mozilla Developer Networks' redesign, I found that our many media queries, although helpful, sometimes left JavaScript in the dark about the device state. Is the user viewing the site in desktop, tablet, or phone screen size? Easy from a CSS perspective but CSS doesn't directly speak with JavaScript. I've created a system based on media queries and z-index which can tell me which media query the user is viewing the site in at any given time, so that I can make adjustments to dynamic functionality whenever I want!

The CSS

The first step is creating media queries important to your application and goals. For the sake of an example, we'll create three new media queries (not including the default "all") which will accommodate four states: desktop (default, doesn't require a media query), "small desktop", tablet, and phone. With each of those states, we'll set a different z-index on an element we'll use to detect the state. The element will be positioned well offscreen so that's not visible; remember, it's only purpose is holding a z-index value that we can retrieve via JavaScript:

Each of those z-index numbers will indicate to our JavaScript code that we're in a given device size at that time. We aren't trying to detect that the user is giving a given device, as the user could simply have their desktop window in a narrow state, but it does give us information about screen real estate for the sake of our web app layout.

The JavaScript

You'll likely want to know the screen size upon DomContentLoaded but since you may want to query for it at any time (since the user could resize their window), we'll require a function be called to get the state any time it is requested:

How you organize this code is also up to you. If you have one global object where you pin methods and properties (like a window.config or window.app global or similar), you can pin the method on that. I prefer using AMD format modules but to each their own. You could add it as a plugin to jQuery or whichever JavaScript library you use. Regardless of how you implement, you now have reliable, easy to use device state detection on the client side thanks to media queries!

Furthering the Effort

We know that screen resizes happen, whether manual window resizing on desktop or via orientation change on mobile devices, so we may want some type of event system to announce those changes when they occur. That's as simple as you would expect:

var lastDeviceState = getDeviceState();
window.addEventListener('resize', debounce(function() {
var state = getDeviceState();
if(state != lastDeviceState) {
// Save the new state as current
lastDeviceState = state;
// Announce the state change, either by a custom DOM event or via JS pub/sub
// Since I'm in love with pub/sub, I'll assume we have a pub/sub lib available
publish('/device-state/change', [state]);
}
}, 20));
// Usage
subscribe('/device-state/change', function(state) {
if(state == 3) { // or "tablet", if you used the object
}
});

Note that I've used function debouncing to limit the rate at which the resize method is fired -- that's incredibly important for the sake of performance. Whether you use pub/sub or custom DOM events is up to you, but the point is that creating a state change listener is easy!

I love this system of resize and device state management. Some will point out matchMedia as an option but the problem with that is needing to have the media queries in both the CSS and the JavaScript and since media queries can be complex, that seems like more of maintenance nightmare than simply using z-index codes. People could argue than one could use window.innerWidth measurements but that's simply trying to translate media queries to JS conditionals and that's a nightmare too. What's also nice about this is that you can use the same type of system for any type media query signifier, like checking for portrait or landscape orientation.

CSS animations are right up there with sliced bread. CSS animations are efficient because they can be hardware accelerated, they require no JavaScript overhead, and they are composed of very little CSS code. Quite often we add CSS transforms to elements via CSS during...

I work with an awesome cast of developers at Mozilla, and one of them in Daniel Buchner. Daniel's shared with me an awesome strategy for detecting when nodes have been injected into a parent node without using the deprecated DOM Events API.

Though MooTools 1.2 is in its second beta stage, its basic syntax and theory changes have been hashed out. The JavaScript library continues to improve and become more flexible.
Fellow DZone Zone Leader Boyan Kostadinov wrote a very useful article detailing how you can add a...

My concern with your approach (it’s minor I concede) is that the UA is drawing a box somewhere offscreen (kind of like the old text hide approach). With the pseudo class method the UA doesn’t have to draw anything.

Awesome thanks! but It’s kind of disappointing to not get a download link, mostly for learning porpoises.

castus

Brilliant solution, I like it, thanks!

Loupax

Great article! The only thing that confused me is that there is no reference on what the debounce() function do or how it works, causing someone who haven’t heard of it think this is a native javascript function

Interesting to see that nobody has raised the issue where you’re using classes and not ids? Wouldn’t it be more performant to just create the div element with a specific id that can be referenced in both the css and js?

I’ll get my coat.

Cid

Just came across this nifty little idea (I think it was in a presentation by Swen Wolfermann). Anyway It’s the same principle, but somehow more simple and it lets you name your media query hooks in CSS directly and skip the numeric translation in js – just use the :after content of some element (why not the body?) to store info about your media query, instead of creating ghost elements.

I set up a webpage with a Desktop.Css and Mobile.Css but I don’t know How to put it together one I have set up as Index.html and the other as m.index.html how can I get it set up where one is for Desktop/laptop and the other for mobile

Luis Herrero

Another cool solution is add a pseudoselector to the body tag, change content via media queries so you can read it in JavaScript.

This approach is a brilliant idea. But using a pseudoelement on body may cause problems because some other application may use it for something else. I prefer to create an empty span with unique id for that to avoid conflicts.

Continue this conversation via emailGet only replies to your comment, the best of the rest, as well as a daily recap of all comments on this post. No more than a few emails daily, which you can reply to/unsubscribe from directly from your inbox.