CSS Selector Listeners

The following article explores a new event mechanism that I like to call CSS Selector Listeners. Selector listeners are completely bad ass, and sufficiently indistinguishable from magic, as you will soon find out.

Rise of the Mutants

Developers recently were empowered with a new way to listen for changes in the DOM: DOM Level 4 Mutation Observers. Mutation Observers emit fairly general events that tell you basic things, like when a new element has entered the DOM, or an attribute has changed on an element or subtree you are observing.

As a result of their relatively low detail resolution, developers have created libraries to help make Mutation Observers more useful for developers. Rafael Weinstein of Google recently introduced a library called Mutation Summary, which lets you watch the DOM through the lens of a limited subset of CSS selectors using a combination of Mutation Observers and its own custom logic layer to filter and interpret mutation events. The concept of listening to selector matches is intriguing because selectors inherently require the parser to quickly assess complex DOM conditions and perform selector-related logic to check for matches. Having the ability to listen for when selector states become active would be a powerful tool indeed!

A Hidden World of Events

Mutation Summary is pretty cool, but what if I told you there was a hidden world of arcane CSS selector event magic living in your browser that offered unlimited, detailed insight into the state of the DOM? Well there is, and technically there always was, in every style sheet you’ve ever written. All we needed was the right key to access this world…well, a Keyframe, to be exact.

Piggybacking the Parser, with Keyframes

I recently posted a method for using CSS Animation Keyframes to detect node insertions via the animationstart event of a dummy keyframe animation. Now let’s take a second to realize what this hack really is at its core: the ability to listen for any selector-based matches the CSS parser makes anywhere in the DOM. The Selector Listener code I’ve developed provides two methods, addSelectorListener and removeSelectorListener. These methods are available at both document and element level, and allow you to listen for any selector match, regardless of complexity. Once the parser detects a matched selector, the event bubbles up the DOM from the matched target element to the element or document the selector listener is attached to. Here’s what it looks like in action:

// Some action to perform when a match occurs
var sequenceMatch = function(){
  alert("Selector listeners, they're easy as A, B, C!");
};

// Attaching your selector listener to the document or an element
document.addSelectorListener('.one + .two + .three', sequenceMatch);

// Remove the selector listener when it is no longer needed
document.removeSelectorListener('.one + .two + .three', sequenceMatch);

The Goods: Code & Demo

The code that provides all this new hotness, as well as more examples of what’s possible, is available on Github here: SelectorListener Code Repo

You can also play around a bit with this demo page: SelectorListener Demo

6 comments
shwups-cms
shwups-cms

This would be the biggest api since querySelectorAll!

John Fawcett
John Fawcett

Pretty awesome. Though every time I think of a use-case, I think to myself I could just be listening to changes on my model rather than the DOM. I'm sure there are some pretty neat uses for this. Did you have any in mind?

Sean Hogan
Sean Hogan

A couple of questions: 1. What is the overhead of this technique?2. I would assume that browsers consider animations as optional, so they can be skipped under heavy load. Has this hack been rigorously tested for any missed insertions?

Christophe
Christophe

@John You have control on the model but it's not the case for everybody. I build third party widgets and I can see a bunch of applications of this technique. I just discovered it today and can't wait to try it out!

csuwldcat
csuwldcat moderator

@Sean Hogan the overhead is light, because style matches are tied to paint loops that run outside the main thread and do not happen synchronously. As far as missed insertions and skipping animations: animations may "skip", but only in the visual sense - they always fire their start and end events, even if the GUI thread is painting choppy frames or dropping frames under load. You should be good to go!