Grow your CSS skills. Land your dream job.

Enquire.js – Media Query Callbacks in JavaScript

Published by Guest Author

The following is a guest post Nick Williams. Nick emailed me about a new library he created to assist in working with responsive design. Specifically, he was implementing an idea he found here on CSS-Tricks but wanted to do so more intelligently by only running the script when needed. Thus the birth of this new mini library! There are some kind of similar ones out there, but this one is very small and works with native media queries. I'll let Nick tell you more about it...

Enquire.js is a JavaScript library I created for dealing with media queries in JavaScript.

"Whoa! Back it up! Media queries in JavaScript?! What kind of abomination is this?"

Whilst the premise may seem odd, it really is a handy addition to the responsive toolbox.

The Lowdown

Enquire.js is a lightweight, pure JavaScript library for handling media queries. It is less than 1kb when GZIP'd and minified, and has absolutely no dependencies. Yup, you read that right - no dependencies, not even jQuery! The most you will have to do is provide a matchMedia polyfill if you wish to support browsers without a native implementation.

Use Cases

  • Responsive Design
  • Mobile-first Design
  • Assist older browsers with media queries

Raison D'etre

So how and why did this come about? I think the easiest way to explain is through a simple scenario...

Imagine you've got an established website, currently fixed-width and targeted at large-screened devices. You've heard all the hubbub about this "responsive design" business and wish to get in on some of the action. So you dutifully start designing and planning how your website may work on small-screened devices. You've cut out all the chuff, cleared your floating sidebar and bumped up the size of your form elements. Great!

But what about that pesky menu? It's beefy, it's got multiple levels and relies on CSS hover effects to expose the deeper hierarchy - in other words, it ain't gonna work on a small-screened touch device!

So you do a little digging around on Google and stumble across a clever solution to convert a menu into a dropdown. High fives all round, your menu is now functional on mobile touch-based devices! But you've got this nagging sensation. You're not quite sure what it is, but something's still not quite right. Ah yes, that's it; you're running this code even for large screened devices, even though they have absolutely no need for it! You shed a little tear for the wasted CPU cycles, wishing that there was a way to selectively run this JavaScript.

A New Challenger Appears!

This is where enquire steps in to help out.

For now, we'll run with the converting a menu to a dropdown scenario, making the menu appear when the window is less than 960px. With enquire, it's as simple as this:

enquire.register("max-width: 960px", function() {
  // put Chris' code here to convert your menu to a dropdown
});

All this does is tell enquire to invoke the supplied function only if the given media query matches. And so, you save those precious CPU cycles for large-screened devices. OK, I realise that's a bit silly since mobile devices typically have lesser-powered CPUs compared to larger screened devices, but it works from the other direction also - the much-fabled mobile first approach!

Digging Deeper

At this point it's worth quickly running through the enquire API before we get into some meatier scenarios. The previous example showed the most simple use-case for enquire - supplying a function as the second parameter to be run when a media query matches. This will suffice in most scenarios, however enquire can do a lot more if you've got more demanding requirements. You can pass the register method an object as so:

enquire.register("screen and (max-width: 1000px)", {

  // REQUIRED
  // Triggered when the media query transitions
  // from *unmatched* to *matched*
  match : function() {},
  
  // OPTIONAL   
  // Triggered when the media query transitions 
  // from a *matched* to *unmatched*                        
  unmatch : function() {},    
                                
  // OPTIONAL
  // Triggered once immediately upon registration of handler
  setup : function() {},      
                                
  // OPTIONAL
  // Defaults to false
  // If true, defers execution of the setup function
  // until the first media query is matched (still just once)
  deferSetup : true           
});

This signature gives you full control and allows you to define exactly what you want to happen, and when you want it to happen. This only really becomes useful when you make a call to enquire's listen function (more on this shortly).

Examples

So let's get onto some examples that show the more advanced features in action.

Faux Media Queries In Legacy Browsers

