Using Media Queries in JavaScript

by Krasimir Tsonev on March 24, 2014

The modern web is always changing, and this article is more than two years old.

absurd_media_queries_header

By Krasimir Tsonev

If you practice responsive design then you use a lot of media queries. Media Queries are a CSS feature that gives designers the power to add something like if statements. By this I mean that it allows you to apply rules only if the current page meets certain conditions. In this article I’ll show you how I use media queries in JavaScript.

The Basics

Many of you probably already know the basics of using media queries in CSS. First you create the media query and then every rule defined within it is applied to the page only if the conditions of the media query are valid.

@media all and (max-width: 700px) {
    body {
        background: #FF0;
    }
}

This is pretty cool and it acts as the basis of the responsive design whereby designers need to change the look of the site based upon the specific dimensions of the screen.

However, the same is true for the developers. We all want to make our applications adaptive. We want to write the code once and support various devices. This is now possible using the window.matchMedia function. It is well supported and if you don’t need to support IE8 or lower you may use it without worry.

var mq = window.matchMedia('@media all and (max-width: 700px)');
if(mq.matches) {
    // the width of browser is more then 700px
} else {
    // the width of browser is less then 700px
}

Not only that, we can add an event listener and wait for changes.

mq.addListener(function(changed) {
    if(changed.matches) {
        // the width of browser is more then 700px
    } else {
        // the width of browser is less then 700px
    }
});

Advanced Media Queries

During the last few weeks I’ve been working on the client-side components of AbsurdJS. I’m filling the library with small but useful built-in modules. Being able to use media queries in JavaScript brings a lot of benefits and I wanted to have this ready-to-use within the library. Yes, we could get the current width or height of the screen with plain JavaScript. While this can be a little bit tricky because the different browsers provide different properties, media queries are capable of more than just testing screen dimensions.

@media all and (orientation: portrait) { ... }
@media screen and (min-resolution: 2dppx) { ... }
@media tv and (scan: progressive) { ... }

As you can see we are able to write styles that target a device with specific characteristics. It’s really simple and it doesn’t require tons of code.

Tackling Browser Support

Media queries in CSS are supported in almost every browser. though in Internet Explorer it only works in version 9 and up. Support for window.matchMedia is also pretty good, though it only works in IE 10+. So what can you do when it is not supported.

var mq = function(query, callback, usePolyfill) {
    var host = {};
    var isMatchMediaSupported = !!(window && window.matchMedia) && !usePolyfill;
    if(isMatchMediaSupported) {
        var res = window.matchMedia(query);
        callback.apply(host, [res.matches, res.media]);
        res.addListener(function(changed) {
            callback.apply(host, [changed.matches, changed.media]);
        });
    } else {
        // ... polyfill
    }
}

The usePolyfill variable was added because I wanted to test the polyfill in browsers which support window.matchMedia. The other part of the code is trivial. Calling the specified callback within a specific context and passing the result as parameters. Here is how this function could be used:

mq('all and (min-width: 300px)', function(match) {
    // match = true or false
});

And now it gets interesting. In general we are mostly interested in the changes of the screen’s width and height. Most of the solutions which I found are based on the resize event dispatched by the window object. They listen for that event, get the new dimensions and check if the passed query works. This is of course not enough, because sometimes we need to know that our app is running on a TV or a screen that has a lower dpi.

Because I’m writing this function for AbsurdJS, I decided to use the features of the library to help solve the problem. The idea is simple. I’ll add a span element to the body tag. I’ll monitor its display property which will be controlled by the media query used by the developer. You need to know a little bit more about AbsurdJS’s client-side components to fully understand the code below, but basically they are JavaScript classes that compile to CSS and HTML at runtime.

Before to start with the implementation of the component let’s first define its CSS and HTML.

var id = ".match-media-" + absurd.components.numOfComponents;
var css = {}, html = {};
css[id] = { display: 'block' };
css[id]['@media ' + query] = { display: 'none' };
html['span' + id] = '';

We need an unique id because we may have more then one usage of the mq function on the page. The rest is just the usual JavaScript object definition. absurd.components.numOfComponents is a number which is incremented during the components’ definition.

absurd.component(id + '-component', {
    css: css,
    html: html,
    intervaliTime: 30,
    status: '',
    loop: function(dom) {
        var self = this;
        if(this.el) {
            var d = this.getStyle('display');
            if(this.status != d) {
                this.status = d;
                callback.apply(host, [d === 'none'])
            }
        }
        setTimeout(function() { self.loop(); }, this.intervaliTime);
    },
    constructor: ['dom', function(dom) {
        var self = this;
        this.set('parent', dom('body').el).populate();
        setTimeout(function() { self.loop(); }, this.intervaliTime);
    }]
})();

