Categories:

CSS media query matching in JavaScript using window.matchMedia()

There may be times when, in addition to CSS, you also need to do something in JavaScript when a CSS media query is matched. While CSS purists may cower at the idea of mixing the two when it comes to handling media queries, at the end of the day, having JavaScript on board just means an additional tool in our "responsive" arsenal, and a very robust tool at that.

In JavaScript, you can detect for the same CSS media query string defined in CSS with the method window.matchMedia(). For example, in CSS, if you have the following CSS media query:

/* #### CSS that's applied when the viewing area's width is 765px or less #### */
@media screen and (max-width: 765px){
 /* CSS definitions here */
}

To detect the same media query in JavaScript, you'd do the following:

var mql = window.matchMedia("screen and (max-width: 765px)")

Notice the query string entered in both cases is identical with the exception of "@media" in the former case. That's the beauty of the JavaScript set up- it pretty much mirrors the construct of its CSS counterpart. window.matchMedia() returns a MediaQueryList object containing a couple of methods and properties, the most frequently used being the "matches" property. It's a Boolean property that returns true if the media query matches the state of the current window, and false if not. More on this later.

Browser compatibility wise, window.matchMedia() is supported in FF6+, IE10+, Chrome/ Safari, and Opera 12+. To test for browser support, you can simply test for support for the property window.matchMedia.

window.matchMedia() in detail

When you call window.matchMedia(), it returns a MediaQueryList object, which really is just an ordinary JavaScript object containing some useful methods and properties for us to examine the outcome of the match. Lets look at the returned properties first.

window.matchMedia() returned properties
Property Description
matches Boolean that returns true if the current state of the window matches the conditions defined in the CSS query string, or false if not.
media Returns the serialized media query list. In the case of the operation:

var mql = window.matchMedia("screen and (max-width: 765px)")

mql.media would return in NON IE browsers:

"screen and (max-width: 765px)"

and in IE (as of IE11) browsers, the same value, but with no space between a property and its value:

"screen and (max-width:765px)"

Furthermore, in IE, if a media is not specified (ie: "screen") inside the media query list, it returns "all" instead. So for the following operation:

var mql = window.matchMedia("(max-width: 765px)")

The property mql.media returns "(max-width: 765px)" in non IE browsers, but "all and (max-width:765px)" in IE instead.

Due to these differences, when probing the media property to determine the type of incoming media query, you should use regular expressions to equalize the slight differences in the return value between browsers.

The property you'll most frequently be referring to is "matches", which returns true when our constructed media query matches the current state of the window. The following easily checks at run time if the user's browser width is 800px or more:

var mql = window.matchMedia("screen and (min-width: 800px)")
if (mql.matches){ // if media query matches
 alert("Window is 800px or wider")
}
else{
 // do something else
}

Now, the above code only runs the desired CSS media query and compares it to the current window state once at run time, and doesn't react to any changes to the later thereafter. At this point the code is hardly responsive. In order to utilize window.matchMedia() in a responsive manner, we need to also make use of its methods/ event handlers:

window.matchMedia() returned methods/ event handlers
method Description
addListener( functionref) Adds a new listener function, which is executed whenever the state of the window changes and triggers a re-evaluation of the defined CSS media query.
removeListener(functionref) Removes a previously added listener function from listening in on changes between the current state and the defined CSS media query.

The key method to befriend is addListener(). By a function to wrap the code we want to run and enter it into addListener(), our function will now fire whenever any changes to the current window state leads to a match against the defined CSS media query. In other words, our code can now react to not just a CSS media query match at run time, but whenever the window state changes..

The following code uses addListener() to react to a CSS media query not just on run time, but also when any changes to the window state occur:

function mediaqueryresponse(mql){
 if (mql.matches){ // if media query matches
  console.log("The condition " + mql.media + " has been met")
 }
 else{
  console.log("Condition not met yet")
 }
}

var mql = window.matchMedia("screen and (max-device-width: 480px) and (orientation: portrait)")
mediaqueryresponse(mql) // call listener function explicitly at run time
mql.addListener(mediaqueryresponse) // attach listener function to listen in on state changes

Here we've defined a listener function mediaqueryresponse(). Notice how such a function is inexplicitly passed the MediaQueryList object from the call to window.matchMedia(), allowing our function to process the result. One curious line in the above code is the call to our mediaqueryresponse() function, in addition to passing it into addListener(). This may seem redundant, but it's important to note that addListener() only fires when the state of the window changes, which doesn't occur when the page first loads. The explicit call to mediaqueryresponse() ensures that this function is also executed when the page loads.

Now we have all the pieces in JavaScript to respond to a CSS media query. One caveat of bringing JavaScript into the equation is often the need to duplicate the media query twice in your page- once in your CSS @media rule, and again in JavaScript, depending on what you're trying to do. This could lead to maintenance issues, as it's easy to change one query but forget to update the same for the other. But that's really a small price to pay for the added robustness the language brings to sculpting responsive pages.

End of Tutorial