humanwhocodes.com Open in urlscan Pro
104.198.14.52  Public Scan

Submitted URL: http://bit.ly/2sd4HMP
Effective URL: https://humanwhocodes.com/blog/2012/01/19/css-media-queries-in-javascript-part-2/
Submission: On December 29 via manual from US — Scanned from DE

Form analysis 1 forms found in the DOM

Name: sideemailformPOST https://mailinglist.humanwhocodes.com/api/subscribe

<form method="post" name="sideemailform" action="https://mailinglist.humanwhocodes.com/api/subscribe">
  <div id="mc_embed_signup_scroll">
    <div style="margin-bottom: 0.5em">
      <label>First name: <input type="text" value="" style="width:100%" name="firstName">
      </label>
    </div>
    <!-- <div style="margin-bottom: 0.5em">
                    <label>Last name:
                        <input type="text" value="" style="width:100%" name="lastName">
                    </label>
                </div> -->
    <div>
      <label>Email address: <input type="email" value="" style="width:100%" name="email" required="">
      </label>
    </div>
    <input type="hidden" name="formId" value="1580135">
    <div class="center-text gutters">
      <input type="submit" value="Send my E-book">
    </div>
  </div>
</form>

Text Content

 * Skip to content
 * Books
 * Reading
   
 * Donate
 * Contact

--------------------------------------------------------------------------------


CSS MEDIA QUERIES IN JAVASCRIPT, PART 2

Posted at January 19, 2012 by Nicholas C. Zakas

Tags: CSS, JavaScript, Media Query

In my previous post1, I introduced using CSS media queries in JavaScript both
through a custom implementation and using the CSSOM Views matchMedia() method.
Media queries are incredibly useful, both in CSS and JavaScript, and so I
continued with my research to see how best to take advantage of this capability.
As it turns out, the matchMedia() method has a few interesting quirks that I
didn’t realize when I wrote the first part of this series.


MATCHMEDIA() AND ITS QUIRKS

Recall that matchMedia() returns a MediaQueryList object that allows you to
determine whether or not the given media type matches the current state of the
browser. This is done using the matches property, which returns a boolean. As it
turns out, matches is a getter, which requeries the state of the browser each
time it’s called:

var mql = window.matchMedia("screen and (max-width:600px)");
console.log(mql.matches);

//resize the browser

console.log(mql.matches);  //requeries


This is actually really useful, because it allows you to keep a reference to a
MediaQueryList object and repeatedly check the state of the query against the
page.

