Fire an event when DOM element's computed style changes?


Is there a non-polling method to listen for changes to an element's computed style?

This fantasy code snippet should explain what I mean succinctly:

var el = document.getElementById('doodad');

el.addComputedStyleChangeListener('width', function (prev, new) {
  alert('Previous width: ' + prev + '; New width: ' + new);
});

I'm aware of the DOMAttrModified mutation event and the upcoming MutationObserver, but neither is sufficient -- they can only be used to watch the style DOM attribute of an element, which doesn't wholly determine an element's computed style.


The use case for this was originally part of this question, which really just lead me down a course of curiosity.

There is no such method. CSS OM is not there yet.

And it is not clear what "computed style change" means.

In principle you can detect change of used (for e.g. rendering) style. But this will need some event like "paint" or "layout" to happen.


The best you can do at the moment is request animation frames.

getComputedStyle apparently returns a live-updating object of computed properties.

You can do a basic animation loop like this:

var computedStyle = getComputedStyle(element);
var animate = function () {
    ctx.clearRect(0, 0, canvas.width, canvas.height);
    ctx.fillStyle = computedStyle.color;
    ctx.fillRect(0, 0, canvas.width, canvas.height);
    requestAnimationFrame(animate);
};
animate();

You could potentially optimize by only updating if any of the used properties have changed:

var computedStyle = getComputedStyle(element);
var lastFrameBackground;
var lastFrameColor;
var animate = function () {
    if (
        computedStyle.background !== lastFrameBackground ||
        computedStyle.color !== lastFrameColor
    ) {
       lastFrameBackground = computedStyle.background;
       lastFrameColor = computedStyle.color;

       // assuming expensive drawing code here
       // not like this!
       ctx.clearRect(0, 0, canvas.width, canvas.height);
       ctx.fillStyle = computedStyle.color;
       ctx.fillRect(0, 0, canvas.width, canvas.height);
    }
    requestAnimationFrame(animate);
};
animate();

If it's for a specific CSS animation, you could maybe manage the requestAnimationFrame loop by listening for animationstart and animationend, or if those events don't have good enough browser support, you could kick it off when you know the animation will start (e.g. mouseenter for :hover) and stop when an animated property's computed value stops changing (i.e. don't call requestAnimationFrame if it's equal to its previous value).

If you don't need to animate smoothly, you could use setInterval for possibly-better performance (checking if the document is hidden which requestAnimationFrame does implicitly):

var computedStyle = getComputedStyle(element);
setInterval(function () {
    if (!document.hidden) {
        ctx.clearRect(0, 0, canvas.width, canvas.height);
        ctx.fillStyle = computedStyle.color;
        ctx.fillRect(0, 0, canvas.width, canvas.height);
    }
}, 200);