10

I've been searching for a lightweight, flexible, cross-browser solution for accessing CSS Media Queries in JavaScript, without the CSS breakpoints being repeated in the JavaScript code.

CSS-tricks posted a CSS3 animations-based solution, which seemed to nail it, however it recommends using Enquire.js instead.

Enquire.js seems to still require the Media Query sizes to be hardcoded in the script, e.g.

enquire.register("screen and (max-width:45em)", { // do stuff }

The Problem

All solutions so far for accessing Media Queries in Javascript seem to rely on the breakpoint being hardcoded in the script. How can a breakpoint be accessed in a way that allows it to be defined only in CSS, without relying on .on('resize')?

Attempted solution

I've made my own version that works in IE9+, using a hidden element that uses the :content property to add whatever I want when a Query fires (same starting point as ZeroSixThree's solution):

HTML

<body>
    <p>Page content</p>
    <span id="mobile-test"></span>
</body>

CSS

#mobile-test {
    display:none;
    content: 'mq-small';
}
@media screen only and (min-width: 25em) {
    #mobile-test {
        content: 'mq-medium';
    }
}
@media screen only and (min-width: 40em) {
    #mobile-test {
        content: 'mq-large';
    }
}

JavaScript using jQuery

// Allow resizing to be assessed only after a delay, to avoid constant firing on resize. 
var resize;
window.onresize = function() {
    clearTimeout(resize);
    // Call 'onResize' function after a set delay
    resize = setTimeout(detectMediaQuery, 100);
};