Chrome and Safari have a weird behavior, though. The initial value for matches
is always correct but doesn’t get updated by default unless the page has a media
block defined with the same query and at least one rule (hat tip: Rob Flaherty2.
For instance, in order for a MediaQueryList representing “screen and
(max-width:600px)” to update appropriately (including firing events), you must
have something like this in your CSS:

@media screen and (max-width:600px) {
    .foo { }
}


There needs to be at least one rule in the media block, but it doesn’t matter if
that rule is empty. As long as this exists on the page then the MediaQueryList
will be updated appropriately and any listeners added via addListener() will
fire when appropriate. Without this media block on the page, the MediaQueryList
acts like a snapshot of the page state at its creation time.3

You can fix this by adding a new rule using JavaScript:

var style = document.createElement("style");
style.appendChild(document.createTextNode("@media screen and (max-width:600px) { .foo {} }"));
document.head.appendChild(style);    //WebKit supports document.head


Of course, you would need to do that for every media query being accessed using
matchMedia(), which is a bit of a pain.

There is also a strange quirk in Firefox’s implementation. In theory, you should
be able to assign a handler for when the query state changes and not keep a
reference to the MediaQueryList object, such as:

//doesn't quite work in Firefox
window.matchMedia("screen and (max-width:600px)").addListener(function(mql) {
     console.log("Changed!");
});


When this pattern is used in Firefox, the listener may never actually be called
even though the media query has become valid. In my tests, it would fire between
0 and 3 times, and then never again. The Firefox team has acknowledged this is a
bug4 and should hopefully be fixed soon. In the meantime, you need to keep the
MediaQueryList reference around to ensure your listeners fire:

//fix for Firefox
var mql = window.matchMedia("screen and (max-width:600px)");
mql.addListener(function(mql) {
     console.log("Changed!");
});


The listener here will continue to be called as long as there is a reference to
the mql object.


MORE ON LISTENERS

My initial description of the media query listeners in my previous post was
incomplete due to a misunderstanding on my part. The listeners are actually
trigger in two instances:

 1. When the media query initially becomes valid. So in the previous example,
    when the screen becomes 600 pixels wide or less.
 2. When the media query initially becomes invalid. For example, when the screen
    becomes wider than 600 pixels.

This behavior is why the MediaQueryList object is passed into the listener, so
you can check matches to determine if the media query just became valid or not.
For example:

mql.addListener(function(mql) {
    if (mql.matches) {
        console.log("Matches now!");
    } else { 
        console.log("Doesn't match now!");
    }
});


Using code like this, you can monitor when a web application moves into and out
of certain states, allowing you to alter the behavior accordingly.


TO POLYFILL OR NOT?

When I first looked at matchMedia(), I did so with the intent of creating a
polyfill. Paul Irish5 implemented a polyfill using a technique similar to the
one I described in my last post (and gave me credit for it, thanks Paul!). Paul
Hayes then forked6 his work to create a polyfill with rudimentary listener
support based on a very ingenuous use of CSS transitions to detect changes.
However, as it relies on CSS transitions, the listener support is limited to
browsers with CSS transition support. That, coupled with the fact that calling
matches doesn’t requery the browser state, and the bugs in both Firefox and
WebKit, led me to believe that building a polyfill wasn’t the right approach.
After all, how can you polyfill appropriately when there are such obvious bugs
in the real implementations that need fixing?

My approach was to create a facade to wrap this behavior in an API where I could
smooth out the issues. Of course, I chose to implement the API as a YUI Gallery
module7 called gallery-media. The API is very simple and consists of two
methods. The first is Y.Media.matches(), which takes a media query string and
returns true if the media matches and false if not. No need to keep track of any
objects, just get the info:

var matches = Y.Media.matches("screen and (max-width:600px)");


The second method is Y.Media.on(), which allows you to specify a media query and
a listener to call when the media query becomes valid or invalid. The listener
is passed an object with matches and media properties to give you information
about the media query. For example:

var handle = Y.Media.on("screen and (max-width:600px)", function(mq) {
    console.log(mq.media + ":" + mq.matches);
});

//detach later
handle.detach();


Instead of using CSS transitions to monitor for changes, I use a simple onresize
event handler. On the desktop, the size of the browser window is the main thing
that will change (as opposed to mobile devices, where the orientation may also
change), so I made this simplifying assumption for older browsers. The API uses
the native matchMedia() functionality where available and patches up the
differences in WebKit and Chrome so that you get consistent behavior.


CONCLUSION

CSS media queries in JavaScript are a bit more complicated than I first
expected, but still quite useful. I don’t think it’s appropriate to polyfill
matchMedia() giving the strange bugs that are still abound, effectively
preventing you from even using the native code the same way across browsers. A
facade, on the other hand, insulates you from the bugs and changes that are
likely to occur going forward. Now go forth and use CSS media queries to their
potential…in JavaScript.


REFERENCES

 1. CSS media queries in JavaScript, Part 1 by me
 2. Rob Flaherty’s tweet
 3. matchMedia() MediaQueryList not updating
 4. matchMedia() listeners lost
 5. matchMedia polyfill by Paul Irish
 6. matchMedia polyfill by Paul Hayes
 7. YUI 3 Gallery Media module by me

Demystify JavaScript promises with the e-book that explains not just concepts,
but also real-world uses of promises.


DOWNLOAD THE FREE E-BOOK!

The community edition of Understanding JavaScript Promises is a free download
that arrives in minutes.

First name:
Email address:



Please enable JavaScript to view the comments powered by Disqus.


ADDITIONAL INFORMATION

ECO-Server von OVHcloud. Dedicated Server für kleine und mittlere Projekte zum
kleinen Preis.ads via Carbon


TOP SPONSORS

 * 


MY BOOKS

 * 
 * 
 * 
 * 


RECENT SNIPPETS

 * How to add npm packages for client-side use in Eleventy
 * Creating a new user with an SSH key on Linux
 * How to setup and deploy a web application on Dokku
 * How to regain Jenkins web access after being locked out
 * Create TypeScript declarations from JavaScript and JSDoc
 * How to read environment variables in Deno using JavaScript
 * How to validate the signature of a GitHub webhook using Node.js
 * How to generate ID attributes in headings using Eleventy
 * How to optionally await a JavaScript function call
 * Setting up Visual Studio Code intellisense for Jest globals


ARCHIVES

 * 2022
 * 2021
 * 2020
 * 2019
 * 2018
 * 2016
 * 2015
 * 2014
 * 2013
 * 2012
 * 2011
 * 2010
 * 2009
 * 2008
 * 2007
 * 2006
 * 2005
 * 2004

--------------------------------------------------------------------------------


ABOUT THE HUMAN

Hi, I'm Nicholas C. Zakas, an independent software developer living in Mountain
View, California. I've been a software architect at companies like Yahoo and
Box, as well as an author and speaker. I created the ESLint open source project
and wrote several books. At the moment, I'm recovering from Lyme disease and
haven't been able to leave my home much in the past five years. (Health update,
More about me)


ON THE WEB

 * Twitter
 * GitHub
 * Instagram
 * YouTube
 * LinkedIn
 * Slideshare
 * Amazon

Privacy Policy | Terms of Service

Copyright © 2004-2022 Human Who Codes LLC. Content licensed under a Creative
Commons Attribution-NonCommercial-ShareAlike 4.0 International License.
All code examples on all pages, unless otherwise indicated, are BSD licensed.
Some links may be affiliate links. We may get paid if you buy something or take
an action after clicking one of these. As an Amazon Associate we earn from
qualifying purchases. Blog Feed