Can we detect an element's computed CSS values at any point of a CSS animation?

Suppose I toggle a class on an element, and this triggers a CSS transition that lasts 2s.

Is there a way to get the computed value of the animated property (or properties) at any point during the animation? If there is more than one way, what is the best way?

I am guessing that I can make a loop with requestAnimationFrame and continually poll for the computed properties, but I haven’t tested this yet, and plus it feels really hacky in general to be polling for things.

Also, and unfortunately, MutationObserver doesn’t have a way to observe an element’s computed styling. That may be a nice feature! The MO callback for such a feature would ideally fire right before the following animation loop. I suppose this isn’t any worse than polling, technically, but it may seem less hacky (though, in practice, maybe both are effectively the same, but one is explicit and fires only if a change actually happened, while the other needs to poll in order to determine if a change happened).

EDIT: I forgot that CSS transitions don’t work with --custom properties. That puts a huge damper on things. Oh wait, the new CSS Properties and Values API Level 1 allows custom properties to be transitionable!

EDIT: I made a feature request for Houdini on GitHub:

Are you able to provide the usecase for why you want to do this? Doing this can potentially have negative implications on your sites performance and the animation itself.

Hello Greg, I wrote about some use cases in this Houdini GitHub issue, in the “Why” section.

Basically it’s a need that arrises with custom rendering systems that want to provide users access to capabilities via standard web APIs the same way as with built-in features.

Sure you can use getComputedStyle() to read the animated style. Reading it in a requestAnimationFrame callback will mean you get the values for that frame. (requestPostAnimationFrame would allow you to avoid forcing a synchronous style flush but it probably won’t make much difference in most cases.)

If you want to seek the animation to a particular point you can use getAnimations() to get the transition and then set its currentTime.

1 Like

Oops! I noticed I posted the wrong link in my previous comment! That was a minecraft video for my nephew. :laughing: Fixed it.

@birtles I want to avoid polling though. With rAF we will be requesting computed style even if nothing changed.

I think something like the idea in the GitHub issue ^ would be nice, so that there is a notification/push based API available.

Ok, you said “Is there a way to get the computed value of the animated property (or properties) at any point during the animation” which I assumes means you already know an animation is running?

Most animations change their target properties on each frame in their active interval (step timing functions and repeated keyframes being a few of the rare exceptions) so there is little additional overhead from using requestAnimationFrame, assuming the animation is running on the main thread.

If you want to avoid polling when no animation is running you could listen for animationstart/animationend and transitionstart/transitionend events. This would also have the advantage of skipping the delay phase. Unfortunately there is no such event for animations generated using the Web Animations API.

I believe at least Firefox and Chrome internally have an animation mutation observer mechanism that reports when new animations are generated/updated/deleted. This is used for DevTools but, at least in Firefox, it does not notify for changes to animated style.

For the Houdini issue, might I suggest you move the use cases to the top of the issue. Requesting a non-polling API begs the question of why the use cases require that. In fact, you will likely receive more constructive feedback if you focus on the concrete problem rather than the proposed solution.

Regarding a general style change observer API, note that it would still introduce performance penalties if it requires only queuing events when a value changes. For animations that run on another thread, browsers can avoid performing the animation-related style calculations on the main thread unless requested to by Javascript. Such an API would force the style calculation for off main thread animations to also be performed on the main thread in order to detect if it the style has changed or not.

That’s true! I had figured along those lines, so I suggested in that Houdini issue that the observer can observe only certain specified properties, and that unlike with DOM mutations, it would only observe the final state of animation once the handler is fired (so there would be only one item in the mutation list), which would prevent the engine from having to calculate styles potentially many times before finally triggering the observer callback.

If the only observed property is a --custom property, then in that case the engine can entirely skip reflow/relayout, and just supply the inherited value to the observer callback, because --custom properties, as far as I know, are values only, not computed values (unless they have calc(), but the calc()ed value does not involve a reflow/relayout).

In some cases, we may only want to observe a non-custom CSS property’s pre-computed value (not the computed values). getComputedStyle doesn’t provide this for us. If there was a way to specify that we want pre-coputed values with the MutationObserverInit object (or a CSSPropertyObserverInit for CSSPropetyObserver, or similar (and adding a getPreComputedStyle function too)) then we could have the engine tell us what the pre-computed values are (f.e. height: 100% is always going to give us back 100% for the value, regardless what the actual size is), and therefore we can also avoid reflow/relayout when we know we don’t need it.

As an example, a custom rendering engine could take values from 0% to 100% to determine how to draw WebGL objects, without needing the computed values of the DOM counterparts. The graphics could still be in sync (a DOM element with 100% size as well as a WebGL object with the same size on the screen, but via its own calculations based on percentages and not actual size values).

EDIT: What about ResizeObserver? I imagine, that it must have the same considerations, because in order to tell you the size of an element, it would need to use computed values from reflow/relayout. It seems a CSSPropertyObserver would be similar, when wanting to observe computed values.

Yes, IntersectionObserver, at least, has similar issues. See Mozilla Bug 1636416 for an example of a recent bug because of that.

1 Like