Document that any custom property being observed must had been set before observer

#133 · open · 9 comments

View on GitHub ↗

n0099

https://jsfiddle.net/fkws0v7c/50/ ```html <main> <div class="padding">Scroll down or hover on the gray zone:</div> <div class="item sticky"><p></p></div> </main> ``` ```js import StyleObserver from "https://cdn.jsdelivr.net/npm/[email protected]/+esm"; const observer = new StyleObserver(console.log); observer.observe(document.querySelectorAll(".item, .item > p"), "--my-custom-property"); ``` ```css main { height: 200vh; } .padding { height: 10vh; } .item { background: darkgray; height: 50vh; } .item, .item.sticky > p { --my-custom-property: 'initial'; } .item:hover { --my-custom-property: 'hovering'; } .item::before { content: var(--my-custom-property); font-size: 2rem; } .item.sticky { /* https://stackoverflow.com/questions/25308823/targeting-positionsticky-elements-that-are-currently-in-a-stuck-state/79471060#79471060 */ container-type: scroll-state; position: sticky; top: 0; } @container scroll-state(stuck: top) { .item.sticky > p { --my-custom-property: 'stuck'; } .item.sticky > p::before { content: var(--my-custom-property); font-size: 2rem; } } ``` --- If we delete lines ```css .item, .item.sticky > p { --my-custom-property: 'initial'; } ``` from CSS, the observe callback will never trigger.

Comments

DmitrySharabin

Hey @n0099, Thank you for reporting the issue. I can reproduce this, investigating...

DmitrySharabin

Some intermediate results: even with the CSS removed, the observer callback is triggered if we hover the `.item` element (close to its top) and, while hovering, scroll the page so that the `.item.sticky` is stuck at the top. If we don't move the mouse but scroll again so that `.item.sticky` is no longer stuck, the callback will be called again. As a result, we have two cases when the Style Observer is not fired: 1. On `.item` hover 2. On stuck if the `.item` element is not hovered.

DmitrySharabin

Okay, here is what we have. In Style Observer, if a custom property (e.g., `--foo`) is not registered explicitly, and the browser is affected by the [unregistered property transition bug](https://issues.chromium.org/issues/360159391), we register it as follows: ```css @property --foo { syntax: "*"; inherits: true; } ``` This allows us to observe the property changes. However, as it turned out, this is not enough. Suppose the property is updated on hover or in a container query rule (not all, probably in the new ones, like `@container scroll-state()`; we have [tests](http://127.0.0.1:5500/tests/?test=change) where the changes are correctly observed) and _it doesn't have an initial value_ (set in some other CSS rule). In that case, the transition events are **not fired**, and we can't observe the property changes. Reduced testcase: https://codepen.io/dmitrysharabin/pen/xbGPVmE?editors=1111 Safari doesn't seem affected by this bug. As a possible fix, I'd suggest assigning the property an initial value (one that won't break the author's code, if we have any). Something like that: ```css @property --foo { syntax: "*"; inherits: true; initial-value: ""; } ``` @LeaVerou, what do you think? I think @n0099 pointed out another browser bug for us to consider.

LeaVerou

Oof. I don’t think there is any initial value we can pick that is guaranteed to work in every single case and won’t break anything. Is this a regression or can it be reproduced on every version?

DmitrySharabin

> Is this a regression or can it be reproduced on every version? I was also thinking it's a regression at first. But no. It's reproducible on every version. I can't even write a test for it (because IDK how to programmatically “emulate” the hovered state); I tried a while ago (before we released the first version). Is it something we should document then?

LeaVerou

I don’t think I have a good grasp of what triggers the bug exactly. So it’s specific to hover and container queries? What else triggers it?

DmitrySharabin

> I don’t think I have a good grasp of what triggers the bug exactly. So it’s specific to hover and container queries? What else triggers it? I experimented with it more (https://codepen.io/dmitrysharabin/pen/xbGPVmE?editors=1111). All of the following apply to _non-registered_ custom properties _without an initial value_ only. 1. `:hover`, `:empty`, `:invalid` (I might suggest all state pseudo classes) don't trigger the transition events at all 3. `:focus` doesn't trigger the transition events at all if _the element gets focus with the keyboard_; If we focus the element with the mouse, the transition events are fired when the element gets focus, but are not fired when the element loses it 4. Media queries and container queries (not only `scroll-state()`) don't trigger the transition events at all @LeaVerou, what else should I try? **P.S.** See the codepen in Safari to get the expected behavior without setting the property to any initial value.

LeaVerou

> I might suggest all state pseudo classes `:state()` too? What about other pseudo-classes? `:nth-*`, `:lang()` etc What _doesn't_ trigger the bug? E.g. class names added via JS? > :focus doesn't trigger the transition events at all if the element gets focus with the keyboard; If we focus the element with the mouse, the transition events are fired when the element gets focus, but are not fired when the element loses it I suspect that’s a red herring, and has to do with the fact that when you focus with the keyboard, there is a `:hover` that applies first, so the property doesn't transition from its initial value, but from `:hover`. Hence why it's not fired when it loses focus: because then it goes to initial again. I just experimented with your codepen, and it seems that even `initial-value:;` fixes it. Unfortunately, even that is way too invasive, e.g. it breaks any `var(--foo, fallback)` reference.

DmitrySharabin

> I suspect that’s a red herring, and has to do with the fact that when you focus with the keyboard, there is a `:hover` that applies first, so the property doesn't transition from its initial value, but from `:hover`. Hence why it's not fired when it loses focus: because then it goes to initial again. Oh, right. This is the thing I observed in @n0099's demo. So, yeah, it's a red herring. > What about other pseudo-classes? `:nth-*`, `:lang()` etc When we use those pseudo-classes, we give the custom property an initial value. So, technically, we no longer transition from “nothing” to “something,” but from “something” to “something-else.” In that case, the transition events are fired. > `:state()` too? I thought it's a pseudo-class that matches a custom element's states, no? > What _doesn't_ trigger the bug? E.g. class names added via JS? Still couldn't find any. IDK if anything can force the browser to transition from “nothing” to “something.” I was thinking of `@starting-style`, but even in that case, we need to set the custom property to ”something” before it can be transitioned. It feels like I'm at a dead end right now. 🤷‍♂️ I wonder if the fact that it works in Safari but not in other browsers is actually a WebKit bug. What if we should consider not observing custom properties without an initial value as a Style Observer limitation, and document it as one? 🤔