Use element queries today with eq.js Sep. 11th, 2014 Chris Ruppel

Use element queries today with eq.js

September 11th, 2014

With responsive images this close to landing natively in several major browsers, everyone has turned their attention to the next major hurdle: element queries. You might be asking yourself what an element query is. Well, here’s some background info, but to give you the elevator pitch, an element query allows us to write conditional CSS/JS based on the properties of an element, rather than the properties of a user-agent, which is what media queries gave us.

Why do I want element queries?

Think of the awesome new power we have with the <picture> element. You can specify many possible images, and only a single one is downloaded and displayed on your device. Conditions may change, but the settings you supply to <picture> ensure that your browsers can continue picking the right one. That is good for users, servers, and everything in between.

Now consider the settings we supply to <picture>. Right now they’re primarily based on browser viewport settings. We even baked that into the <picture> markup by naming the attribute media, and supplying it with media queries.

What if we could tell <picture> to react to its parent container? So instead of sending the <picture> in your main content column one set of config, and sending the <picture> in your much-skinnier sidebar another set, you just have one group of settings that depends on the parent element of the <picture>. Now we’re talkin’ element queries! This is so intriguing to the <picture> crowd that the Responsive Images Community Group has begun discussing element queries, with the intention of spawning a similar process that resulted in our newest HTML element.

What’s stopping browser makers?

Several things actually. You can read about them in Tab’s article linked in the intro. He’s a sharp dude, and if he says it’s a tricky problem you can believe him. But even if we can’t implement a safe solution in native browser code today, there are alternatives with JS that give us most of that power right now.

Enter eq.js

eq.js is a small library written by Sam Richard, who has worked with Four Kitchens very closely over the years to produce some pretty awesome RWD training. So we take this stuff very seriously. Our jobs depend on it.

All eq.js requires is that you put a single data-attribute on your elements that need to be considered components. It does the rest, and frees you to style your components without ever thinking about the browser viewport size again. Here’s a quick example:

<div class="bio" data-eq-pts="medium: 412, large: 688">
  <img src="path/to/my/face.jpg" alt="white male web developer">  
  <h2>Chris Ruppel</h2>
  <p>a frontend developer who makes websites load fast and shrink on your phone.</p>
</div>

Like I said, eq.js does the rest. When the page loads, it allows the DOM to assemble itself then checks the width of any element that has the data-eq-pts attribute. It evaluates how wide your elements are and assigns a new data-eq-state attribute based on the labels you provided in the configuration. Like so:

            <!-- oh hai, component state! -->
<div class="bio" data-eq-state="medium" data-eq-pts="medium: 412, large: 688">
  <img src="path/to/my/face.jpg" alt="white male web developer">  
  <h2>Chris Ruppel</h2>
  <p>a frontend developer who makes websites load fast and shrink on your phone.</p>
</div>

That means you now have a human-readable hook upon which component styles can be placed. Here’s a smattering of Sass to demonstrate:

.bio {
  // Default "small" bios have a linear layout.
  // No styles needed.

  // Medium/Large: we want name/desc to read
  // like a sentence. Inline them and add the
  // word "is" after the name.
  &[data-eq-state="medium"],
  &[data-eq-state="large"] {
    h2, p {
      display: inline;
    }
    h2 {
      font-weight: 700;
    }
    h2:after {
      content: " is ";
    }
  }

  // Large: float the image to the left and bump
  // the font size up a notch.
  &[data-eq-state="large"] {
    font-size: 1.2em;
    img {
      float: left;
    }
  }
}

Notice that medium has no unique styles of its own, rather everything that happens to a medium bio also gets applied to a large bio. This method makes it simple to share component styles between states, meaning that a progressive change between states is very easy to manage, and each state will only contain the unique styles for itself, instead of repeating all the previous code.

More than styling

Since eq.js is a JavaScript library, it means you have the opportunity to do much more than just style based on the data-eq-state attribute. You can also supply your own callback, meaning that responsive images powered by element queries are easily possible. Here’s a short example I wrote up for the RICG EQ use-cases GitHub issue:

<!-- eq.js creates data-eq-state attribute from data-eq-pts -->
<div class="my-component" data-eq-pts="small: 400, medium: 600, large: 900">
  <h1>Hello World</h1>
  <img alt="my responsive image"
    data-src-small="path/to/small.jpg"
    data-src-medium="path/to/medium.jpg"
    data-src-large="path/to/large.jpg">
</div>
// Create callback for eq.js (example assumes jQuery is around)
function eqImages() {
  $('[data-eq-state="small"] img[data-src-small]').each(function () {
    var $this = $(this);
    $this.attr('src', $this.data('src-small'));
  });
  // repeat for other two values in data-eq-pts...
}

// Register callback and send nodeList to library. A full example would also
// include a window.resize listener so that they update on orientation change;
// the code in the handler would be identical to this line.
eqjs.query(document.querySelectorAll('[data-eq-pts]'), eqImages);

Note: please know that any JS implementation of responsive images will always be slower than a native one. Unless you have a hard, non-negotiable business requirement for element queried images, it’s probably best to stick with our shiny new <picture> element! This example was from an earlier time before <picture> was implemented natively, and so it wasn’t as problematic of an alternative.

Performance

Much like Respond.js, eq.js is intentionally simple and greedy with its evaluation of the configuration you provide. It first checks to see if the width is greater than the max, or lesser than the min, and with those simple calculations out of the way, it checks any intermediate breakpoints you’ve provided. Oh, and it uses min-width only both because that is the best way to build components and RWD layouts, and because limiting the evaluation to a single media query keeps the execution as fast as possible.

The DOM writes are minimized by relying on requestAnimationFrame to batch the writes. So it tries to be conscious of the amount of repaints it creates. From the eq.js project page: “With the current test setup of around 2.2k nodes, it can parse all of the nodes, calculate the size, and apply the proper attributes in about 35ms.”

Can I use this now?

Yes. That’s why I wrote this article. Go forth and query your elements! The GitHub page has installation instructions (including Bower) so it’s a snap to set up.

View eq.js on GitHub or View eq.js demo

Other approaches

I’ve been meaning to write this up for almost a year now, and was suddenly prompted to by Scott Jehl’s very interesting prototype. His is almost entirely CSS-bound. You might his approach more fitting for your needs, but we’re all working together to see how eq.js can support this method.

Update 2014-12-04: the original JSBin demo has been put into a repo called Elementary.js, so I have changed the link to point to GitHub:

See Elementary.js demo

Comments