Love the web

Love the web is a design, programming, and usability blog

onMediaQuery - Responsive Javascript 7 comments

Josh Barr 09 May 2012

Chances are, you're going to want to execute some code based on media queries in your snazzy responsive layout. At Springload we started by doing this with the window.matchMedia() function. It worked pretty well, but it always felt a bit, well.. inelegant. The problem was, we had to set our media queries in our Javascript as well as in our CSS files. This made our geeky department very nervous, so we struck out in search of a better, simpler way that didn't keep our developers up at night in a pool of cold sweat.

The CSS

Jeremy Keith recently posted a great article about using conditional CSS to add properties to the page that are enumerable in Javascript. Jeremy uses the :after psuedo property on the <body> tag to add information about the current viewport. The markup is really simple:

<style> body:after { content: 'mobile'; display: none; /* comment this line for debugging purposes */ } @media screen and (min-width: 35em) { body:after { content: 'skinny' } } @media screen and (min-width: 56em) { body:after { content: 'wide-screen' } } </style>

This gives us a hidden string containing the currently active query, which we can fish out using a couple of lines of Javascript. All we need to do is evaluate the window when it resizes, check if the body:after property has changed, and fire a callback.


The Javascript

We've wrapped up Jeremy's great solution in a little Javascript module that can be inserted into your page. It's super lean and engineered with mobile loading times in mind.

<script type="text/javascript" src="js/onmediaquery.min.js"></script> <script> // Define the queries you want to test for.. and what to do if they're TRUE var queries = [ { context: 'mobile', callback: function() { console.log('Mobile callback. Maybe hook up some tel: numbers?'); // Your mobile specific logic can go here. } }, { context: 'skinny', callback: function() { console.log('skinny callback! Swap the class on the body element.'); // Your tablet specific logic can go here. } }, { context: 'wide-screen', callback: function() { console.log('wide-screen callback woohoo! Load some heavy desktop JS badddness.'); // your desktop specific logic can go here. } } ]; // Go! MQ.init(queries); </script>

Make sure that the strings you define in the context: property match the :after content in your css!

Adding queries

As well as passing an array of objects when you initialise the plugin, you can add extra callbacks at any time. This is especially handy if you've got multiple JS files across the site that need to test whether a query is true.

//Add a media query test var my_query = MQ.addQuery({ context: 'skinny', callback: function() { console.log( 'second skinny callback!' ) } });

The addQuery function returns a reference to the object you added, so you can remove it later if you need to:

MQ.removeQuery( my_query );

This weighs in at a whopping 1.06KB (517 bytes gzipped), well worth including in your next project.

Read the next post - fadeSlide.js - a jQuery slider

Subscribe to these comments

7 comments submit a comment

22 May 2012

mymoonlog

cool

23 May 2012

Dan Ott

Great approach! I also tried building something similar with matchMedia, Respondr as a robust solution for adding/removing JavaScript based on a media query. I think I might rework it in favor of named contexts, as this seems like a more readable solution for real web work.

23 May 2012

Trevor Phillippi

I love the concept behind this, my only question is this: does the JS constantly check for changes in browser size, in case, let's say, the user scaled his/her browser invoking a new media query. How would the scripts run properly? I mean I guess you could have a listener or something but this is the only potential disadvantage I see to an otherwise really cool idea.


Trevor

23 May 2012

Josh Barr

Hi Trevor,

When the browser is scaled, the plugin checks to see if the context has changed, i.e. between skinny and desktop. If the context changes, then the callbacks are fired. However, there's some built-in smarts to stop it firing the callbacks hundreds of times as you scale the window. It'll only execute the callbacks on load, or at the points where the CSS changes the content of body:after. If you use the addQuery method and add a query to the current context, then it'll fire that straight away as well.

In terms of how you to structure the code, you can wrap up all your view-specific logic in an init() function that you run when the context changes, like jsMobile.init() or jsDesktop.init(). We've found this is a bit cleaner to read than putting all the logic directly in the callback.

cheers,

Josh

23 May 2012

Josh Barr

Hi Dan,

Thanks for the comment! I tried out your plugin when we first started thinking about responsive javascript solutions - our first inclination was to use matchMedia as well.. and indeed the beta version of this plugin relied on it heavily. What we came up against on some big projects (kiwibank.co.nz) was just how out-of-hand things were getting with media strings and breakpoint declarations all through multiple JS files for different sub-sections. Now media query trigger points are managed in our SaSS framework, and the JS is nicely decoupled. The named contexts definitely help!

- Josh

23 May 2012

Matt

Great solution!

I was pondering how to achieved this only today. It still means that you need to match strings in both the CSS and the JavaScript though.

One possible solution would be to setup some variables in LessCSS that are updated by the php (prior to compilation of the less into css - in our case we use less and PHP to process it on the fly) that are also passed into an array in javascript to then be passed into the MQ.init. method.

Of course it means that we still need to match up the callbacks with the variables but you could define the callbacks in the PHP too as strings that are called by using square bracket notation - could even define functions that are named like the contexts - again, requires us to know what the strings are and to match them in more than one place - but certainly automates things a little more. Oh and it is of course technology and workflow dependent (PHP to compile LessCSS on the fly)...

Cheers,
Matt

09 Jun 2012

Rémi Grumeau

I like setting a mediaquery classname on HTML element better, updating it on load, onResize & onorientationchange. Then .mediaqueryclassname element { ... } do the job.

Submit a comment

Do not fill in this field (bot protection)

Submit comment

Love the web posts

Code

Design

General

Usability