View demoView complete code

Detecting certain CSS media queries with Javascript is often necessary, but can be kind of tricky. Usually you end up using Modernizr and/or some hard coded width to detect mobile layouts, like this:

if($(window).width < 725 || Modernizr.touch) {
  // Code for smaller screens...
}

The major problem with this is that hard coding a pixel value is not a good long term solution. CSS media queries can easily be changed during a projects lifespan, and it’s easily forgotten to update both the CSS and JS to when changing values for browser width. You’ll also probably end up with a bunch of JS-files that has dozens of different widths.

Set with CSS, get with Javascript

Since our media queries is set by the CSS, why not have the CSS tell our Javascript what media query we’re currently inside? That way our JS will never have to worry about what width values we set.

So we’re going to build a few SCSS-mixins that’ll define and output our breakpoints right inside the web page with the help of pseudo-elements. We’ll also build a Javascript function that will parse that information and trigger a custom event that we can use for writing functions for our different media queries.

Let’s start with setting up our breakpoints in CSS (SCSS).

@mixin bp-large {
  @media only screen and (min-width: 65em) {
    @content;    
  }  
}

@mixin bp-medium {
  @media only screen and (min-width: 35em) {
    @content;    
  }  
}

@mixin bp-small {
  @media only screen and (min-width: 25em) {
    @content;    
  }  
}

Next up we’ll create a mixin that will write out the breakpoint name in a pseudo element using the content property.

@mixin define-breakpoint($name) {
    &:after {
      content: $name;
      display: none;
    }
}

This content-attribute is what we’ll be hooking our Javascript on to later.

And the last CSS we’ll do is to use all our three media queries on the body element, where we’ll call our define-breakpoint mixin with the names of the media queries. I feel like outputting our media query names in the body element is the cleanest way since it’s always a part of each page. You can also output it in the html-element, either is fine.

body {
    @include bp-small(){
        @include define-breakpoint("bp-small");
    }  

    @include bp-medium(){
        @include define-breakpoint("bp-medium");
    }

    @include bp-large(){
        @include define-breakpoint("bp-large");
    }
}

This will result in the following when inspecting our website:
z63mq-screen

The Javascript

So, now we’ve outputted our media queries into the page in a clean way. Let’s create a Javascript function that gets the content inside our body:after element and triggers a custom event which we can listen to. Let’s start with the foundation:

'use strict';
var Z63 = (function (parent, $) {

    var MediaQueryListener = function() {
        this.afterElement = window.getComputedStyle ? window.getComputedStyle(document.body, ':after') : false;
        this.currentBreakpoint = '';
        this.lastBreakpoint = '';
        this.init();
    };

    parent.mediaqueryListener = parent.mediaqueryListener || new MediaQueryListener();

    return parent;

}(Z63 || {}, jQuery));

The first thing we do is checking if the browser supports getting our pseudo element content with the help of window.getComputedStyle. Next we’ll define our variables that we’ll be needing and finally we’ll fire up our initiation function.

Next up is writing our init function and a resize listener with an event trigger:

'use strict';
var Z63 = (function (parent, $) {

    var MediaQueryListener = function() {
        this.afterElement = window.getComputedStyle ? window.getComputedStyle(document.body, ':after') : false;
        this.currentBreakpoint = '';
        this.lastBreakpoint = '';
        this.init();
    };

    MediaQueryListener.prototype = {

        init: function () {
            var self = this;

            if(!self.afterElement) {
                // If the browser doesn't support window.getComputedStyle just return
                return;
            }

            self._resizeListener();

        },
        _resizeListener: function () {
            var self = this;

            $(window).on('resize orientationchange load', function() {
                self.currentBreakpoint = self.afterElement.getPropertyValue('content');

                if (self.currentBreakpoint !== self.lastBreakpoint) {
                    $(window).trigger('breakpoint-change', self.currentBreakpoint);
                    self.lastBreakpoint = self.currentBreakpoint;
                }

            });
        }

    };

    parent.mediaqueryListener = parent.mediaqueryListener || new MediaQueryListener();

    return parent;

}(Z63 || {}, jQuery));

The only thing needed in the init function is to check whether or not the browser is capable of getting the styles, this is true for all modern browsers except Internet Explorer 8 which is neat.

After that we call our resize listener, which will get the text content of our body’s :after element and trigger a custom event that we can listen to. We also compare the last known breakpoint to the current one, if those two aren’t the same, meaning we’ve entered a new breakpoint, we’ll trigger the breakpoint-change event.

And that’s all we need. We can now listen to our custom event and do whatever we want when the media queries triggers in the browser.

$(window).on('breakpoint-change', function(e, breakpoint) {

    if(breakpoint === 'bp-small') {
        document.body.innerHTML = 'CSS Breakpoint screen-small';
    }

    if(breakpoint === 'bp-medium') {
        document.body.innerHTML = 'CSS Breakpoint screen-medium';
    }

    if(breakpoint === 'bp-large') {
        document.body.innerHTML = 'CSS Breakpoint screen-large';
    }

});

View codeView demoCheck it on Codepen

Thanks to Jonathan Persson for helping me out with this article.