[css-houdini-drafts] [css-properties-values-api] feature request: observe element's computed style changes (with MutationObserver?) (#987)

trusktr has just created a new issue for https://github.com/w3c/css-houdini-drafts:

== [css-properties-values-api] feature request: observe element's computed style changes (with MutationObserver?) ==
## Problem

Given a reference to an element, I'd like to be able to observe property changes on that element in a non-polling fashion.

I could currently make a loop with `requestAnimationFrame` and use `getComputedValue` inside of it, like this:

```js
const el = document.querySelector('div')
const elStyle = getComputedStyle(el)

// Starts animation of the element with a CSS transition
setTimeout(() => el.classList.add('move'), 1000) 

function loop() {
  // Logs the current `left` value every frame.
  console.log(elStyle.getPropertyValue('left'))
  requestAnimationFrame(loop)
}

loop()
```

[JSFiddle demo](https://jsfiddle.net/fut1pd94/2/)

But this is not a good approach, because it does not know when the values have actually changed, and so continuously polling in a loop will use CPU over time.

## Solution

It would be better to have a push-based approach, so that computed style observers can execute only when style changes actually happen.

Maybe we can add this to [`MutationObserver`](https://developer.mozilla.org/en-US/docs/Web/API/MutationObserver); it seems like the best place, currently, where such a feature could live.

Maybe it would look something like this:

```js
// Create an observer that will watch for computed style changes.
const observer = new MutationObserver(mutations => {
  for(const mutation of mutations) {
    const style = mutation.styleMap // live StylePropertyMapReadOnly?
    console.log(style);
  }
});

// Start observing the target node for computed style changes.
observer.observe(targetNode, {
  computeStyle: true,
  stylePropertyFilter: ['left'] // observe only the "left" style property
});

// ...Later, we can stop observing.
observer.disconnect();
```

where perhaps `mutation.styleMap` is a "live" `StylePropertyMapReadOnly` instance, so that under the hood the engine can be more efficient by always returning that same instance, or something.

If we used `MutationObserver`, I think there would only ever be a single item inside the `mutations` list, unlike with other types of changes. I think we can encourage people to rely only on the last known value of a style property (inside the `styleMap`) in order to do something for next paint.

Or maybe, like other observer classes similar to `ResizeObserver`, there could be a separate `ComputedStyleObserver` class instead of adding this feature onto `MutationObserver`.

## Why

For example:

- When an element's `left` style changes, we want a WebGL scene to be updated with the observed values.
- We want to polyfill new CSS rendering features, and we may need to have a separate `<canvas>` for rendering the graphics.
- We want to implement new custom rendering libraries. For example imagine libraries like [a-frame](https://aframe.io), [CSSVR](https://github.com/keithclark/cssvr), or (disclaimer, my own library) [Lume](http://lume.io) having rendering features defined by `--custom` CSS properties defined with `CSS.registerProperty`)

All of the above use cases are possible today, but not as easy as it would be with an official observation API.

## Other way to achieve it?

Can we use a worklet, which will update on property changes, and somehow allow that worklet to communicate back to the main thread (or other worker thread) the changes that happened, so that we can avoid polling with rAF?

Please view or discuss this issue at https://github.com/w3c/css-houdini-drafts/issues/987 using your GitHub account

Received on Saturday, 2 May 2020 03:49:46 UTC