Style Observer

A robust, production-ready library to observe CSS property changes. Detects browser bugs and works around them, so you don't have to.

npm gzip size

Compatibility

Feature Chrome Safari Firefox % of global users
Custom properties 117 17.4 129 89%
Custom properties (registered with an animatable type) 97 16.4 128 93%
Standard properties (discrete)
117 17.4 129 89%
Standard properties (animatable) 97 15.4 104 95%

Install

The quickest way is to just include straight from the Netlify CDN:

import StyleObserver from "https://observe.style/index.js";

This will always point to the latest version, so it may be a good idea to eventually switch to a local version that you can control. E.g. you can use npm:

npm install style-observer

and then, if you use a bundler like Rollup or Webpack:

import StyleObserver from "style-observer";

and if you don’t:

import StyleObserver from "node_modules/style-observer/dist/index.js";

Usage

You can first create the observer instance and then observe, like a MutationObserver. The simplest use is observing a single property on a single element:

const observer = new StyleObserver(records => console.log(records));
observer.observe(document.querySelector("#my-element"), "--my-custom-property");

You can also observe multiple properties on multiple elements:

const observer = new StyleObserver(records => console.log(records));
const properties = ["color", "--my-custom-property"];
const targets = document.querySelectorAll(".my-element");
observer.observe(targets, properties);

You can also provide both targets and properties when creating the observer, which will also call observe() for you:

import StyleObserver from "style-observer";

const observer = new StyleObserver(callback, {
	targets: document.querySelectorAll(".my-element"),
	properties: ["color", "--my-custom-property"],
});

Both targets and properties can be either a single value or an iterable.

Note that the observer will not fire immediately for the initial state of the elements (i.e. it behaves like MutationObserver, not like ResizeObserver).

Records

Just like other observers, changes that happen too close together (set the throttle option to configure) will only invoke the callback once, with an array of records, one for each change.

Each record is an object with the following properties:

Future Work

Limitations & Caveats

Transitions & Animations

Observing display

Observing display is inconsistent across browsers (see relevant tests):

Rule Chrome Firefox Safari Safari (iOS) Samsung Internet
From display: none
To display: none
From not none to not none

To observe elements becoming visible or not visible, you may want to take a look at IntersectionObserver.

Changing transition properties after observing

If you change the transition/transition-* properties dynamically on elements you are observing after you start observing them, the easiest way to ensure the observer continues working as expected is to call observer.updateTransition(targets) to regenerate the transition property the observer uses to detect changes.

If running JS is not an option, you can also do it manually:

  1. Add , var(--style-observer-transition, --style-observer-noop) at the end of your transition property. E.g. if instead of transition: 1s background you’d set transition: 1s background, var(--style-observer-transition, --style-observer-noop).
  2. Make sure to also set transition-behavior: allow-discrete;.

Prior Art

The quest for a JS style observer has been long and torturous.

While StyleObserver builds on this body of work, it is not a fork of any of them. It was written from scratch with the explicit goal of extending browser support and robustness. Read the blog post for more details.