Enquire.js can be used to fake some rudimentary media queries in older browsers. However, please don't use this approach as a replacement to genuine media queries in your CSS, just use it as a monkey patch for older browsers (jQuery used in this example):

$(function() {

  // cache body for speed
  var $body = $("body");

  // DRY up handler creation
  function handlerFactory(className) {
    return {
      match : function() {
        $body.addClass(className);
      },
      unmatch : function() {
        $body.removeClass(className);
      }
    };
  }

  // hook up our "media queries"
  enquire
    .register("screen and (max-width : 320px)", handlerFactory("lt-320"))
    .register("screen and (max-width : 640px)", handlerFactory("lt-640"))
    .listen();

});

Here we're using match and unmatch handlers to add or remove classes to the <body> element, which allows us to target styles at different sized screens. Notice we can chain calls to all of enquire's functions.

Of particular note here is the call to the listen function so that enquire will respond to browser resize and orientation change events (though of course in this example, orientation change events are unlikely in legacy browsers). For optimal performance, listen is throttled to only respond to one event in a given timeframe. By default this is 500ms, but you can specify your own throttle by passing as a parameter to listen:

// 10000 milliseconds = 10 seconds. Why not?!
enquire.listen(10000); 

Dogfooding

Let's walk through an example of a real world use of enquire, taken from the enquire.js project page (dogfooding FTW!). Here I wanted to make the page load as quick as possible by only loading the bare minimum amount of JavaScript by default, opting to load anything beyond that asynchronously, only when required. This is so that small-screened devices (and by proxy those more likely to be on a slow connection) get the speediest page load possible, and only large-screened devices have to deal with the extra resources.

In particular, this pattern is used to pull all code required to generate and render the "Jump To" sidebar of the project page. This example is slightly simplified as not to distract from the important concepts:

$(function() {

  $toc = $(".toc");

  enquire.register("screen and (min-width: 1310px)", [{

    deferSetup : true,

    setup : function () {

      // use Modernizr's yepnope to load 
      // additional scripts and manage callbacks
      Modernizr.load([ 
        {
          load: "js/jquery.toc.js",
          callback : function() {
            // code to generate "Jump To" list
            // and append to body
          }
        },  

        // load in the bootstrap plugins.
        // they hook themselves up via their data-API
        "js/vendor/bootstrap-scrollspy.js",
        "js/vendor/bootstrap-affix.js"
      ]);
    },

    match : function() {
      $toc.fadeIn();
    },

    unmatch : function() {
      $toc.hide();
    }

  }]).listen();   

});

Two new options have been introduced here - setup and deferSetup. setup is a function that gets called just once, which is great for getting all your expensive DOM manipulation out of the way up-front. By default, setup is invoked as soon as you register your query handler, regardless of whether the query has matched yet. However, if you supply the deferSetup flag we can postpone setup until the first time a query is matched.

In this case it obviously makes sense, as scripts only be loaded for large-screened devices. The match and unmatch function are then left to simply show and hide the "Jump To" list.

Other Examples

A few more scenarios which enquire would handle effortlessly:

  • Responsive images
    • Use data-* attributes and switch an image's src depending on which media query matches
  • Shuffle content around
    • Sometimes it's not as easy as just stacking items on small screens, you may want content to appear in a completely different order.
    • Again, I'd recommend a declarative approach with data-* attributes so you can pick out source elements and destination elements.

There's More...

Enquire.js still has more to offer! You can register multiple handlers per query, and also multiple queries, meaning you've got ultimate flexibility! To read more about this, please visit the enquire.js project page.

Download, Fork and Contribute!

For downloads and source code, visit the project on github. If you wish to contribute, please do so, I'd love other people's opinions, thoughts and ideas. Fork to your heart's content and make a pull request with any changes. If you encounter any issues, feel free to create an issue on github and I will duly fix the problem :)

Licence

The project is licenced under the MIT licence, so you're free to use it in any way you wish.