This is what the finished component looks like. The constructor method is called immediately. It has the dom module injected, which we need in order to get a reference to the body tag. You may read more about it here, but, essentially the same effect could be achieved by using document.querySelector.

Next, we set the parent DOM element of the component and call the populate method. That’s the place where AbsurdJS compiles the css and html properties. What is important to understand here is that the library appends the generated CSS as a style tag in the head of the current document and the generated DOM element to the parent. At the end of the constructor we are calling the loop method which will be fired again and again in order to catch the changes in the span’s display property. There is a status variable which stores the latest value.

Thankfully for AbsurdJS, the polyfill for window.matchMedia is just 22 lines of code. And it still works with complex media queries, because it actually uses CSS.

An Example

If you want to see this in action check the example below. Just move the panel dividers to make the output panel greater than 300 pixels.

JS Bin

Notice that the example uses the polyfill directly (i.e. there is a true parameter at the end of the mq call). If you inspect the right part of the output iframe source, you will notice that the following CSS is added to the head:

<style id=".match-media-1-component-css" type="text/css">
    .match-media-1 {
      display: block;
    }
    @media all and (min-width: 300px) {
      .match-media-1 {
        display: none;
      }
    }
</style>

And the following span tag is attached to the body.

<span class="match-media-1"></span>

P.S.
The code written in this article is tested under Chrome, Firefox, Safari, Opera and IE9. If the example works properly or doesn’t work inside your browser please comment below. There are also Jasmine tests of the media query module in AbsurdJS which are available here.

25 comments"

  1. Wow thank you for this it’s exactly what i’m looking for!

  2. Scott Busche says:

    What’s the use case for polyfills here? Supporting someone with an 800*600 resolution running IE8? All mobile traffic should be using a browser that supports the query. What did I miss?

  3. Sam Jarvis says:

    Really nice solution.

    Though, our RWD strategy never includes IE8 support. IE8 users are on a desktop and expect a desktop site. Why would you ever (ever) need media queries for a site in IE8<? The two userbases (users expecting a responsive site and users using a browser that supports media queries) don't overlap at all.

    1. There are polyfills available to make media queries work on IE8. This would also provide matchMedia capabilities for IE8.

  4. Ben Plum says:

    Just a quick note, the matchMedia API is not supported until IE10: https://developer.mozilla.org/en-US/docs/Web/API/Window.matchMedia.

    You will need a polyfil when supporting both IE8 or 9:
    https://gist.github.com/benplum/8045336
    https://gist.github.com/benplum/8045327

  5. Marc Brooks says:

    I much prefer leaving all the breakpoints in CSS and simply using a pseudo content on the body tag like Cid recommends here:

    http://davidwalsh.name/device-state-detection-css-media-queries-javascript#comment-73889

  6. Daniel Premo says:

    Your JavaScript seems to be reversed — where your code is commented to be detecting the browser width greater than 700px it’s actually less than 700px and vice versa.

    Also, in Chrome padding ‘@media all and’ in front of the max-width breaks the check (without a JS error). But if I just do this it works:

    var mq = window.matchMedia(‘(max-width: 700px)’);
    if (mq.matches) {

    1. Ricardo says:

      Awesome comment! Thanks, that was exactly happening to me.

  7. Check out http://www.responsivejavascript.com/, which sums up a couple libraries you can use to do it for you.

  8. http://github.com/ryanve/actual can test media queries and also find the actual breakpoints.

  9. Here’s a very simple approach for media queries in JS. The advantage is that you can leave your queries where they belong. In the Stylesheets…
    http://jsfiddle.net/Freizeitler/AH3VJ/

    1. Mike says:

      Elegant solution, nice job. In my current project I define the width of a container class in each media query so I can just get its value directly.

  10. Ohad says:

    Is there a way that this is working on projector screen (and not opera browser in full screen mode) ?

  11. Tien Do says:

    Sample code doesn’t work at all in Chrome without any error as Daniel Premo pointed out.

    Thanks Daniel,

  12. Ali says:

    It works in Chrome if you do it this way instead:

    if (window.matchMedia(“(min-width: 700px)”).matches) {
    alert(‘the width of browser is more then 700px’);
    } else {
    alert(‘the width of browser is less then 700px’);
    }

  13. Hi guys. I tested the jsbin provided in the article and it does exactly what I wanted. Just open and shrink the right panel http://jsbin.com/paqegexe/26/edit

Leave a Reply

Your email address will not be published. Required fields are marked *

© 2016 Modern Web & our authors. All rights reserved.