// Collect the value of the 'content' property as a string, stripping the quotation marks
function detectMediaQuery() {
    return $('#mobile-test').css('content').replace(/"/g, '');
}

// Finally, use the function to detect the current media query, irrespective of it's breakpoint value
$(window).on('resize load', function() {
    if (detectMediaQuery() === 'mq-small') {
        // Do stuff for small screens etc
    }
});

This way, the Media Query's breakpoint is handled entirely with CSS. No need to update the script if you change your breakpoints. How can this be done?

Timmah
  • 1,283
  • 2
  • 10
  • 26
  • What is the purpose of your `window.onresize` handler function? It appears to debounce your `detectMediaQuery` function by 100ms, but that function does nothing but return a string.. which isn't even used by the handler. – Alex McMillan Jun 22 '17 at 01:35
  • It's certainly not ideal, that particular piece of the code was appropriated from [this question](https://stackoverflow.com/questions/5489946/jquery-how-to-wait-for-the-end-of-resize-event-and-only-then-perform-an-ac#answer-5926068) – Timmah Jun 22 '17 at 12:12
  • I'm not saying it's not ideal, I'm saying it looks like completely dead code, muddying up your question. – Alex McMillan Jun 22 '17 at 21:44
  • I meant my code wasn't ideal :) but thanks for the info – Timmah Jun 22 '17 at 22:36

4 Answers4

13

try this

const mq = window.matchMedia( "(min-width: 500px)" );

The matches property returns true or false depending on the query result, e.g.

if (mq.matches) {

  // window width is at least 500px
} else {
  // window width is less than 500px
}

You can also add an event listener which fires when a change is detected:

// media query event handler
if (matchMedia) {
  const mq = window.matchMedia("(min-width: 500px)");
  mq.addListener(WidthChange);
  WidthChange(mq);
}

// media query change
function WidthChange(mq) {
  if (mq.matches) {
    // window width is at least 500px
  } else {
    // window width is less than 500px
  }

}
Francesco Taioli
  • 2,687
  • 1
  • 19
  • 34
  • 2
    Thanks @Taioli, but this still involves hardcoding the breakpoint value into the JavaScript. I'm trying to find out how to achieve this result *without* hardcoding breakpoint values – Timmah Sep 27 '17 at 12:09
  • 4
    Years later, I still don't see why this is being upvoted when it misses the main points of the question - not hardcoding breakpoint values in JavaScript. – Timmah Nov 25 '19 at 23:52
  • Still being upvoted, still misses the question. SO at work ;p – Timmah Sep 01 '21 at 06:59
  • MediaQueryList.addListener() is deprecated. Ref: https://developer.mozilla.org/en-US/docs/Web/API/MediaQueryList/addListener – Ken May 24 '22 at 15:22
4

See this post from expert David Walsh Device State Detection with CSS Media Queries and JavaScript:

CSS

.state-indicator {
    position: absolute;
    top: -999em;
    left: -999em;
}
.state-indicator:before { content: 'desktop'; }

/* small desktop */
@media all and (max-width: 1200px) {
    .state-indicator:before { content: 'small-desktop'; }
}

/* tablet */
@media all and (max-width: 1024px) {
    .state-indicator:before { content: 'tablet'; }
}

/* mobile phone */
@media all and (max-width: 768px) {
    .state-indicator:before { content: 'mobile'; }
}

JS

var state = window.getComputedStyle(
    document.querySelector('.state-indicator'), ':before'
).getPropertyValue('content')

Also, this is a clever solution from the javascript guru Nicholas C. Zakas:

  // Test a media query.
  // Example: if (isMedia("screen and (max-width:800px)"){}
  // Copyright 2011 Nicholas C. Zakas. All rights reserved.
  // Licensed under BSD License.
  var isMedia = (function () {

    var div;

    return function (query) {

      //if the <div> doesn't exist, create it and make sure it's hidden
      if (!div) {
        div = document.createElement("div");
        div.id = "ncz1";
        div.style.cssText = "position:absolute;top:-1000px";
        document.body.insertBefore(div, document.body.firstChild);
      }

      div.innerHTML = "_<style media=\"" + query + "\"> #ncz1 { width: 1px; }</style>";
      div.removeChild(div.firstChild);
      return div.offsetWidth == 1;
    };
  })();
Timmah
  • 1,283
  • 2
  • 10
  • 26
itacode
  • 3,721
  • 3
  • 21
  • 23
  • Thanks, the top JS solution looks slick, however it still relies on hardcoding the query breakpoint. The second solution looks spot on, however David Walsh's solution addresses the issue of browser support via using `z-index` rather than `content`, then reassigns each z-index value to the desired text value via a JS object – Timmah Jun 22 '17 at 12:24
  • It appears you can also use `body:before` (at least in Chrome) if you don't want to add the `.state-indicator` element to the DOM. – diachedelic May 01 '19 at 00:02
  • 1
    These are all clever workarounds, but isn't there an "official" way to know which media queries are active in the CSS? – Kokodoko Jun 30 '21 at 12:25
2

I managed to get the breakpoint values by creating width rules for invisible elements.

HTML:

<div class="secret-media-breakpoints">
    <span class="xs"></span>
    <span class="tiny"></span>
    <span class="sm"></span>
    <span class="md"></span>
    <span class="lg"></span>
    <span class="xl"></span>
</div>

CSS:

$grid-breakpoints: (
    xs:   0,
    tiny: 366px,
    sm:   576px,
    md:   768px,
    lg:   992px,
    xl:   1200px
);

.secret-media-breakpoints {
    display: none;

    @each $break, $value in $grid-breakpoints {
        .#{$break} {
            width: $value;
        }
    }
}

JavaScript:

app.breakpoints = {};
$('.secret-media-breakpoints').children().each((index, item) => {
    app.breakpoints[item.className] = $(item).css('width');
});
WoodrowShigeru
  • 1,418
  • 1
  • 18
  • 25
1

I found an hackish but easy solution :

@media (min-width: 800px) {
.myClass{
    transition-property: customNS-myProp;
}

this css property is just a markup to be able to know in JS if the breaking point was reached. According to the specs, transition-property can contain anything and is supported by IE (see https://developer.mozilla.org/en-US/docs/Web/CSS/transition-property and https://developer.mozilla.org/en-US/docs/Web/CSS/custom-ident).

Then just check in js if transition-property has the value. For instance with JQuery in TS :

const elements: JQuery= $( ".myClass" );
        $.each( elements, function (index, element) {
            const $element = $( element );
            const transition = $element.css( "transition-property" );
            if (transition == "custNS-myProp") {
                // handling                     ...
            }
        });

Of course there is a word of warning in the wiki that the domain of css property identifiers is evolving but I guess if you prefix the value (for instance here with customNS), you can avoid clashes for good.

In the future, when IE supports them, use custom properties instead of transition-property https://developer.mozilla.org/en-US/docs/Web/CSS/--*.

Nicolas Nobelis
  • 772
  • 6
  • 10