clubmate.fi

A good[ish] website

Web development blog, loads of UI and JavaScript topics

A lesson in Sass Maps: make Sass media queries talk to JavaScript

Filed under: HTML/CSS, JavaScript— Tagged with: media queries, sass

Get media queries to JavaScript from a Sass file.

Here’s a little experiment of mine. Remember the technique, from few years back, where you would stick the name of a breakpoint into a :before or :after pseudo element in body tag, like this:

body {
  @include bp(m) {
    &:after {
      content: 'm';
    }
  }

  @include bp(l) {
    &:after {
      content: 'l';
    }
  }

  @include bp(xl) {
    &:after {
      content: 'xl';
    }
  }

  &:after {
    content: 's';
    display: none;
  }
}

And then read it with JavaScript using getComputedStyle:

var size = window
  .getComputedStyle(document.body, ':after')
  .getPropertyValue('content')
// Size equals to your current breakpoint now

I "improved" it a little, and made a Sass mixin to encapsulate it better. The mixin uses Sass maps, with the map value being a Sass list. More on the JavaScript bit later in the article.

The mixin

This mixin sticks the breakpoints name into :before and the breakpoints size into :after. I have no idea if the size is needed for anything, I haven't needed it ever, but there might be a use case for it, I'll leave that up to you.

This is actually a set of mixins, it needs a breakpoint mixin as it companion. Read the comments in the code for more details:

// Media queries
$breakpoints: (
  's': (
    min-width,
    48em
  ),
  'm': (
    min-width,
    50em
  ),
  'l': (
    min-width,
    64em
  ),
  'xl': (
    min-width,
    90em
  ),
  'xxl': (
    min-width,
    105em
  )
) !default;

// Make the breakpoints
@mixin bp($breakpoint) {
  $query: map-get($breakpoints, $breakpoint);

  @if $query != null {
    @media (#{nth($query, 1)}: #{nth($query, 2)}) {
      @content;
    }
  } @else {
    @warn "Unfortunately, no value could be retrieved from #{$breakpoint}. "
        + "Please make sure it is defined in `$breakpoints` map.";
  }
}

// ----
// Here's the sauce, this jugs the BPs name (key) into an `:after` and the
// value to `:before` and `:after` pseudo elements, we can read that with
// JavaScript.
// ----
@mixin bp2js($default) {
  // One of the BPs has to be the default, meaning that it's on when
  // there's no BP affecting. In the min breakpoint mobile first approach,
  // the default would be the smallest BP.
  &:before {
    $foo: map-get($breakpoints, $default);
    display: none;
  }

  &:after {
    content: '#{$default}';
    display: none;
  }

  @each $point, $dim in $breakpoints {
    @if $default != $point {
      @include bp(#{$point}) {
        &:after {
          content: '#{$point}';
        }

        &:before {
          content: '#{nth($dim, 2)}';
        }
      }
    }
  }

  @content;
}

Demo:

Play with this gist on SassMeister.

JavaScript to read the values

Here's the script wrapped into an AMD module:

// mq2js.js
'use strict'
define(function () {
  var getValues, value
  getValues = function (el) {
    value = window
      .getComputedStyle(document.body, ':' + el)
      .getPropertyValue('content')
    return value.replace(/(\'|\")/g, '')
  }
  return {
    name: getValues('after'),
    size: getValues('before')
  }
})

It returns an object called bp with with two keys:

  • name — the breakpoint name
  • size — the breakpoint width

Usage

Here's an example that prints them out to the console:

define(function(require) {
  // Require the module
  require('./mq2js')

  // Define environment. Ideally you'd check this programatically somehow
  var isDev = true;

  // Make a little helper function
  var bpShower = function() {
      console.log('BP name: ' + bp.point + 'n BP width: ' + bp.width);
  }

  // If on dev, run it
  if (isDev === true) {
    bpShower();
    // Run also on resize
    window.addEventListener('resize', bpShower);
  }
}

Another use case for this is to load assets conditionally depending on the screen size:

define(function(require) {
  // Require the module
  require('./media-queries')

  // Check the that we're on the large breakpoint
  if (bp.point === 'xxl') {
    // Load an image gallery, for example
    $("#result").load("ajax/images-template.html", function() {
      // This is a callback for the load,
      // init lazyload here or something else...
    });
  }
}

Thoughts and conclusions

This is very light and library-free method of doing this, albeit not very advanced nor sophisticated. There's libraries like enquire.js that can do tons of amazing stuff for you! Enquire.js is also dependency free, get to know it here, it's quite alright.

Comments would go here, but the commenting system isn’t ready yet, sorry. Tweet me @hiljaa if you want to make a correction etc.

  • © 2021 Antti Hiljá
  • About
  • Follow me in Twatter → @hiljaa
  • All rights reserved yadda yadda.
  • I can put just about anything here, no one reads the footer anyways.
  • I love u!