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 April 13 via manual from NL — Scanned from NL
Effective URL: https://humanwhocodes.com/blog/2012/01/19/css-media-queries-in-javascript-part-2/
Submission: On April 13 via manual from NL — Scanned from NL
Form analysis
1 forms found in the DOMName: sideemailform — POST 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>
<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 * Coaching * 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. 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: ADDITIONAL INFORMATION Use AI to craft eye-catching marketing assets that stand out. Get started today.ads via Carbon TOP SPONSORS * MY BOOKS * * * * RECENT SNIPPETS * Mimicking __dirname and __filename in ESM modules in Node.js * 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 ARCHIVES (19 YEARS) * 2023 * 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 * Mastodon * Twitter * GitHub * Instagram * YouTube * LinkedIn * Slideshare * Amazon Privacy Policy | Terms of Service Copyright © 2020-2023 Human Who Codes LLC. Content licensed under a Creative Commons Attribution-NonCommercial-NoDerivs 3.0 Unported License. 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