How to React to Changing Environments Using matchMedia

5 min read

This article was initially posted on 4waisenkinder.de

I was at my parents place over Christmas. Besides eating the whole day and playing board games I tried to read two books I brought with me.

One of the books was RESPONSIBLE RESPONSIVE DESIGN by Scott Jehl. It was released recently and it is a really good read. I have not finished it yet, but I enjoy it and it is for sure in the TOP5 list of my favourite books about responsive web design.

In the world of responsive web design we deal with media queries all the time to give our visitors the best experience possible depending on the their devices and environments. Usually this means that web developers deal with screen dimensions inside of media queries and this is becoming crazier and more useful every day.

If you are interested in what may come in the future the spec for Media Queries Level 4 is quite interesting - check out the spec for light-level. ;)

Media queries are mostly used inside of the CSS files to adapt the styling to the particular device the visitor is using. This makes total sense, but responsive web design should not be only about "making it look pretty".

# It should also be about only using and initializing needed components.

At my last job I was working with Foundation a lot. And I really loved it. The block grid is awesome! And also the Off-canvas implementation is quite nice in my opinion.

The only thing that always bothered me about the implementations in famous frameworks like Bootstrap and Foundation is that the scripts and modules used for smaller devices are also executed on larger devices and vice versa.

No matter if the modules and their functionality are needed or not, the given code is executed and all event handlers are still bound. These event handlers will not be triggered in a lot of cases, because the given DOM elements connected to the functionality are hidden via CSS in several environment combinations.

When having a look at navigation solutions for smaller devices in both projects you will see that the JavaScript modules to guarantee a good experience on smaller devices are initialized and event handlers are bound for no reason - regardless of whether they are needed. This is a simple waste of CPU and not needed at all.

There is a lot of discussion regarding what to serve to a customer in terms of feature detection and custom builds, but not every project has enough time and resources to implement complicated build systems.

This is why I started to initialize given modules only when needed. A custom build of Modernizr is usually included in my projects so that the handy Modernizr.mq function is largely used to not initialize things that are not needed at all.

// only initialize the off-canvas 
// navigation for smaller devices
if ( Modernizr.mq( '(max-width: 50em)' ) ) {
  // initialize Foundation's offcanvas
  $document.foundation( 'offcanvas' );
}

# Modernizr.mq - how does it work?

Let us have a look at how this function works. The functionality is defined in mq.js and testMediaQuery.js.

// -> testMediaQuery.js
define(['injectElementWithStyles'], function( injectElementWithStyles ) {
  // adapted from matchMedia polyfill
  // by Scott Jehl and Paul Irish
  // gist.github.com/786768
  var testMediaQuery = (function () {
    var matchMedia = window.matchMedia || window.msMatchMedia;
    if ( matchMedia ) {
      return function ( mq ) {
        var mql = matchMedia(mq);
        return mql && mql.matches || false;
      };
    }

    return function ( mq ) {
      var bool = false;

      injectElementWithStyles('@media ' + mq + ' { #modernizr { position: absolute; } }', function( node ) {
        bool = (window.getComputedStyle ?
                window.getComputedStyle(node, null) :
                node.currentStyle)['position'] == 'absolute';
      });

      return bool;
    };
  })();

  return testMediaQuery;
});

As we see internally Modernizr uses the matchMedia function ( when available ), which is defined in the spec for the CSSOM View Module. If matchMedia is not supported it provides a shim to achieve the same functionality.

# window.matchMedia - so what?

matchMedia provides the functionality to check media queries on the JavaScript side. And the great thing about it is, that it is actual quite widely supported according to caniuse.com.

Modernizr.mq already gives us support to get information on whether the given media query is matching or not. One key thing to watch out here is, that matchMedia does not return a boolean value to work with - instead it returns a MediaQueryList Interface.

The information if the media query matches is stored inside of the property matches.

( function( window, document ) {
  // get MediaQueryList Interface
  var mql = window.matchMedia( '(min-width:20em)' );
  // get a container to do something with it depending 
  // on matching media query
  var container = document.getElementById( 'container' );
  // create new paragraph object
  var paragraph = document.createElement( 'p' );

  // set innerHTML depending on matching media query
  paragraph.innerHTML = mql.matches ?
    'Yeah - matching!' : 
    'No - not matching!';

  // append element to container
  container.appendChild( paragraph );
} )( window, document );

This is already great because this way we can easily evaluate if a particular JavaScript module needs to be initialized or not. But what about the case of changing environments like a changed viewport from portrait to landscape mode? This is something I have been struggling with for a while already - and while reading Scott Jehl's book I just came across a super neat solution.

I already read a lot of articles about matchMedia, but I did not know before that it provides more than just the matches property.

The returned MediaQueryList Interface also provides the ability to attach listeners to the moment whenever the media query changes from matching to not matching or the other way around.

( function( window, document ) { 
  // get MediaQueryList Interface
  var mql = window.matchMedia( '(min-width:20em)' );
  // get a container to do something with it depending 
  // on matching media query
  var container = document.getElementById( 'container' );
  // create new paragraph element
  var paragraph = document.createElement( 'p' );

  // set innerHTML depending on matching media query
  paragraph.innerHTML = mql.matches ?
    'Yeah - matching!' : 
    'No - not matching!';

  // append element to container
  container.appendChild( paragraph );

  // attach event listener to changed state
  // of the given media query
  mql.addListener( function( mql ) {
    // create new paragraph element
    var paragraph = document.createElement( 'p' );

    // set innerHTHML depending on matching media query
    paragraph.innerHTML = mql.matches ?
      'Yeah - changed to matching!' : 
      'No - changed to not matching!';

    // append element to container
    container.appendChild( paragraph );
  } );
} )( window, document );

This is exactly what I was looking for a while, it gives an easy way to enhance an interface on changing environments on the fly and save resources from the beginning.

If you want to play around with it I created a pen for you to play around with.

And this is it for today - I hope you enjoyed the short read. Any ideas and comments are more than welcome. :)

Load time