Introducing @bramus/style-observer, a MutationObserver for CSS

@bramus/style-observer is a MutationObserver for CSS. It allows you to attach JavaScript callbacks to changes in computed values of CSS properties


This content originally appeared on Bram.us and was authored by Bramus!

A shortcoming of MutationObserver (imho) is that it cannot be used to subscribe to value changes of CSS properties.

To plug that hole I built a library allowing just that: @bramus/style-observer. It allows you to attach JavaScript callbacks to changes in computed values of CSS properties.

It differs from previous attempts at doing this by not relying on requestAnimationFrame. Instead it is powered by CSS Transitions and transition-behavior: allow-discrete.

~

Demo

Let’s jump straight in with a demo. Every time you click the document, the background-color is set to a random color. In response to each change, thanks to @bramus/style-observer, a callback gets executed. This callback shows the new computed value in a notification using the notyf library.

See the Pen @bramus/style-observer demo by Bramus (@bramus) on CodePen.

~

Installation and Usage

To obtain @bramus/style-observer install it through NPM (or whatever package manager you are using):

npm install @bramus/style-observer

The following code shows you how to use it. See the inline comments for explanation on what each section does:

// Import the CSSStyleObserver class
import CSSStyleObserver from "@bramus/style-observer";

// Array with the names of the properties to observe.
// This can be one or more, both regular properties and custom properties
const properties = ['background-color'];

// Create a CSSStyleObserver that tracks the properties.
// Every time one of those properties their computed value changes, the passed in callback gets executed
// Here, the callback shows the new computed value in a notification
const cssStyleObserver = new CSSStyleObserver(
	properties,
	(values) => {
		showNotification(values['background-color']);
	}
);

// Have the CSSStyleObserver instance observe the `<body>` element
cssStyleObserver.attach(document.body);

// Change the background-color every time you click the document
document.documentElement.addEventListener('click', (e) => {
	document.body.style.setProperty('background-color', randomBackgroundColor());
});

~

Under the hood

Under the hood @bramus/style-observer relies on CSS Transitions and transition-behavior: allow-discrete;.

Each property that you monitor with @bramus/style-observer gets a short CSS Transition applied to it. The transition is set to a very short transition-duration of 0.001ms and the transition-timing-function is set to step-start so that the transition immediately kicks in.

To catch this transition, the library also sets up a transitionstart event listener which invokes the callback that was passed into the CSSStyleObserver.

By default, transitions only fire for properties that don’t animate discretely. Thanks to the very recent transition-behavior: allow-discrete;, it is now possible to have transitions – along with their events – on properties that animate discretely (which includes Custom Properties).

~

Browser Support

Technically speaking, @bramus/style-observer works in any browser that supports CSS Transitions. To also observe properties that animate discretely, support for transition-behavior: allow-discrete; is also required so in practice that boils down the the following browsers that are supported:

  • Chrome/Edge 117 (*)
  • Firefox 129
  • Safari 18

However, Chrome is currently affected by https://crbug.com/360159391 in which it does not trigger transition events for unregistered custom properties. You can work around this Chrome bug by registering the custom property using @property.

But … Safari doesn’t like that workaround as it then seems to be stuck in a transition loop when the custom property is registered with a "<string>" or "*" syntax. Other syntaxes – such as "<number>" or "<custom-ident>" (which kinda look like a string) don’t mess up things in Safari (and also bypass that Chrome bug).

~

Prior Art and Acknowledgements

This section is purely informational.

The requestAnimationFrame days

Wanting a Style Observer is not a new idea. There have been attempts at making this before such as ComputedStyleObserver by keithclark (2018) and StyleObserver by PixelsCommander (2019).

Both rely on using requestAnimationFrame, which is not feasible. This because requestAnimationFrame callbacks get executed at every frame and put a load on the Main Thread.

Furthermore, the callback used in those libraries would also typically trigger a getComputedStyle and then loop over all properties to see which values had changed, which is a slow process.

Besides putting this extra load on main thread, looping over the getComputedStyle results would not include Custom Properties in Chrome due to https://crbug.com/41451306.

And finally, having a requestAnimationFrame forces all animations that run on the Compositor to also re-run on the Main Thread. This because getComputedStyle needs to be able to get the up-to-date value.

Add all those things up, and it becomes clear that requestAnimationFrame is not a feasible solution 🙁

The CSS transitions approach

In 2020, Artem Godin created css-variable-observer which ditched the requestAnimationFrame approach in favor of the CSS Transitions approach. While that library is more performant than the previous attempts it has the big limitation that it only works with (custom) properties that contain <number> values.

This is due to the (clever!) approach to storing all the data into the font-variation-settings property.

The choice for font-variation-settings was made because its syntax is [ <opentype-tag> <number> ]# (with <opentype-tag> being equal to <string>) and it’s a property that is animatable.

🙏 The code for @bramus/style-observer started out as a fork of css-variable-observer. Thanks for your prior work on this, Artem!

Transitioning discretely animatable properties

Just two days ago, former colleague Jake Archibald shared a StyleObserver experiment of his in the CSS Working Group Issue discussing this. His approach relies on Style Queries and a ResizeObserver to make things work.

In the follow-up discussion, Jake foolishly wrote this:

Huh, could some of the discrete value animation stuff be used to make this work for non-numbers?

Whoa, that was exactly the clue that I needed to go out and experiment, resulting in this Proof of Concept. That POC was then used to build @bramus/style-observer into what it is now 🙂

🙏 Thanks for providing me with the missing piece of the puzzle there, Jake!

~

Spread the word

Feel free to repost one of the posts from social media to give them more reach, or link to this post from your own blog.

~


This content originally appeared on Bram.us and was authored by Bramus!


Print Share Comment Cite Upload Translate Updates
APA

Bramus! | Sciencx (2024-08-30T23:15:29+00:00) Introducing @bramus/style-observer, a MutationObserver for CSS. Retrieved from https://www.scien.cx/2024/08/30/introducing-bramus-style-observer-a-mutationobserver-for-css/

MLA
" » Introducing @bramus/style-observer, a MutationObserver for CSS." Bramus! | Sciencx - Friday August 30, 2024, https://www.scien.cx/2024/08/30/introducing-bramus-style-observer-a-mutationobserver-for-css/
HARVARD
Bramus! | Sciencx Friday August 30, 2024 » Introducing @bramus/style-observer, a MutationObserver for CSS., viewed ,<https://www.scien.cx/2024/08/30/introducing-bramus-style-observer-a-mutationobserver-for-css/>
VANCOUVER
Bramus! | Sciencx - » Introducing @bramus/style-observer, a MutationObserver for CSS. [Internet]. [Accessed ]. Available from: https://www.scien.cx/2024/08/30/introducing-bramus-style-observer-a-mutationobserver-for-css/
CHICAGO
" » Introducing @bramus/style-observer, a MutationObserver for CSS." Bramus! | Sciencx - Accessed . https://www.scien.cx/2024/08/30/introducing-bramus-style-observer-a-mutationobserver-for-css/
IEEE
" » Introducing @bramus/style-observer, a MutationObserver for CSS." Bramus! | Sciencx [Online]. Available: https://www.scien.cx/2024/08/30/introducing-bramus-style-observer-a-mutationobserver-for-css/. [Accessed: ]
rf:citation
» Introducing @bramus/style-observer, a MutationObserver for CSS | Bramus! | Sciencx | https://www.scien.cx/2024/08/30/introducing-bramus-style-observer-a-mutationobserver-for-css/ |

Please log in to upload a file.




There are no updates yet.
Click the Upload button above to add an update.

You must be logged in to translate posts. Please log in or register.