A robust, production-ready library to observe CSS property changes. Detects browser bugs and works around them, so you don't have to.
- ✅ Observe changes to custom properties
-
✅ Observe changes to standard properties (except
display
,transition
,animation
) - ✅ Observe changes on any element (including those in Shadow DOM)
- ✅ Lightweight, ESM-only code, with no dependencies
- ✅ 150+ unit tests you can run in your browser of choice
- ✅ Throttling per element
- ✅ Does not overwrite existing transitions
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)
Except display , transition , animation
|
117 | 17.4 | 129 | 89% |
Standard properties (animatable) | 97 | 15.4 | 104 | 95% |
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";
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
).
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:
-
target
: The element that changed -
property
: The property that changed -
value
: The new value of the property -
oldValue
: The previous value of the property
- Observe pseudo-elements
-
immediate
convenience option that fires the callback immediately for every observed element
- You cannot observe
transition
andanimation
properties. - You cannot observe changes caused by CSS animations or transitions.
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
.
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:
- Add
, var(--style-observer-transition, --style-observer-noop)
at the end of yourtransition
property. E.g. if instead oftransition: 1s background
you'd settransition: 1s background, var(--style-observer-transition, --style-observer-noop)
. - Make sure to also set
transition-behavior: allow-discrete;
.
The quest for a JS style observer has been long and torturous.
- Early attempts used polling. Notable examples were
ComputedStyleObserver
by Keith Clark andStyleObserver
by PixelsCommander - Jane Ori was the first to do better than polling, her css-var-listener using a combination of observers and events.
-
css-variable-observer by Artem Godin pioneered using transition events to observe property changes, and used an ingenious hack based on
font-variation-settings
to observe CSS property changes. - Four years, later Bramus Van Damme pioneered a way to do it "properly" in style-observer,
thanks to
transition-behavior: allow-discrete
becoming Baseline and even blogged about all the bugs he encountered along the way.
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.
By Lea Verou and Dmitry Sharabin.