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
Nice API.
Damn you IE8 for dropping support of expressions!
Yeah I know right, the one time I need IE’s non-standard hacks to provide a bridge for backwards compatibility, it’s nowhere to be found. Though IE9 doesn’t have CSS Animations, so it would be an issue there too :/
[…] Buchner has code that lets you watch for DOM mutations that match a given CSS selector. The cool thing about this approach is that it uses the browser’s own machinery to do the […]
[…] CSS Selector Listeners took me far longer to “get” than it should have… I really love the idea, and think I will come to love it even more once I’ve had a chance for the full potential to really sink-in… (And if you’re playing with the demo, be sure to have your Console open, or it is a little less than impressive…) […]
[…] CSS Selector Listeners | Back Alley Coder Code4k: Going Native 2.0, The future of WinRT JetBrains .NET Tools Blog » ReSharper 7.0 Plug-ins JetBrains .NET Tools Blog » ReSharper 7.0 is Released High-Performance, Garbage-Collector-Friendly JavaScript Code […]
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?
The browser does not skip animations under heavy load, and will always fire an animationstart event when it does. The browser will always fire the animation and start event that drives this mechanism before repaint and DOM rearrangement occurs, without that, animations would be a broken and unreliable API.
[…] CSS Selector Listeners […]
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?
How do you listen on the DOM in a performant way if you want to find out things like:
– when two elements of a certain type are next to each other in DOM position
– when a CSS pseudo is active or inactive
– etc
These events/occurrences do not have native events to alert you and it would be far more cumbersome to write brittle, model-specific code to try and inflexibly catch such cases. This methodology takes less code, is more proformant, and is insanely flexible – three great reasons to use it.
@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!
[…] SourceURL: http://www.backalleycoder.com/2012/08/06/css-selector-listeners/ […]
This would be the biggest api since querySelectorAll!
Nice!
Very good!
@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!