14

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.

Community
  • 1
  • 1
namuol
  • 9,816
  • 6
  • 41
  • 54
  • 6
    As far as I know: no. – Matt Ball Nov 01 '12 at 22:25
  • The *DOMAttrModified* event will be deprecated in [DOM 3 Events](http://www.w3.org/TR/2012/WD-DOM-Level-3-Events-20120906/#event-type-DOMAttrModified) so not useful anyway (and I think support is pretty patchy to start with). All [mutation events](http://www.w3.org/TR/2012/WD-DOM-Level-3-Events-20120906/#events-mutationevents) will be deprecated, there will be a [DOM 4 MutationObserver](http://www.w3.org/TR/dom/#mutationobserver) instead. – RobG Nov 01 '12 at 23:26
  • Agreed with @MattBall, you either [trigger an event handler explicitly](http://stackoverflow.com/questions/1950038/jquery-fire-event-if-css-class-changed) or [use polling](http://stackoverflow.com/questions/4567987/jqueryevent-watch-element-style) – mccannf Nov 02 '12 at 00:08
  • Maybe if you described the *goal* of doing this people would be able to suggest alternate approaches. – Stephen P Nov 02 '12 at 00:46
  • It might actually be possible to create a trick for the special cases of height and width, but probably not for other styles. – Alohci Nov 02 '12 at 01:04
  • @Alohci -- Any hunches on how such a trick might be performed? – namuol Nov 02 '12 at 02:58
  • @RobG -- Good point(s). Still, `MutationObserver` doesn't do what I need here, either. – namuol Nov 02 '12 at 02:59
  • 3
    @namuol - http://jsfiddle.net/e5QW5/ shows the basic idea. It adds an iframe to the element with a size proportional to the element. When the element size changes, so does the iframe, and the resize event for the iframe's window fires, which can be listened for. – Alohci Nov 02 '12 at 09:18
  • Update: [It's possible to fire events when an element's dimensions change for any reason, including styling changes, by exploiting 'flow' events.](http://www.backalleycoder.com/2013/03/18/cross-browser-event-based-element-resize-detection/) – namuol May 08 '13 at 19:30

2 Answers2

3

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.

c-smile
  • 26,734
  • 7
  • 59
  • 86
  • 1
    Computed style can be thought of as the *effective* style of an element after all CSS rules have been applied, including the user agent stylesheet. [window.getComputedStyle](https://developer.mozilla.org/en-US/docs/DOM/window.getComputedStyle) provides a means of accessing this data. A naive approach would be to "watch" the results of `getComputedStyle` using `setInterval`, but that violates the requirements of this question. – namuol Nov 02 '12 at 02:57
  • @namuol Consider that some element has not-yet-resolved style, thus el.getComputedStyle() shall resolve it and so to generate that hypothetical event. My point is that there is no deterministic moment of time in element style life cycle when you can say "computed style changed" - it could be many such moments. As an example: `el.getComputedStyle(); el.style.color = "red"; el.getComputedStyle();` where exactly in this sequence you'd expect that event to be fired? Or not there but rather when element style will be **used** for layout calcualtions or rendering? – c-smile Nov 02 '12 at 05:24
-1

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);
1j01
  • 3,714
  • 2
  • 29
  • 30
  • The return value of `getComputedStyle(element)` updates itself whenever the computed styles change. So you can just call it once and re-use the returned value. By the way, I can't think of any reason to use requestAnimationFrame if you're already using setInterval. Just use setInterval without rAF. – Steven Liekens Mar 23 '18 at 10:44
  • @StevenLiekens Wow, I didn't know that, thanks! The rAF in that case is so it doesn't trigger in the background; you could also check `document.hidden`; I should've mentioned that. ...In fact, you should definitely check `document.hidden` and not do it this way, because the callbacks could build up. I'll update my answer. – 1j01 Mar 25 '18 at 06:09