https://github.com/LeaVerou/style-observer
Run JS when a CSS property changes. Any CSS property.
https://github.com/LeaVerou/style-observer
css observer style-observer
Last synced: 2 months ago
JSON representation
Run JS when a CSS property changes. Any CSS property.
- Host: GitHub
- URL: https://github.com/LeaVerou/style-observer
- Owner: LeaVerou
- Created: 2025-01-14T06:43:59.000Z (over 1 year ago)
- Default Branch: main
- Last Pushed: 2025-02-19T14:59:05.000Z (over 1 year ago)
- Last Synced: 2025-02-19T15:47:24.441Z (over 1 year ago)
- Topics: css, observer, style-observer
- Language: JavaScript
- Homepage: https://observe.style
- Size: 365 KB
- Stars: 342
- Watchers: 2
- Forks: 3
- Open Issues: 5
-
Metadata Files:
- Readme: README.md
Awesome Lists containing this project
README
#
Style Observer
- [Install](#install)
- [Usage](#usage)
- [Future Work](#future-work)
- [Limitations & Caveats](#limitations-%26-caveats)
- [Prior Art](#prior-art)
A robust, production-ready library to observe CSS property changes.
Detects browser bugs and works around them, so you don't have to.
[](https://www.npmjs.com/package/style-observer)
[](https://pkg-size.dev/style-observer)
- ✅ 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](https://pkg-size.dev/style-observer), ESM-only code, with no dependencies
- ✅ [200+ unit tests](tests) you can run in your browser of choice
- ✅ Throttling per element
- ✅ Does not overwrite existing transitions
## 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)
Except display, transition, animation
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](https://www.netlify.com/) CDN:
```js
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:
```sh
npm install style-observer
```
and then, if you use a bundler like Rollup or Webpack:
```js
import StyleObserver from "style-observer";
```
and if you don’t:
```js
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:
```js
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:
```js
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:
```js
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:
- `target`: The element that changed
- `property`: The property that changed
- `value`: The new value of the property
- `oldValue`: The previous value of the property
## Future Work
- Observe pseudo-elements
- `immediate` convenience option that fires the callback immediately for every observed element
## Limitations & Caveats
### Disconnected elements
You cannot observe changes on elements not connected to a document.
### Transitions & Animations
- You cannot observe `transition` and `animation` properties.
- You cannot observe changes caused by CSS animations.
### Observing `display`
Observing `display` is inconsistent across browsers (see [relevant tests](tests/?test=display)):
| 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`](https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API).
### 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.
- Early attempts used polling. Notable examples were [`ComputedStyleObserver` by Keith Clark](https://github.com/keithclark/ComputedStyleObserver)
and [`StyleObserver` by PixelsCommander](https://github.com/PixelsCommander/StyleObserver)
- [Jane Ori](https://propjockey.io) was the first to do better than polling, her [css-var-listener](https://github.com/propjockey/css-var-listener) using a combination of observers and events.
- [css-variable-observer](https://github.com/fluorumlabs/css-variable-observer) by [Artem Godin](https://github.com/fluorumlabs) 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](https://github.com/bramus) pioneered a way to do it "properly" in [style-observer](https://github.com/bramus/style-observer),
thanks to [`transition-behavior: allow-discrete`](https://caniuse.com/mdn-css_properties_transition-behavior) becoming Baseline and even [blogged about all the bugs he encountered along the way](https://www.bram.us/2024/08/31/introducing-bramus-style-observer-a-mutationobserver-for-css/).
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](https://lea.verou.me/2025/style-observer/) for more details.
By [Lea Verou](https://lea.verou.me/) and [Dmitry Sharabin](https://d12n.me/).