An open API service indexing awesome lists of open source software.

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.

Awesome Lists containing this project

README

          

# Style Observer

API
·
Tests




- [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.

[![npm](https://img.shields.io/npm/v/style-observer)](https://www.npmjs.com/package/style-observer)
[![gzip size](https://img.shields.io/badge/gzip-2.73kB-blue)](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)



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/).