Skip to content

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

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
trusktr opened this issue May 2, 2020 · 0 comments

Comments

@trusktr
Copy link

trusktr commented May 2, 2020

What

We want to observe computed property values (custom or not). For example:

  • When an element's computed left style changes (for any reason, including CSS transitions, or stylesheet addition/removal, etc), we want a WebGL scene to be updated based on the observed value.
  • We want to polyfill new CSS rendering features, and we may need to have a separate <canvas> for rendering the graphics, so we'll want to update the canvas any time the computed (possibly --custom) CSS properties of an element change for any reason.
  • We want to implement new custom rendering libraries (and similarly to the previous point, we will render to a canvas). For example imagine libraries like a-frame, CSSVR, or (disclaimer, my own library) Lume 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. Current techniques involve polling with requestAnimationFrame.

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:

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

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; it seems like the best place, currently, where such a feature could live.

Maybe it would look something like this:

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

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? Are worklets able to communicate with the main thread or any other thread?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant