Have you ever been in a situation where you needed a JavaScript event to fire at a specific width? Did you also need to coordinate that behavior with a CSS media query breakpoint? It’s a total bummer to sync those up, isn’t it? Are you tired of herding those cats? I was. And now I am done with those shenanigans.

I engineer a lot of mobile-first, responsive user interfaces. One of my biggest pain points was perfectly timing my breakpoints with DOM or data manipulation. Another challenge was keeping the mobile experience light and snappy. Hiding sidebars or carousels while secretly smuggling them across the wire is just wrong. You may as well be dumping your garbage in your neighbors’ trash can. I mean, Geez. Why would you do something like that? And don’t get all indignant, entitled, and self-righteous about how their kids came over to play with yours all summer long and you basically gave them a free babysitting service. After all, they owe it to you. Not to mention you fed them like every day.

What was I talking about?

Oh. Yeah. So, it is also really suboptimal to duplicate your CSS breakpoints in your JavaScript with:

matchMedia("(min-width: 11235813213455px)").matches

or do some sort of fancy:

someNode.clientWidth

on a resize event. That way is zero fun. But do you know what is? This JavaScript viewport utility I wrote. It comes in two versions: a standard vanilla JS version and an AngularJS module. This utility creates generated content variables within CSS media queries that you can use to sync up your width-sensitive JavaScript methods on load and resize events.

Jeremy Keith first inspired this module from a 2012 blog post titled, Conditional CSS. Viewport enhances Keith’s window.getComputedStyle technique and a debounced resize event utility method that I first learned about from Paul Irish’s 2009 Debounced Resize() jQuery Plugin.

Here are demos for the Vanilla version and the AngularJS version.

Install and Bootstrap

For AngularJS

  1. npm install viewport-angular
  2. Or download the zip from GitHub
  3. Add this in your head tag: <link rel="stylesheet" href="[assetsPath]/viewport.css">
  4. Load this after your Angular script: <script src="[assetsPath]/viewport.js" ></script>

Once everything is bootstrapped, dependency inject viewport into your main app.js like this:

angular.module('app', ['viewport'])

For Standard Vanilla

  1. npm install js-viewport
  2. Or download the zip from GitHub
  3. Add this in your head tag: <link rel="stylesheet" href="[assetsPath]/js-viewport.css">
  4. Add this script tag: <script src="[assetsPath]/js-viewport.js" ></script>

Basic Usage

The CSS

Basically, you set variables as generated content in the CSS. The JavaScript then reads those generated values from the page. By managing your media queries in your CSS and allowing JS to follow that lead, your presentational layer and behavioral layer are totally synced up. Another benefit is you don’t have to replicate your breakpoints in your JS with matchMedia.

@media all and (min-width: 28.125em) {
  body:after {
    content: "mini";
    display: none;
  }
}
@media all and (min-width: 31.25em) {
  body:after {
    content: "tablet";
    display: none;
  }
}
@media all and (min-width: 60em) {
  body:after {
    content: "widescreen";
    display: none;
  }
}

Feel free to change the breakpoints for your own needs. You can add new ones as long as you update the types array. In the standard version, the array is located at the top of js-viewport.js. For the AngularJS version, it is located at the top of ng-viewport.js.

By the way, there is also an .scss file in there if you want to bring it into your own Sass build process.

The JavaScript

There are two methods that viewport returns: getType and ;watchViewport.

viewport.getType()

This method returns the generated content value set in the CSS.

console.log(viewport.getType());
// returns 'handheld', 'mini', 'tablet', or 'widescreen'
watchViewport

This method instantiates a resize event that you pass a callback to.

Example

viewport.watchViewport(function () {
    if ( viewport.getType() === 'handheld' ) {
        // doSomeHandheldThing();
    }
    if ( viewport.getType() === 'mini' ) {
        // doSomeMiniThing();
    }
    if ( viewport.getType() === 'tablet' ) {
        // doSomeTabletThing();
    }
    if ( viewport.getType() === 'widescreen' ) {
        // doSomeWidescreenThing();
    }
});