Comments

  1. Permalink to comment#

    Hmmm….this code be interesting. Nice job!!!

    • Nick Williams
      Permalink to comment#

      Thanks for the kind words! Would love to hear any feedback

  2. Awesome!

  3. Permalink to comment#

    Thanks, I’m going to test !

    One question :
    Can we use other media queries (like device pixel ratio) or max|min width only ?

    • Nick Williams
      Permalink to comment#

      Hey, author here. I expect that it should work. If not get back to me (feel free to raise an issue on github) and I will investigate further. Enjoy :-)

  4. Very nice work. I’ve got something I will try this out on soon. Also curious about other media queries like eQRoeil mentioned.

    • Nick Williams
      Permalink to comment#

      Thank you! Glad you’ve found a use for it.

      See my reply to eQRoeil above, it should work. I’m keen for enquire to deal with every situation, so if you encounter any issues raise an issue on github and I will endeavour to fix.

  5. Permalink to comment#

    I was wondering of something similar existed a few days ago. Definitely gonna consider this in my nex project !

    • Nick Williams
      Permalink to comment#

      I read your mind! Ok maybe not, but good to hear it might meet your needs!

  6. Permalink to comment#

    I did no know about this librairy, thanks a lot for the very clear examples. The idea to use this with data-* for image replacement looks very promissing, I’ll have to dig this subject.

    • Nick Williams
      Permalink to comment#

      It’s a pretty new library which is why you may not have seen it. Good to hear the examples were helpful! The project site is a little bare at the moment but I’m going to bulk it out in the near future with many more examples, including responsive images :-)

  7. basti1350
    Permalink to comment#

    Thank you for this article. I am looking for tools like this a while ago. As an alternative to enquire.js I found Harvey. It is a Little Bit more complicated but worth checking out.
    here is the link: https://github.com/harvesthq/harvey

    • Nick Williams
      Permalink to comment#

      Hey basti1350, I did stumble across Harvey at one point. As you say Harvey is more complicated and also forces you to use a polyfil even if you only want to support browsers with a native implementation. I’ve optimised enquire for simplicity and efficiency but it still has all the features of Harvey, and more!

    • basti1350
      Permalink to comment#

      You are right, the dependence on the polyfill bothered me too. I tried enquire today and it works very well.
      Thank you for sharing.

  8. Matt
    Permalink to comment#

    Just what I was looking for. I wonder though if it’s also possible to use `em` values in the media-queries.

    enquire.register("screen and (min-width: 40em)", {

    is that possible?

    • Nick Williams
      Permalink to comment#

      Should not pose a problem :-) if however you do encounter any issue let me know via github

  9. Paul
    Permalink to comment#

    I’ve never heard of data-* and am curious to know more, can someone point me to any resources on it?

    • Nick Williams
      Permalink to comment#

      The data-* attributes are awesome! They were introduced as part of html5 as a way of storing custom application style data. You define your own (without breaking html validation) and use them however you wish. Great way to declaratively hook up javascript functionality.

      More info: http://html5doctor.com/html5-custom-data-attributes/

    • Paul
      Permalink to comment#

      Aha sounds awesome! Thanks Nick!

    • Nick Williams
      Permalink to comment#

      If you would like to see some great usages of this, see the twitter bootstrap javascript widgets as they all have a “data API” . This has become my preferred way to hook in functionality

  10. Michał Rakowski
    Permalink to comment#

    That’s great, gonna test it out in my current project :)

  11. Matt
    Permalink to comment#

    What I might have missunderstood here is how to use it as a listener for media-query changes?

    Imagine this: I have a Responsive Webdesign where I want JS to recolor the background of the body.

    The straight CSS way would be this

    @media screen and (min-width: 55.5em) { 
    ♒♒body { background: red; }
    }

    Is the same possible with enquire.js?

    What I didn’t understand is how the

    enquire.register("@media screen and (min-width: 55.5em)", function() { $('body').css('background', 'red'); });

    But this is only fired when the page loads. I thought enquire would work as a kind of listener and be always triggered once a media-query matches. Do I have to run the register handler inside $(window).resize(function() ?

    Imagine a responsive webdesign with different layout steps (just like most of the responsive webdesigns work) and I need to apply or remove styles from different elements that have been set via JS before. I do that currently by querying the window.with inside a resize-handler. Can I do this with enquire.js?

    • Nick Williams
      Permalink to comment#

      Hey Matt,

      By default enquire doesn’t listen for resize events for efficiency. Luckily all you need to do to get this functionality is call listen().

      You can make a separate call against enquire directly, or chain it onto your register call:

      enquire.require(...).listen();
      

      This causes enquire to listen and respond to resize and orientation events for all registered queries (i.e not just the one query).

      This was discussed in the article, in the faux media queries example. Dogfooding shows a more complete example. Basically the match and unmatch callbacks are what you want to use

    • Matt
      Permalink to comment#

      There is however a little delay happening between the state where the CSS changes and the JS is fired.

      Is it bad (performance-wise) if I call the listener like every 10s millisecond?

      }).listen(10);
    • Nick Williams
      Permalink to comment#

      Provided you don’t have lots of queries, each with lots of handlers attached it should be fine, but it really is circumstantial. If you have very complex or expensive logic you may temporarily lock up the browser. I’d suggest a little bit of exploratory testing to see what works for you.

      I also have a few ideas about how I can mitigate this, so keep an eye on the github project for updates

  12. Nice going…

    Might try to use it to play with declarative approaches to updating HTML attributes as described here:

    http://www.xanthir.com/blog/b4K_0

    http://andydavies.me/blog/2012/08/13/what-if-we-could-use-css-to-manipulate-html-attributes/

  13. I Saw this today as a trending item in Github….checked it out and was wondering what the point of it was…This article really helped make sense of this neat library…and helped me understand it’s use…

    Thank you…first for creating this library…and second for writing a helpful article written in plain english that is easy to understand….and clearly demonstrates some use cases…

    • Nick Williams
      Permalink to comment#

      Wow, didn’t even realise it was trending on GitHub until I saw your comment. What an honour!

      No problems, I created it as nothing more than an interesting experiment and thought maybe other people would find it useful. Good to see I wasn’t wrong, and that my writing isn’t all that bad :-)

  14. Joel
    Permalink to comment#

    Fantastic! I’ve been doing this sort of stuff my own, seat-of-the-pants way.

    You know that thing where you think “If I was smart, I’d create a library to abstract this out in a reusable way…or I could wait a week or two and someone else will have done it better”?

    That.

    I love this community.

    • Nick Williams
      Permalink to comment#

      Very kind words! Hopefully you won’t have to code on the seat of your pants anymore ;-)

  15. Permalink to comment#

    Just in time when I need it. Great job guys.

  16. Brian
    Permalink to comment#

    This is awesome! I’ve been needing a solution just like this. Thanks!

  17. Kevin L
    Permalink to comment#

    Was hoping more of a better image replacement system for responsive design with this library; it doesn’t seem to really be a solution for dynamic sites as you would essentially be uploading/making 2-3 versions of an image.

    Then again, it is JavaScript we’re talking about here, and the PHP (RESS, RWD+ Server-Side Components) solutions involving GD Library seems to be the reasonable way to go still for dynamic sites it seems.

    Looking for a RESS solution for ruby on rails, but haven’t found any; does anyone know any to share?

    • Kevin L
      Permalink to comment#

      Oops, couldn’t edit; I mean that I wish it had a solution that moved the state of ‘responsive-images’ forward outside of its pretty great core functionality. The shuffle content feature is pretty handy if you don’t want to bother with the CSS technicalities of achieving the effect that many like Trent Walton have provided great snippets about on Codepen.

    • Nick Williams
      Permalink to comment#

      enquire isn’t meant to deal with specific circumstances such as responsive images, but be general enough to deal with most circumstances involving media queries and javascript. This was a conscious decision to keep it small and light.

      I think until there is a concept of responsive images natively in browsers no solution can be considered anything more than a workaround. As far as i’m aware, any working spec for responsive images dictates a URL for each resolution (which would typically mean multiple images). So a solution with enquire may not be that far from how it might eventually work.

    • lozandier@gmail.com
      Permalink to comment#

      Understood. Recognized I spoke too soon, but could not delete my original comment. Looking forward to experimenting with Enquire.js; particularly the really handy faux media queries it allowes you to create.

  18. As to the patching, I generally do 960(~=1024) first, as the default (which goes to browsers that don’t support media queries) and go up for a 1140(>=1200px) view, as well as down for mobile & portrait tablets… This way I can avoid special size classes since the default will be fairly appropriate.

  19. Permalink to comment#

    Quick question, Nick: How is this vastly different from e.g. Modernizr’s `Modernizr.mq(‘only screen and (max-width: 768px)’)` test which tests the given media query, live against the current state of the window?

    • Nick Williams
      Permalink to comment#

      At its heart, there’s not much difference, if all you’re doing is matching and unmatching on page load. But enquire goes above and beyond this by allowing you to listen to browser events, calling the appropriate match/unmatch callback *only* when the state has changed, so you don’t need to manage that yourself. It also allows you to run one-time setup routines, either immediately or deferred until the first time a query matches. Finally it allows you to to short-circuit any queries so that if you’re adopting a mobile-first approach you can still deliver desktop experiences to old browsers like IE8.

  20. shreedhar
    Permalink to comment#

    You can also add some lines to identify the display is retina or not?

  21. Permalink to comment#

    Oh thank you, thank you!!!

  22. Andy White
    Permalink to comment#

    Fantastic stuff

  23. Peter
    Permalink to comment#

    Thanks! It was just what I needed! Easy to use as well…

    • Nick Williams
      Permalink to comment#

      Awesome, good to hear :) If you encounter any problems feel free to raise an issue on github

  24. Hi Nick

    Congrats for your script, I really liked it.

    I wonder you can improve ‘listen’ even more by using different access on “event timeout”. The method I create reacts even faster, here is my suggestion:

    /*
        Adds functionality to detect window finish resize event
        
        By: Arlind Nushi
    */
    jQuery(function($)
    {
        var win = $(window);    
        var win_timeout = null;
        var time_to_check = 300;
        
        win.on('resize', function()
        {
            var win_w = win.width();
            
            window.clearTimeout(win_timeout);
            win_timeout = null;
            
            win_timeout = setTimeout(function()
            {
                win.trigger('afterresize');
                
            }, time_to_check);
            
        });
    });
    

    I hope it will help you somehow.

    Its just a suggestion though.

    Keep up with the good work.

    Arlind Nushi from Laborator

    • Nick Williams
      Permalink to comment#

      Hey Arlind,

      Glad you liked :) Unfortunately enquire doesn’t have any dependencies on jQuery and I wouldn’t like to introduce a dependency to it. Thanks for offering help however.

      Nick

  25. Thanks so much for sharing this. JUST what I was looking for today! Worked great to re-order a set of divs for a mobile version of a site I’m working on.

    • Nick Williams
      Permalink to comment#

      Awesome! Good to hear it worked well for you :) Any feature suggestions or issues, feel free to hit me up on github.

  26. Permalink to comment#

    Nice and easy to understand!

    Have done it my own in the past, also with modernizr.eq with my own timer tiking this function over and over!

    Thanks for bringing this handy little code to the community!

    People like you are making the internet free and open minded!

    Go on like this, hope to see some other cool stuff of you in the future!

  27. thinsoldier

    Is there any way to use this that doesn’t require repeating the media query criteria in both the stylesheet and javascript files?

    For example, if my javascript simply makes use of dimensions defined in the stylesheet and all it does is re-evaluate the same code (with different widths defined in the stylesheet) every time a different media query is triggered, there’s really no need to repeat min-width and max-with criteria in the JS.

This comment thread is closed. If you have important information to share, you can always contact me.

*May or may not contain any actual "CSS" or "Tricks".