... and a little JS

Recently I found myself with a conundrum. I'm in the process of a major overhaul of our company site, turning the old (IE6 compatable) markup into swanky new responsive markup. We, like most sites, have a navigation bar across the top of the page that contains drop-down menus. On narrow screens the nav bar turns into a stack of elements, and their dropdown menus need to trigger on click. The problem... In wide screen view I need the nav bar elements to behave as links to landing pages, but in narrow screen view I need them to no longer be links, but instead expand to show their child content.

There are numerous ways to handle this. Some are very good, others are very bad. Here was my thought process.

Why not create a click handler that checks page width in JS. That's easy enough. Except, I didn't like the potential maintenance problems with this method. For example, my design has media queries that change layout at a few different break points, each sized in EMs. If tell Javascript "Hey Javascript, change the way this link works at xxx pixels" I have created a second, independent definition for the media query that has to be maintained. If I take a max-width: 40em media query and change it to max-width: 45em, I now have to remember to change the associated JS or face the wrath of confused scripts. Considering some day my crappy code will become someone else's problem, I feel I should be a good citizen and help my fellow designer out. This method is a no-go.

I could create two sets of navigation, one wired for narrow screen mode and another for wide screen mode. The media query could hide/show them as needed. This solution is NO... just, no. I think it's obvious why.

So how can I get my JavaScript to be aware of changes in layout that are defined entirely in the CSS? Why use what CSS does best of course, detect changes in style.

If you can give your JavaScript one universal assumption about what changes during Media Queries you now have a way to detect what Media Queries are active.

Take for example the site that started this all. My navigation is styled with display: table on wide screens and display: block on narrow. So now I can bind a click handler to my nav bar anchors that checks if css('display') === 'block', and if so, prevents the default action and applies the class OPEN to the <li>. This works, but can we make it more generic?

Like all good rhetorical questions, that answer was hopefully obvious. We can in fact make this all more generic, assuming we're OK adding one meaningless element to the DOM. Anywhere in the page add an empty span with class ".mediaquerier". This class can be display:none, position: fixed, left: -1. Now in our media query we reposition .mediaquerier with left: -2. We can follow this pattern for each media query (we can also apply top positioning). Now our JavaScript can look at css('left') and know what media queries are applied by the arbitrary values we've assigned each. "But", I hear you ask, "I don't like arbitrary-ness-icity.... whatever... anyway, can we make this more semantic?". Why yes my good madam or sir, yes we can.

I'm editing following @AmeliaBR 's comment.

The trick to making this semantic is to use a CSS attribute that lets us put absolutely any string value we want into it, and won't be converted to a different 'computed' value. Font-Family. We can use a single font family with any string we want. Note that you can't use a comma here. For example:

  @media (max-width: 40em) {
    .mediaquerier {
    font-family = 'mq-lt40em';

Now we can cause JavaScript to poll for the very semantic string mq-lt40em (media query - less than 40 em). As was pointed out in the comment you may have to work some REGEX magic to strip out the quotes browsers may or may not return on this value as well as watch for commas which have special meaning in the font-family attribute. Here's Amelia's pen demonstrating this:

<Don't Use What's Below>

The trick to making this semantic is to use the only(?) CSS attribute that lets us put absolutely any string value we want into it, and won't be checked or converted to a different 'computed' value. Background-image! We can use a string that means something to us as the "url" for background-image. For example:

  @media (max-width: 40em) {
    background-image: url('mq-lt40em');

Now we can cause JavaScript to poll for the very semantic string mq-lt40em (media query - less than 40 em). BUT WAIT!!!! This is now going to make a phony request for a file called mq-lt40em, filling up our server error logs (which we all read oh so diligently) and wasting network time. That's why we make the following adjustment

  @media (max-width: 40em) {
    background-image: url('mq://lt40em');

Our resource now has a bogus protocol 'mq://' and a bogus domain (with no TLD), so rather than try to fetch, the browser will fail silently, though a message may print to console. Just be careful here. If you use a valid protocol by mistake (http, ftp, file) or write something that could look like a URL by, lets say, including .com/.net/.biz/.info etc you'll be in a world of hurt. If you don't trust your co-workers with this kind of power, then use the left: -1 approach above.

Here's an example of this working.

Keep in mind this is, at best, a hack until better support for https://developer.mozilla.org/en-US/docs/Web/API/Window.matchMedia or a counterpart has rolled out.

Comments or improvements are appreciated.

3,315 3 2