The Lowdown

What is it?

enquire.js is a lightweight, pure JavaScript library for responding to CSS media queries.

  • JavaScript callbacks for media queries matching and unmatching.
  • Clean & intuitive API
  • Absolutely tiny - around 0.8kb minified & gzipped!

Why should I use it?

In responsive design, CSS media queries can only take you so far. enquire.js allows you to harness media queries in JavaScript, giving you the tools to create advanced responsive sites.

Dependencies?

None! Not even jQuery.

The most you will need to do is provide a matchMedia polyfill if you wish to support old/incapable browsers.

Downloads & All That Jazz

Latest Build

Grab the latest code, ready to go, from one of the locations below:

If you wish to browse/fork the source, you can do so on GitHub.

Install via Bower

To install via the bower package manager, enter the following at the command line:

bower install enquire

Build From Source

If you would like to build from source (and run all unit tests etc.), enter the following at the command line:

git clone git://github.com/WickyNilliams/enquire.js.git
cd enquire.js
npm install
grunt

Quick Start

The main method you will be dealing with is register. Its signature is as follows:

enquire.register("screen and (max-width:45em)", {

    // OPTIONAL
    // If supplied, triggered when a media query matches.
    match : function() {},      
                                
    // OPTIONAL
    // If supplied, triggered when the media query transitions 
    // *from a matched state to an unmatched state*.
    unmatch : function() {},    
    
    // OPTIONAL
    // If supplied, triggered once, when the handler is registered.
    setup : function() {},    
                                
    // OPTIONAL, defaults to false
    // If set to true, defers execution of the setup function 
    // until the first time the media query is matched
    deferSetup : true,
                                
    // OPTIONAL
    // If supplied, triggered when handler is unregistered. 
    // Place cleanup code here
    destroy : function() {}
      
});

This should be enough to get you going, but please read on for a more detailed guide.

Walkthrough

The easiest way to learn is by example. So with that in mind, let's work through a problem using enquire.js to solve our issues. We won't have full code for the examples, as the details are less important than the concepts at play.

The Scenario

We're developing a website with using a mobile-first approach. For small viewports we want a single column containing just the main content. For larger viewports we want to display a sidebar containing supplementary content. A large portion of our user base accesses the site on mobile devices with slow connections - so it's required that the page weight be kept to a minimum.

Normally we would use CSS media queries for showing and hiding the sidebar, but this has been ruled out by that final requirement. If performance is paramount we shouldn't be loading content needlessly! So the only other option is to use a JavaScript-based solution. In the past, such an approach would probably be unreliable and ugly. Thankfully, times have changed…

Match

enquire.js makes solving this problem trivial. Let's say that we want the sidebar displayed at 45em. A simple solution would be as follows:

enquire.register("screen and (min-width:45em)", function() {
    // Load sidebar content in via AJAX.
    // Show sidebar
});

By supplying a function to register, you will be able to respond to a media query being matched. Therefore, when the query is matched we will load in content and insert it into the DOM to be displayed. Problem solved? Almost, but not quite!

Unmatch

We also need to handle the case where the query goes from a matched state to an unmatched state. For this we can supply an object to register, instead of a function. This gives us a lot more power and allows us to handle unmatching:

enquire.register("screen and (min-width: 40em)", {
    match : function() {
        // Load sidebar content in via AJAX.
        // Show sidebar
    },  
    unmatch : function() {
        // Hide the sidebar
    }
});

In a lot of circumstances this is an adequate and complete solution. However, for our scenario we can do better.

Setup

There's a problem with the above solution. What happens if the query gets matched a second time? We needlessly make a second AJAX request. We could put a check in our match callback to see if the content has already been loaded, but that sucks because match no longer has a single responsibility. This is where setup comes in.

enquire.register("screen and (min-width: 45em)", {
    setup : function() {
        // Load in content via AJAX (just the once)
    },
    match : function() {
        // Show sidebar
    },
    unmatch : function() {
        // Hide sidebar
    }
});

Setup is run once, as soon as a handler is registered. So now all our one-time code is in a setup callback, meaning no needless AJAX requests. This gives a neat separation of concerns, freeing the match callback from handling any upfront work.

Deferring Setup

So now we've got to a point were our task is almost complete. Content is dynamically loaded in at setup, and the sidebar shown and hidden on match and unmatch respectively. But we can still make one further improvement! By default setup is executed as soon as the query is registered. What this means is that our AJAX request is being made even for small viewports - where it will never be needed because the content will never be shown! The solution to this problem is simple: deferSetup.

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

    deferSetup : true,
    setup : function() {
        // load content via AJAX
    },
    match : function() {
        // show sidebar
    },
    unmatch : function() {
        // hide sidebar
    }  

});

And with that simple adjustment, we now make our AJAX request just once, the first time the media query is matched!

Delving Deeper

Multiple Handlers

As the complexity of your site increases, it can get cumbersome to have all your logic in one handler. enquire allows you to register multiple handlers for a media query, so you can divide functionality into logical chunks. Supply an array as the second parameter to register to have multiple handlers per media query:

