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.
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.
Wow thank you for this it’s exactly what i’m looking for!
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?
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.
There are polyfills available to make media queries work on IE8. This would also provide matchMedia capabilities for IE8.
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
I much prefer leaving all the breakpoints in CSS and simply using a pseudo content on the body tag like Cid recommends here:
https://davidwalsh.name/device-state-detection-css-media-queries-javascript#comment-73889
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) {
…
Awesome comment! Thanks, that was exactly happening to me.
Check out https://www.responsivejavascript.com/, which sums up a couple libraries you can use to do it for you.
https://github.com/ryanve/actual can test media queries and also find the actual breakpoints.
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…
https://jsfiddle.net/Freizeitler/AH3VJ/
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.
Is there a way that this is working on projector screen (and not opera browser in full screen mode) ?
Sample code doesn’t work at all in Chrome without any error as Daniel Premo pointed out.
Thanks Daniel,
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’);
}
Hi guys. I tested the jsbin provided in the article and it does exactly what I wanted. Just open and shrink the right panel https://jsbin.com/paqegexe/26/edit
I want to use break media css how to use that tell me somebody