Responsive Javascript
Everyone’s talking about responsive design. As a developer, using CSS media queries is pretty much mandatory nowadays! Whilst the whole concept of applying specific CSS to different media types (most commonly device viewport widths) is really clean and simple, there doesn’t really exist a similarly universal solution for media-specific Javascript.
But it makes sense, no? A truly responsive site will tailor not only the aesthetic appearance to a given medium, but also the behaviour. Really, it should tailor the content too, but that’s a discussion for another day!
I decided to take a look at one surprisingly pioneering example in the field; the BBC TV homepages. If you haven’t read their fantastic blog post on the design process for ‘responsifying’ the site, I encourage you to bookmark it now.
Sniffing around the Javascript on the site, I noticed they have a little script called breakpoint.js. What this essentially does is beautifully simple - it continually checks specific CSS properties (in this case, z-index) of an off-screen element to see when they have changed, and therefore knows when specific media queries have kicked in at certain breakpoints.
I used this as the basis for writing something similar in a site I’m currently developing. Using require.js for module loading / dependency injection, I firstly load up the main.js module which manages all the inter-module communications:
The next module to load is the aforementioned breakpoint.js, which will keep an eye on a dynamically generated DOM element, watching for z-index changes on both DOM ready and window resize events, and notifying all subscribed ‘observers’ accordingly:
You might notice that one of the dependencies specified is observable.js -this is a very lightweight implementation of the observer pattern I adapted from this excellent article by JS guru Nicholas Zakas. It’s not strictly necessary, but makes inter-module communication incredibly easy:
That aside, what’s essentially happening in the breakpoint script is that any time the z-index changes on the #responsiveBreakpoint element, the script fires off a notification to each observer in its internally maintained list, passing it the current breakpoint value as a single argument. By doing this, the script doesn’t need to know the breakpoints in advance, and other modules don’t need to worry about listening for window resize events or otherwise managing the use of media-specific code. It’s all abstracted into a separate plug-in which just watches your existing CSS media queries in action. The only thing required of modules that want to use this breakpoint detection feature is that they supply the correct interface - namely a public method called breakpoint() that accepts the same arguments as the media query. In this case I am only interested in the viewport width so I simply pass in the width as the only argument.
But how did I get the width? Well, in this case I’m ensuring that for each breakpoint in CSS, I set the z-index of the watched element to equal the breakpoint width. So, for example:
OK, it means that you do have to include that little 3-line CSS declaration for every breakpoint, but it has absolutely no visible effect so isn’t really a problem. With this little framework all in place, any module I write now can choose to provide a public breakpoint() method as mentioned above, and respond to the breakpoints in any way it likes. To give an example, this is my navigation menu module:
In the example above, if the current breakpoint is less than 768px wide, collapse the nav menu into a dropdown list. Otherwise, expand it into a full horizontal list. Simple!
“So what’s the benefit of writing all of this code when I could just detect the window size in Javascript directly?” I hear you say. Well, my arguments would be as follows:
- By using this architecture, you maintain a very clean Separation of Concerns between your scripts - is your navigation menu really responsible for detecting window dimensions? No, it’s responsible for navigation menu-specific behaviour.
- You don’t have to redefine your breakpoints in Javascript - whatever you use in your CSS is what your scripts use.
- You can take advantage of everything media queries offers by default - pixel aspect ratio, monochrome, max-height, etc., without having to implement anything! The example above only uses viewport min-width, but could be extended to use any number of media features.
This is very much a work in progress, and there are lots of possible enhancements to make this much more extensible and reusable, but it’s a nice place to start.