// You can pass an array containing your handlers
enquire.register("screen and (min-width: 45em)", [
    { match : function() { console.log("handler 1 matched"); } },
    { match : function() { console.log("handler 2 matched"); } }
]);
 
// Or you can make multiple calls to register
var query = "screen and (min-width:45em)";
enquire.register(query, function() { console.log("handler 3 matched"); });

// then later in code...
enquire.register(query, function() { console.log("handler 4 matched"); });

Multiple Queries

Often you will need more than a single media query, and for that reason enquire allows multiple queries to be registered. Calls to register can be chained together for this purpose.

enquire
.register("screen and (max-width:50em)", function() { 
    console.log("handler 1 matched");
})
.register("screen and (max-width:40em)", function() {
    console.log("handler 2 matched");
});

Unregister Handlers

Sometimes you may wish to unregister a specific handler, or group of handlers. For that we have the unregister method. unregister accepts a media query as it's first parameter, and optionally, a reference to a specific handler as it's second parameter. If the second parameter is omitted all handlers for the supplied media query are unregistered. When a handler is unregistered its destroy callback is executed. If a handler does not have a destroy callback then the unmatch callback is fired instead. Once a handler is unregistered it will no longer respond to changes to a media query's state.

var query1 = "screen and (min-width: 40em)",
    query2 = "screen and (min-width: 50em)",
    handler1 = {
        match : function() {},
        destroy : function() { console.log("handler 1 destroyed"); }
    },
    handler2 = {
        match : function() {},
        unmatch : function() { console.log("handler 2 unmatched"); }
    };

enquire.register(query1, handler1);
enquire.unregister(query1); // "handler 1 destroyed"

enquire.register(query2, handler2);
enquire.unregister(query2, handler2); // "handler 2 unmatched"

Mobile-First

If you're taking a mobile-first approach you will typically run into issues with incapable legacy browsers not understanding CSS3 media queries, meaning that browsers such as IE8 will be served the mobile versions of the site. Again, enquire has you covered here!

register can accept an optional third parameter, shouldDegrade. When this is passed as true (it defaults to false) it signifies to enquire that if the browser is incapable of understanding CSS3 media queries, then always consider this query a match.

enquire.register("screen and (min-width:40em)", function() {
    
    //execute some code for large-screen devices

}, true); // note the `true`!

Because this only affects incapable browsers, modern browsers will respect the media query and behave as expected. This allows you to adopt the mobile-first paradigm, whilst still serving a desktop experience to incapable browsers.

Legacy Browsers

enquire relies on the matchMedia API. It utilises both matchMedia and matchMedia.addListener. Unfortunately the matchMedia API isn't universally supported in browsers. Fear not, there are polyfills available which provide various levels of support. Which polyfill you choose is dependent on how much support you need.

Basic support

You can use Paul Irish/Scott Jehl's matchMedia polyfill (both matchMedia and matchMedia.addListener) if you just wish to support CSS3 capable browsers who do not implement the matchMedia API (IE9 and Opera 12.0 are the main examples). Use this in combination with enquire's shouldDegrade flag to offer a short-circuit for earlier browsers.

Deep support

If you wish to give full support to incapable browsers you can use David Knight's media-match polyfill. With this you do not need to use shouldDegrade, everything will work exactly as you would expect in more capable browsers. This has been tested with enquire and works all the way back to IE6.

If you are using Modernizr (and you should be!) it's as easy as this to asynchronously load your polyfills:

Modernizr.load([
    //first test need for polyfill
    {
        test: window.matchMedia,
        nope: "/path/to/polyfill.js"
    },

    //and then load enquire
    "/path/to/enquire.js",
    "/path/to/your/script.js"
]);

This is the approach that this site adopts.

API

enquire.register( mediaQuery, handler )

mediaQuery (Required)
string
The media query you wish to respond to
handler (Required)
function or object

A function to handle the media query being matched, or an object to handle more advanced scenarios

enquire.unregister( mediaQuery[, handler] )

mediaQuery (Required)
string
The media query you wish to unregister.
handler (Optional)
function or object
If supplied, only this handler will be unregistered

handler object

deferSetup (Optional)
boolean
Flag to determine whether setup function should be deferred until the media query is first matched. Defaults to false
setup (Optional)
function

A setup function is run just once. By default it will be called as soon as the handler is registered. If deferSetup is set to true setup will instead be called the first time the media query is matched.

match (Optional)
function
The callback to handle the media query being matched
unmatch (Optional)
function
The callback to handle the media query being unmatched

Examples

Changelog

v2.1.0 (2013-09-13)

  • Cumulative bug fixes
  • UMD support

v2.0.0 (2013-04-17)

  • Tinier than ever — now only 0.8kb minified & gzipped
  • Improved performance — resize events replaced with matchMedia.addListener
  • Simplified API — listen and fire no longer required

If you are upgrading from v1, be aware that there are some small breaking changes.

listen and fire are no longer needed as of v2, and have been dropped from the API. Removing any usage of them in your code is all that is required. Also, as enquire no longer relies on resize events you must make sure your polyfill supports matchMedia.addListener. Details of this can be found in the legacy browser section.

v1.5.6 (2013-01-30)

  • Fix bug with missing useCapture parameter on addEventListener

License

Licensed under the MIT License.