It is difficult to make custom CSS properties that have custom rendering abilities.
Using tools like PostCSS, it is not possible to make custom properties with specific rendering abilities because PostCSS (etc) compile new language features into old language equivalents. This does not give us any opportunity to define rendering effects, only language features.
So, if I want to make a new property called
distort that will bend the shape of an object in 3D space…
How would I do that?
I already have the graphical requirement fulfilled, which is that I can render things using
Three.js and expose the 3D objects as Custom Elements.
So the question is, how do I define a custom property in CSS, and react to changes of this custom property so I can update rendering?
This is basically a re-make of my other thread, A way to observe changes in CSS custom properties, but I wanted to start fresh.
Is there any established way of achieving this today?
It would be great to have a conventional way to achieve this so that we can prototype new rendering features, not just language features (PostCSS).
This ResizeObserver polyfill by @que-etc watches changes to style sheets, element style attributes, etc, using MutationObserver.
If there’s no other options, I might just start by taking that approach and re-computing rendering based on that technique.
If you want to implement new, custom ways to render an element, maybe the CSS Paint API is what you’re looking for. It lets you use a (limited) canvas to draw on, so you can get inventive there.
On the down side, they’re worklets, so they live in their own realm and you can’t exchange data with them (to answer your question as if we can have a way to observe custom properties).
But if it’s about new “properties” like
distort, maybe you’re golden.
How? Or do you mean only in the worklets?
Basically, I have custom elements that you compose to make a 3D scene, powered by WebGL behind the scenes. For example:
It is not possible to do this with a worklet canvas, as all the elements render into a single WebGL canvas (inspect the shadow root on the
i-scene element to see where the canvas is).
I’d like to react to property changes (f.e.
cast-shadow, etc) and update the WebGL graphics of this canvas.
Basically, the attributes that the elements have could also be CSS properties, just like in SVG attributes are also CSS properties.
Another problem is, properties are global, so if we add properties for a specific use case, then they can conflict with properties that someone else might want to add. It’d be nice to scope properties on a per-element basis (f.e.
align has certain meaning for my elements only, and if someone else defines it for their elements, then when applied to their elements it takes on their meaning instead of mine).
The Houdini APIs are aimed to provide the surface are to cover polyfilling and experimenting in CSS. It isn’t ready yet to be used in the wild, but Chrome is trying to push a workable implementation for developers to test with for feedback on the specs.
As things are today if you need production polyfills, I have no idea. It’s really fallback into JS to detect support and script what isn’t available.
Polyfills are available for some of the Houdini APIs.
Specifically here is the paint polyfill. https://github.com/GoogleChromeLabs/css-paint-polyfill
@Garbee @iank even with Houdini, I don’t see a way to react to property changes in a custom way other than with worklets (because they update with custom properties change), but in my case I can’t use a paint worklet because:
- I need WebGL (so far it is 2D only, but I imagine it’ll come later)
- I have a root i-scene element which contains a tree of nodes for rendering to WebGL, and I need those subtree nodes to have CSS properties that determine what is drawn in the root i-scene’s canvas (or paint worklet if it supported WebGL) but there’s no way for a worklet to detect changes of a (grand)child element CSS properties, and there’s just no way in general, not even that I see in Houdini.
So it looks like the only way to do it is with all of the following steps:
- use MutationOberver to detect existing and newly inserted
- MutationOberver on all
<style> elements. When their content changes, re-render all scenes after reading CSS property values and detecting if they changed.
- set up a polling mechanism on the
.sheet JS property of all style elements and detect changes to the OM. Some libs like JSS do not output any text content to style elements, only to the OM directly, so MutationOberver won’t work in that case.
Object.observe would’ve been awesome for this, if it was not deprecated.
- Similarly set up a MutationOberver on every node in my scene tree, to detect changes to
style="" attributes and thus scan for changes to CSS properties.
- Similarly poll for changes to each node’s style OM in case those don’t update attribute text values.
- use MO to detect existing and newly inserted
- use MO to detect changes to link href values.
- fetch those href resources so we know when they’ve been received so we can then scan for CSS properties.
- poll the link’s style OM? I don’t know if this exists, but I’m guessing it doesn’t. I’ll have to check.
- if any
<style> elements have
@import we need to do the same fetch trick as with link elements (or some way to detect download of resource finished) then scan for property changes.
- if the DOM has shadow roots, we need to implement a way to traverse closed ShadowDOM boundaries (by monkey patching attachShadow) so that we can traverse the whole scene tree when detecting property changes so we can determine which nodes inside the shadow root need re-rendering in the top-level canvas.
- I’m sure I missed something. What else?
Ideally, MutationOberver (or something) could just simply tell me when and which CSS properties of a given element have changed.
Check the https://github.com/GoogleChromeLabs/css-paint-polyfill/blob/master/src/index.js Houdini polyfill implementation which @iank mentioned, and it may answer both of your questions, as it implements the listener/callback for selected style property changes.
The implementation using MutationObserver for detecting style mutations and CSSStyleDeclaration.cssText , HTMLElement.style otherwise + caching of prev observed values [o compare], so you can reuse it for your needs.
Thanks for that. I finally took a look.
Looks like it uses requestAnimationFrame for poll-based observation of style sheets. I hope there’s a better push-based approach (like MutationObserver on textContent would be, but that doesn’t cover the direct CSS OM changes, I believe).
Another issue is, unless I missed it, it doesn’t seem to cover CSS transitions. Someone may want to animate a polyfilled property. I suppose for this, the animation engine needs to be in JS, for those non-native properties. Then we’d need to detect, for example, class name changes so that we know which state to animate to.
I may start with that as a starting point. I also found in another issue on GitHub someone else’s attempt at similar. I need to dig that up, and I’ll post it here.