Introducing enquire.js
For some time now, I’ve been using the enquire.js library to augment the use of media queries in JavaScript. I’ve had a lot of success using the library in various projects, but most developers I’ve met confessed they’ve never used or even heard of it.
In this article, I want to introduce you the library, explain why and how you’d want to use it.
What is enquire.js?
enquire.js is a lightweight, pure JavaScript library for responding to CSS media queries.
In my own words, enquire.js gives you additional functionality and flexibility on top of window.matchMedia()
when working with media queries in JavaScript.
The library was written by Nick Williams and it’s been around for about three years, having a healthy amount of open source activity. Its aim is not to replace or polyfill matchMedia
; rather, it exists to provide additional functionality around media queries that don’t exist natively.
Why Would I Use It?
There are lots of use cases where enquire.js could be used, including:
- Move nodes around the DOM
- Load supplementary content (e.g. ads) via Ajax
- Load and run a JavaScript library (e.g. Packery) to enhance a page
Each of these use cases is covered in the following sections.
DOM Manipulation
Although there are some performance and maintainability considerations with using JavaScript to manipulate the DOM, there are many reasons why would do this.
Let’s assume that on your blog, you have a bio with an image in a sidebar. On small screens, the sidebar is hidden – but you still want the bio visible at the end of your post. Instead of duplicating the bio (and image) markup, you could move it around the DOM depending on the screen size avoiding markup duplication. Here’s an example:
window.$ = document.querySelector.bind(document);
enquire.register("screen and (max-width:40em)", {
match: function() {
$(".Post-content").appendChild($(".Bio"));
},
unmatch: function() {
$(".Sidebar").insertBefore($(".Bio"), $(".Sidebar").firstChild);
}
});
A live demo of this example is shown below:
See the Pen MwNYjp by SitePoint (@SitePoint) on CodePen.
This is a rudimentary example, and honestly doesn’t do much more than what native matchMedia()
provides. In fact, here’s how you could do this with just matchMedia()
:
window.$ = document.querySelector.bind(document);
(function() {
var mq = window.matchMedia("(min-width:40em)");
mq.addListener(positionBio);
function positionBio(mediaQuery) {
if (mediaQuery.matches) {
$(".Sidebar").insertBefore($(".Bio"), $(".Sidebar").firstChild);
} else {
$(".Post-content").appendChild($(".Bio"));
}
}
// On load
positionBio(mq);
}());
A live demo of this example is shown below:
See the Pen WvVbor by SitePoint (@SitePoint) on CodePen.
Let’s get back to enquire.js and take the example from above a step further. In the next code we’ll use the setup
function. It’s a function that runs just once as soon as enquire.register
is executed. In the example we’ll also cache a few selectors to improve the performance of the code, and make it more readable and maintainable. While this is a contrived example (and you could do the same without enquire.js), it enables me to introduce you to the setup
function and to explore more complex examples:
window.$ = document.querySelector.bind(document);
enquire.register("screen and (max-width:40em)", {
setup: function() {
this.bio = $(".Bio");
this.content = $(".Post-content");
this.sidebar = $(".Sidebar");
},
match: function() {
this.content.appendChild(this.bio);
},
unmatch: function() {
this.sidebar.insertBefore(this.bio, this.sidebar.firstChild);
}
});
A live demo of this example is shown below:
See the Pen MwNYbp by SitePoint (@SitePoint) on CodePen.
Supplementary Content
Let’s focus our attention on another use case: loading ad content via Ajax depending on the size of the viewport. This example uses the setup()
function again and introduces another feature of enquire.js: deferSetup()
. It also uses the reqwest Ajax library (no typo here) and the classList
API to demonstrate how a more complex example could be written without jQuery. In case you don’t know what this API does, you can read the article Exploring the classList API.
In the next example, inside the setup
method (that I remind you that only runs once, when this media query matches), we use jQuery’s ajax()
method to request the page ad.html
and to store the result in an ad
property. Then, we set the content of .Ad
to the property and show the container by adding an is-visible
class (handled with CSS).
If the media query is ever “unmatched”, we’ll remove the is-visible
class that will, in turn, hide the .Ad
container.
Finally, we’ll use the deferSetup
flag along with the setup()
method so that the Ajax request only happens once, and only when the media query is matched. This is useful because the call to Ajax is delayed until the point the information is needed instead of making a premature request.
The code that implements what I’ve just described is reported below:
window.$ = document.querySelector.bind(document);
enquire.register("screen and (min-width:40em)", {
deferSetup: true,
setup: function() {
this.ad = "";
this.adContainer = $(".Ad");
this.visibleClass = "is-visible";
this.adUrl = "//codepen.io/damonbauer/pen/LVgxxN.html";
reqwest({ url: this.adUrl, crossOrigin: true})
.then(function(response) {
this.ad = response;
}.bind(this)).fail(function() {
this.ad = "Ad could not be loaded.";
}.bind(this)).always(function(response) {
this.adContainer.innerHTML = this.ad;
}.bind(this));
},
match: function() {
this.adContainer.classList.add(this.visibleClass);
},
unmatch: function() {
this.adContainer.classList.remove(this.visibleClass);
}
});
In addition to the previous JavaScript code, we also have to use the small CSS snippet reported below:
.Ad {
display: none;
}
.is-visible {
display: block;
}
A live demo of this example is shown below:
See the Pen vOoEyb by SitePoint (@SitePoint) on CodePen.
Page Enhancements
In this section, we’ll take a look at adding the Packery library to enhance a page at larger sizes. According to its documentation, Packery “fills empty gaps”. It helps you accomplish something similar to Pinterest’s layout, where cards fill available space in a grid.
Let’s say that you have the following HTML in your page:
<div id="Container">
<div class="Item">...</div>
<div class="Item">...</div>
<div class="Item">...</div>
...
</div>
The following code will use the packery library to achieve what I previously described:
enquire.register("screen and (min-width:30em)", {
deferSetup: true,
setup: function() {
this.container = $( "#Container" );
},
match: function() {
this.container.packery({
itemSelector: '.Item'
});
},
unmatch: function() {
this.container.packery('destroy');
}
});
A live demo of this example is shown below:
See the Pen OVKPWL by SitePoint (@SitePoint) on CodePen.
This is pretty similar to the first example. It starts by storing a container as a property in the setup()
method. Then, if the media query matches, it’ll layout items following the packery
convention; otherwise we destroy the packery
instance by calling its destroy
method.
A potential use case for this example might be a tablet being rotated from landscape to portrait. Perhaps we’ve determined that the screen is too narrow to display the items in a certain way, so we can opt for the items to be stacked vertically.
Potential Pain Points
Hopefully, you’ve gotten a glimpse at how quick and powerful enquire.js can be to use. I’d like to share a couple of instances where I’ve run into problems when using the library.
Old Versions of Internet Explorer
While it doesn’t fully support IE8, I can tell you that sites I built are don’t have any render-breaking JavaScript errors. Enquire.js depends on the matchMedia
API, which is not supported in versions of Internet Explorer prior to 10. In case you have to support such browsers, I suggest you to use the matchMedia polyfills written by Paul Irish.
To do so, you have to load the files provided before enquire.js. Moreover, I suggest you to place these scripts in an IE conditional comment as shown below:
<!--[if lte IE 9]>
<script src="/path/to/matchMedia.js"></script>
<script src="/path/to/matchMedia.addListener.js"></script>
<![endif]-->
Thanks to this approach, only the browsers who need the polyfills will load them. It’s important to highlight that even if you load them in browsers supporting these features, you’ll still have the native implementation as both the scripts are written in a non-invasive way. This means that before executing any code, they detect if the browser supports the features natively. If that’s the case, the polyfill code is skipped altogether.
Manipulating the DOM
In one of the examples above, I showed how to use enquire.js to manipulate the DOM. While this technique works, I would advise a healthy bit of caution in employing it. Watch out for adding lots of DOM changes in multiple enquire.js match
and unmatch
methods as the more operations you add, the harder it’ll become to debug the page if something goes wrong. In addition, since manipulating the DOM is usually slow, using this technique can lead to page jank and poor performance.
Progressive Content
My suggestion is to use enquire.js to “progressively” provide content or functionality, instead of rely on it to show critical parts of content. My advice is to initially show basic content and then enhance it with enquire.js, rather than depending on all your content being enhanced.
In case of the page enhancements’ example, the grid of items is visible by default; if the screen is large enough, the items are laid out using the Packery.js library. This ensures that if for whatever reason (e.g. broken JavaScript, slow connection, and so on) the JavaScript isn’t executed, a user can still consume the content.
Mobile First
It’s a common and suggested practice these days to build sites using a mobile-first approach. If a browser you need to support doesn’t understand media queries (such as IE8), enquire.js has a parameter called shouldDegrade
that comes in handy. It specifies that the result of the enquire.register
block should always be executed if the browser doesn’t understand media queries. By default, it’s value is set to false
but you can change it to true
to fit your needs.
An example of use of this parameter is reported below:
enquire.register("screen and (min-width:40em)", function() {
// execute some code for large-screen devices
}, true); // note the true!
By using snippets like this, you can still build mobile-first websites and support older browsers to have a desktop experience. My advise is to use this parameter or the polyfills mentioned above, not both.
Conclusions
In this article I’ve introduced you to enquire.js, a powerful library written in pure JavaScript for responding to CSS media queries. Hopefully, I’ve demonstrated you how it gives you extra features on top of the native matchMedia()
method to allow you to build performant, feature-rich websites across all screen sizes. While this has not been an exhaustive overview of the library, this should be enough to push you to learn more about it and to think if there’s a place for it in your projects.
Have you ever heard about enquire.js? Have you ever tried to use it in your projects? What about your results? Share your comments below and let’s start a discussion!