Closed khushalsagar closed 4 months ago
+1 to view transition names being tree-scoped. The implication of the current spec/implementation is that custom elements that are supposed to be encapsulated can animate each other's internals if they know the internal vt-names.
More work in the implementation but makes sense to me.
The CSS Working Group just discussed [css-view-transitions-1] Should view transition names be tree scoped?
, and agreed to the following:
RESOLVED: view-transition-name lookup is tree-scoped
View-transition names have been working seamlessly inside shadow trees until now. After updating Chrome to version 126, view-transition-name no longer pierces the shadow tree. My entire project is built with custom elements (CE), including individual elements, pages, and the root. This change has significantly degraded the user experience of my website, and I am currently at a loss for how to address this issue.
Perhaps we should follow Scroll Timeline example here which treats everything as a flat tree:
Like most operations in CSS besides selector matching, features in this specification operate over the flattened element tree.
In ViewTransitions, we limit view-transition-name
in this algorithm, but in the same algorithm use flat tree ancestors, which seems contradictory.
I do not think we should violate shadow DOM principles by exposing shadow DOM names to the root element (in the light DOM). Having a way to run transitions outside of the root element seems like a better solution to the problem @shonya3 is facing.
I'm not sure I understand the distinction. Transitions can run outside of the root element, but they can't reference things within the shadow DOM. Running a transition within the shadow DOM would only solve some cases, but not all of them. For example, icon to detailed view morph wouldn't be possible if the icon is a part of a custom web component.
If view-transition-name: shadow
is applied to a shadow DOM element, having it exposed to the root element through ::view-transition-group(shadow)
and friends, is a violation of shadow DOM should work. The shadow DOM element and the root element are in different tree scopes.
I think this discussion transcends View Transitions and touches all CSS properties that define a name: view-transition-name
, scroll-timeline-name
, anchor-name
, container-name
(and any others I can’t think of right now).
In general I think we should respect the aspect that these names don’t leak out of shadow roots. However, in some cases authors would explicitly want this, e.g. websites where they have a big tree of Web Components.
(For context: the re-opening of this issue was sparked by this report from an author in which they let us know that the View Transitions from the overview to the detail page on their site are no longer working in Chrome 126. Culprit is the view-transition-name
being set on an element that is contained in a Web Component)
In https://github.com/w3c/csswg-drafts/issues/8915#issuecomment-1883461265 there was talk of breaking style containment into two parts: containing names and all the rest. I would love to see a solution where names are contained by default – to not break one of the premises of using shadow DOM – but that there is a way to make the names visible only after the author opts-in (and only for open shadowroots).
This “contain by default” could be part of the UA stylesheet – e.g. :host { contain: names; }
– which authors could then choose to unset/reset/adjust.
My understanding from @bramus is also that timeline-scope
is able to lift a scroll-timeline-name
from within a shadow tree to outside of the shadow root. This seems inconsistent with the treating we've resolved on for view-transition-name
.
To support cases that were broken by this change, we'd like to have some way of loosening this restriction, whether it's with contain: names
or something else. I suspect having bespoke language in the VT spec that prevents cross-shadow-dom interactions isn't the right approach
I'm not sure I understand the distinction. Transitions can run outside of the root element, but they can't reference things within the shadow DOM. Running a transition within the shadow DOM would only solve some cases, but not all of them. For example, icon to detailed view morph wouldn't be possible if the icon is a part of a custom web component.
If you end up with such a component, the components are probably poorly separated in the first place.
To me, it sounds like scoped element transitions would be the better solution to this problem.
To me, it sounds like scoped element transitions would be the better solution to this problem.
That doesn’t work for websites with a tree of custom elements.
In https://divicards-site.pages.dev/ for example the author has a <e-divination-card>
custom element that they want to transition from the overview to the detail page. The value for view-transition-name
gets set on the fly on the first div of that component, which seems pretty logical to me.
Even if they did set that view-transition-name
on the <e-divination-card>
directly it wouldn’t work, because that element itself is a child of another custom element:
<app-root>
<p-home>
<e-card-with-sources>
<e-divination-card></e-divination-card>
</e-card-with-sources>
</p-home>
</app-root>
My understanding from @bramus is also that
timeline-scope
is able to lift ascroll-timeline-name
from within a shadow tree to outside of the shadow root. This seems inconsistent with the treating we've resolved on forview-transition-name
.To support cases that were broken by this change, we'd like to have some way of loosening this restriction, whether it's with
contain: names
or something else. I suspect having bespoke language in the VT spec that prevents cross-shadow-dom interactions isn't the right approach
Perhaps we should find a way for allow shadow hosts to explicitly delegate view-transition names (and other CSS names), similar to how focus is delegated. So you'd have to define something on the host or the shadow root that says which particular CSS names pierce through the shadow boundary. I didn't think through all the details of this but it's important that a solution we come up with keeps the encapsulation between the shadow and the host, otherwise a custom element would interfere with host view-transitions and vice versa.
@bramus's contain: names
solves it. Also for scoped view transitions we will very likely want a property like view-transition-scope
which would also limit the name exposure. I think delegating names is a different approach that would work, but I'd want there a way to say "all names" so we don't have to maintain two sets of names for components which should and want to expose all of the names
Couple things:
@font-face
and other features before and I don't think we have a general solution. Designing solutions for each feature independently seems quite bad. I hope @tabatkins and @fantasai can help out here.Couple things:
- We should try to solve this problem for CSS naming generally. It's come up for
@font-face
and other features before and I don't think we have a general solution. Designing solutions for each feature independently seems quite bad. I hope @tabatkins and @fantasai can help out here.
+1 for a more broad solution.
Happy to volunteer some time for groundwork when I am back from OOO. Perhaps this can be a good TPAC CSS/whatwg joint session topic (with hope to make some progress before)
- We should try to solve this problem for CSS naming generally. It's come up for
@font-face
and other features before and I don't think we have a general solution. Designing solutions for each feature independently seems quite bad. I hope @tabatkins and @fantasai can help out here.
Agreed. I had already given Tab an internal ping about this :)
- Exposing names by default very much goes against encapsulation and is not acceptable. Exposing names always needs to be opt-in.
Agreed. The default should be that all names are contained.
- Opting into exposing names globally also seems fraught. That would violate the encapsulation of the encompassing shadow tree. It re-exposing names exposed to it seems reasonable, but that would only go one level up again.
With contain: names
(or maybe better contain-names: …
to not overwrite existing contain
declarations on those elements) you would be able to tweak this on a per-component basis, so it would only bubble up from one shadow boundary to the other.
I think the needs are to:
With this authors can also easily opt-in to allowing names to bubble up to the root (i.e. global) in just one go by having a rule that reads :host { /* <declaration-here-that-indicates-to-contain-no-names> */ }
– this for situations where they are in full control of all custom elements they use.
This contain-names
property should be limited to apply to only shadow hosts, because I don’t think we’d want authors to sprinkle this around on just any arbitrary element in the tree.
Before jumping to the solutions, I want to see how far part()
can get us there with the current use cases people are trying to build. Seems like it can work for a lot of things like animating an element's internal icon etc?
^ that is the question on this issue: https://github.com/w3c/csswg-drafts/issues/10303. And reading through the discussion here, I also feel like ::part
is the correct way to name elements inside the shadow DOM.
This is worth discussing in the context of all named references for CSS in general, added https://github.com/w3c/csswg-drafts/issues/10304 to the agenda with more context for it.
One of our public-facing apps at Microsoft was also broken by this change. Our app is built entirely with web components; a tree of web components with shadow roots.
In our case, when the user navigates our SPA, we want to view transition everything except that header and footer. So we assigned those a separate view-transition-name
. This worked until Chrome 126.
We'd love a way to say, "this web component has a different view-transition-name" so that it can remain in place during view transitions. I don't feel strongly about any of the proposals so far. So long as we can do what we did before: give elements different view-transition-names so they don't take part in view transition.
One of our public-facing apps at Microsoft was also broken by this change. Our app is built entirely with web components; a tree of web components with shadow roots.
In our case, when the user navigates our SPA, we want to view transition everything except that header and footer. So we assigned those a separate
view-transition-name
. This worked until Chrome 126.We'd love a way to say, "this web component has a different view-transition-name" so that it can remain in place during view transitions. I don't feel strongly about any of the proposals so far. So long as we can do what we did before: give elements different view-transition-names so they don't take part in view transition.
So if I understand correctly, the issue here was not about mixing light-DOM elements with shadow-DOM elements, but rather that all the elements participating were inside the shadow DOM, and now this can't work?
Correct, it's not about mixing light DOM with shadow DOM. Our whole app is built with web components, so it's all shadow DOM.
And the issue is that, we don't want our header and footer to take part in view transition. Since header and footer don't change, we want them to stay in place. We accomplished this by setting view-transition-name
on header and footer. This worked until Chrome 126. Now with the new behavior, the browser ignores view-transition-name
, and thus enlists them in the page transition.
You can see it for yourself in this minimal repo. Or you can see it on our live public-facing site, apps.microsoft.com - just click a link and observe that the header and footer participate in the view transition.
We want a way to say, "This element shouldn't participate in the view transition", even if that element is inside a shadow root.
Correct, it's not about mixing light DOM with shadow DOM.
But you’re calling document.startViewTransition
– with the pseudos getting injected onto :root
– so it kinda is … no? For this to work the Light DOM needs to see the view-transition-name
values declared in the Shadow DOM.
Correct, it's not about mixing light DOM with shadow DOM.
But you’re calling
document.startViewTransition
– with the pseudos getting injected onto:root
– so it kinda is … no? For this to work the Light DOM needs to see theview-transition-name
values declared in the Shadow DOM.
I think we should find a way where the pseudos are also defined in the shadow tree, but we have to deal with edge cases like ::view-transition-group:only-child
Thanks @JudahGabriel! We're taking this breakage very seriously and we'll find a solution.
@shonya3 does your use case involve sharing view-transition-names across shadow boundaries, or it's like this comment, where the captured element is encapsulated within a shadow DOM?
@noamr Yep, i am sharing across boundaries, something like this or this Bramus explained my case perfectly.
Gotcha. I see that you're using shadow parts already in the first example? Do you think enabling view-transition-name
on ::part
like in #10303 work for your use case? Superficially it seems like the right direction.
If I may mention my own use case here: my team is working on an app built entirely with web components, so we have nested components with shadow roots. Chrome 126 broke our View Transitions.
Enabling view-transition-name
on ::part
would be a useful solution if we also had scoped View Transitions. Starting a View Transition on a deeply nested custom element, then giving names to its sub-components with ::part
sounds doable. But without scoped View Transitions, we'd have to use the exportparts
attribute all the way up to the document element, since that's the only element we can start a View Transition on right now, right?
That sounds very impractical. A solution like a CSS attribute, as mentioned here, looks like it would help us a lot more in that case.
If I may mention my own use case here: my team is working on an app built entirely with web components, so we have nested components with shadow roots. Chrome 126 broke our View Transitions.
Enabling
view-transition-name
on::part
would be a useful solution if we also had scoped View Transitions. Starting a View Transition on a deeply nested custom element, then giving names to its sub-components with::part
sounds doable. But without scoped View Transitions, we'd have to use theexportparts
attribute all the way up to the document element, since that's the only element we can start a View Transition on right now, right?That sounds very impractical. A solution like a CSS attribute, as mentioned here, looks like it would help us a lot more in that case.
Thanks for sharing! Do your transitions cross shadow boundaries (an element in a shadow transitions into an element in another shadow/light DOM etc) or it's more like wanting transitions within the same shadow root to work?
Thanks for sharing! Do your transitions cross shadow boundaries (an element in a shadow transitions into an element in another shadow/light DOM etc) or it's more like wanting transitions within the same shadow root to work?
The transitions cross shadow boundaries. view-transition-name
s are set on components nested a few levels deep within a container's shadow root, and the same view-transition-name
will be set on different elements, in different shadow roots, between the start and end state of the transition.
However, there is still a "transition container" element that contains every transitioned element within its shadow root (or its children's shadow roots, or their children's, etc). I feel like ::part
could still work within the "transition container" here, provided the correct chain of exportparts
is used on the nested children. Then we would move all view-transition-name
s definitions into the CSSStyleSheet that's adopted by the "transition container" element. (It would also be nice to define the transition itself in that same style sheet, but for now all ::view-transition-*
selectors have to be in a separate style sheet adopted by the document.)
The main pain point remains that we would also have to modify the whole chain of ancestor elements, from the "transition container" all the way up to the root of the document, to use exportparts
on them as well to make things work with document.startViewTransition
.
And it would make it harder to move the view-transition-name
s between the start and end state of the transition: before Chrome 126, some JavaScript could directly set (or remove) the names (or CSS classes) on the appropriate elements during the View Transition update callback. Now, that JS would have to set (or remove) part
s instead, and we'd have to make sure they're exported through all ancestors, and set the names from the document.
@Remiscan gotcha. So sounds like in the long run, something like a combination of ::part
and element-scoped transition would be ideal for your use case.
Thanks for sharing in detail, we're discussing a few options internally and want to understand the use cases better.
Summarizing internal conversation about this:
We're hitting the same snag that we've hit in anchor positioning and view-timeline, where to support ::part
(as in #10303) is problematic because it requires tracking of "where did this style come from", and style doesn't always come from a particular tree scope, e.g. it can come from an inline attribute set by a script that's not from a particular tree. See https://drafts.css-houdini.org/css-properties-values-api/#shadow-dom.
In other specs the current direction is to have these names operate on the flat tree, and scope them using a separate property (e.g. anchor-scope
) rather than using the shadow boundary for this scoping. Perhaps we could have a "soft" encapsulation for this by defaulting these scope property to be contained in shadow roots.
In any case, we should be consistent here and in anchor-positining/scroll-driven positioning.
Proposing that if anchor positioning and scroll-driven animations don't change course to use shadow encapsulation, including figuring out the "where did the style come from" issue, then we revert the view-transition change so that view-transition-names are in the flat tree to be consistent, and introduce view-transition-scope
which would be as consistent as possible with anchor-scope
(will prepare a proposal for that).
@noamr Yep, i am sharing across boundaries, something like this or this Bramus explained my case perfectly.
Gotcha. I see that you're using shadow parts already in the first example? Do you think enabling
view-transition-name
on::part
like in #10303 work for your use case? Superficially it seems like the right direction.
Enabling view-transition-name for ::part looks logical and natural to me.
But the main concern for me is whether it will work without much hassle.
In other specs the current direction is to have these names operate on the flat tree, and scope them using a separate property (e.g. anchor-scope) rather than using the shadow boundary for this scoping.
No, anchor positioning is tree-scoped, and there's no plan to change it. I think the issue and proposal in https://github.com/w3c/csswg-drafts/issues/10325 was just misunderstood. The anchor-scope property does not replace tree-scoping, it's just an additional mechanism on top of it.
Scroll-driven animations however is more like what you describe. For some reason I never understood, timeline names are not tree-scoped. https://github.com/w3c/csswg-drafts/issues/8135. But I'm not sure we should draw inspiration from this, we might want to fix SDA instead.
Thanks @andruud, this makes sense. So to clarify, the resolution here is fine and matches anchor positioning, and should also support things like putting a view-transition-name
on a ::part
, but the way we interpreted it in the implementation (and perhaps in the spec wording?) is a bug.
@shonya3 @Remiscan @JudahGabriel as of Chrome M127 you should be able to give a view-transition-name
to a ::part
in the document scope. I know this is not an ideal solution for every use-case given that we don't have element-scoped transitions yet, but it should at least give you a way to work around the regression you've experienced in the short term. WebKit is following through with the same.
Just implemented my first try with new approach.
index.html
p-card::part(card),
p-home::part(card) {
view-transition-name: card;
}
p-home.ts
Somewhere in render
method...
Conditionally set part="card" attribute
this.paginated.map(card => {
return html`<li>
${card === this.activeCard
? html`<e-card-with-sources
.name=${card}
.divcordTable=${this.divcordTable}
.cardSize=${this.cardSize}
.sourceSize=${this.sourceSize}
@navigate=${() => this.#setActiveCard(card)}
part="card"
></e-card-with-sources>`
: html`<e-card-with-sources
.name=${card}
.divcordTable=${this.divcordTable}
.cardSize=${this.cardSize}
.sourceSize=${this.sourceSize}
@navigate=${() => this.#setActiveCard(card)}
></e-card-with-sources>`}
</li>`;
});
And there is activeCard?:string global state to set it on p-home render and update on navigate(emits by divination card element on any inner link element click). Not too bad, to be honest 🤓 https://divicards-site.pages.dev/
The spec currently traverses all DOM elements to look for view transition names, see text here. This should probably be tree scoped similar to other CSS naming conventions like scroll timeline and I'm guessing anchor positioning (@tabatkins ?).
@nt1m fyi.