What’s the first thing that comes to mind when you think of media queries? Maybe something in a CSS file that looks like this:
body {
background-color: plum;
}
@media (min-width: 768px) {
body {
background-color: tomato;
}
}
CSS media queries are a core ingredient in any responsive design. They’re a great way to apply different styles to different contexts, whether it’s based on viewport size, motion preference, preferred color scheme, specific interactions and, heck, even certain devices like printers, TVs and projectors, among many others.
But did you know that we have media queries for JavaScript too? It’s true! We may not see them as often in JavaScript, but there definitely are use cases for them I have found helpful over the years for creating responsive plugins, like sliders. For example, at a certain resolution, you may need to re-draw and recalculate the slider items.
Working with media queries in JavaScript is very different than working with them in CSS, even though the concepts are similar: match some conditions and apply some stuff.
Using matchMedia()
To determine if the document matches the media query string in JavaScript, we use the matchMedia()
method. Even though it’s officially part of the CSS Object Model View Module specification which is in Working Draft status, the browser support for it is great going as far back as Internet Explorer 10 with 98.6% global coverage.
This browser support data is from Caniuse, which has more detail. A number indicates that browser supports the feature at that version and up.
Desktop
Chrome | Firefox | IE | Edge | Safari |
---|---|---|---|---|
9 | 6 | 10 | 12 | 5.1 |
Mobile / Tablet
Android Chrome | Android Firefox | Android | iOS Safari |
---|---|---|---|
126 | 127 | 3 | 5.0-5.1 |
The usage is nearly identical to CSS media queries. We pass the media query string to matchMedia(
) and then check the .matches
property.
// Define the query
const mediaQuery = window.matchMedia('(min-width: 768px)')
The defined media query will return a MediaQueryList
object. It is an object that stores information about the media query and the key property we need is .matches
. That is a read-only Boolean property that returns true
if the document matches the media query.
// Create a media condition that targets viewports at least 768px wide
const mediaQuery = window.matchMedia('(min-width: 768px)')
// Check if the media query is true
if (mediaQuery.matches) {
// Then trigger an alert
alert('Media Query Matched!')
}
That’s the basic usage for matching media conditions in JavaScript. We create a match condition (matchMedia()
) that returns an object (MediaQueryList
), check against it (.matches
), then do stuff if the condition evaluates to true. Not totally unlike CSS!
But there’s more to it. For example, if we were change the window size below our target window size, nothing updates the way it will with CSS right out of the box. That’s because .matches
is perfect for one-time instantaneous checks but is unable to continuously check for changes. That means we need to…
Listen for changes
MediaQueryList has an addListener()
(and the subsequent removeListener()
) method that accepts a callback function (represented by the .onchange
event) that’s invoked when the media query status changes. In other words, we can fire additional functions when the conditions change, allowing us to “respond” to the updated conditions.
// Create a condition that targets viewports at least 768px wide
const mediaQuery = window.matchMedia('(min-width: 768px)')
function handleTabletChange(e) {
// Check if the media query is true
if (e.matches) {
// Then log the following message to the console
console.log('Media Query Matched!')
}
}
// Register event listener
mediaQuery.addListener(handleTabletChange)
// Initial check
handleTabletChange(mediaQuery)
The one-two punch of matchMedia()
and MediaQueryList
gives us the same power to not only match media conditions that CSS provides, but to actively respond to updated conditions as well.
When you register an event listener with addListener()
it won’t fire initially. We need to call the event handler function manually and pass the media query as the argument.
The old way of doing things
For the sake of context — and a little nostalgia — I would like to cover the old, but still popular, way of doing “media queries” in JavaScript (and, yes, those quotes are important here). The most common approach is binding a resize
event listener that checks window.innerWidth
or window.innerHeight
.
You’ll still see something like this in the wild:
function checkMediaQuery() {
// If the inner width of the window is greater then 768px
if (window.innerWidth > 768) {
// Then log this message to the console
console.log('Media Query Matched!')
}
}
// Add a listener for when the window resizes
window.addEventListener('resize', checkMediaQuery);
Since the resize event is called on each browser resize, this is an expensive operation! Looking at the performance impact of an empty page we can see the difference.
An even simpler way to see the difference is with the help of a console log.
Even if we look past the performance issues, resize is restrictive in the sense that it doesn’t let us write advanced media queries for things like print and orientation. So, while it does mimic “media query” behavior by allowing us to match viewport widths, it’s incapable of matching much of anything else — and we know that true media queries are capable of so much more.
Conclusion
That’s a look at media queries in JavaScript! We explored how matchMedia()
allows us to define media conditions and examined the MediaQueryList
object that lets us do one-time (.matches
) and persistent (addListener()
) checks against those conditions so that we can respond to changes (.onchange
) by invoking functions.
We also saw the “old” way of doing things by listening for resize
events on the window. While it’s still widely used and a totally legit way to respond to changes to the size of the window.innerWidth
, it’s unable to perform checks on advanced media conditions.
To finish the article here is a useful example that is not achievable in the old way. Using a media query I will check if the user is in the landscape mode. This approach is common when developing HTML5 games and is best viewed on a mobile device.
Thank you for explaining this useful way of using js media quiries.
I like it, I was always using the “old” method
.match is not necessary to check a Boolean
How can this be used in React? I’ve been looking for an elegant way to make my components responsive without using css. I don’t fancy calling myself a React dev and still using css media queries instead of all js. Please weigh in on this. Thanks
You can use the
useState
hook and bind the state set function to the event listener. Therefore, it will set the value on each change and the state will be reactive. Just add the initial check and setup in theuseEffect
hook.Just an idea, I don’t use React a lot.
I am a React developer, but I still think using CSS media queries instead of JS has better performance than waiting for the browser/component to be mounted and getting the viewport width/height after that.
There is no doubt about what is better, they shouldn’t be event compared. This is just a new optimized way to solved old problems,
resize
event plus checking the width of the browser.Today I learned about events comes along with match media….thanks Amit for that…orientation media in its own has some issues I think…it will not work properly when virtual keypad is open….
That is true, unfortunately, that is an old CSS problem. Here is an explanation of why.
If you look at the spec provided by W3 it says:
Spec: https://www.w3.org/TR/css3-mediaqueries/#orientation
This is why this problem can occur sometimes. This can be improved by writing a different query like using
min-aspect-ratio
Base on your post, I created little module, can be use in a good way as we normally needs in developments.
https://github.com/bzaman/JavaScript-Media-Queries/blob/master/README.md
Thanks for an article. On improvement can be added here. Rather using specific method
addListener
which is marked as deprecated, we can use native Event interface and just set it in this waymediaQuery.addEventListener('change', handleTabletChange)
.Deprecated is brought up a lot for
addListener
but actually it is not, AFAIK. Even if you check the MDN page it doesn’t mark it as one.Looking at the CSSWG dom they state:
This is how I use it, basically an aliase that is backwards compatible.
But you are not wrong, adding an event listener for
change
is what is actually happening and your code will work.@marco it is now: https://developer.mozilla.org/en-US/docs/Web/API/MediaQueryList/addListener#browser_compatibility
Sorry for the post spam, but I just saw now that
addListener
is supported in IE10 and up while theaddEventListener
is also supported in IE9, which seems to actually broaden the support.Am I missing something or does the resize total 3498? Good article though.
Well, someone has a sharp eye. Great catch. It is because numbers are float and we are only seeing interger. When summed it added up to a 1 (I guesses).
What about using CSS variables? Something like that:
@media (min-width: 450px) {
:root {
–actualMedia: small;
}
}
and then reading value of –actualMedia variable in JS:
if (getComputedStyle(document.documentElement)
.getPropertyValue(‘–actualMedia’)
.trim() === “small”) {
// …
}
I saw this solution some time ago. Back then it was something like
body:after {content: "mq-small"
and the new one with CSS Custom Properties is even better but won’t work in IE 11. But I’not sure which solution is better from performance perspective?Some time ago I found about this as well and I created a library to help using matchMedia across the board easily, if you want to check it out: https://github.com/enmanuelduran/mediaquerysensor
what is
e
in the function?@Mohammed that is the event object, in this case the object return in
mediaQuery
In a standard use of @media we can do something like this:
@media only screen and (max-width: 479px) and (orientation: portrait) {
// CSS
}
@media only screen and (min-width: 480px) and (max-width: 768px) {
// CSS
}
@media only screen and (min-width: 769px) and (max-width: 1280px) {
// CSS
}
@media only screen and (min-width: 1281px) and (max-width: 1599px) {
// CSS
}
Can something similar be done with your “matchMedia” approach? Your example only shows one resolution (min-width: 768px) so I’m left wondering if this is a limitation. Would like to know if this is possible. Thanks!
Thanks for this post, it helped me a lot today.
I would suggest just an update as “addListener” is deprecated.
Best regards and thanks again.