Repository navigation
[css-view-transitions-2]: Nested ::view-transition-group should have a container pseudo. #11926
Description
One of the common use cases for view-transition-group: contain that we're considering is a scroller with items within in and a view transition that shuffles the items within the container.
This is achieved with something like
.container {
view-transition-name: clipper;
view-transition-group: contain;
}
.container > .item {
view-transition-name: auto;
}In the current spec this builds the following pseudo tree:
:root
|-::view-transition
| |-::view-transition-group(clipper)
| |-::view-transition-image-pair(clipper)
| | |-::view-transition-old(clipper)
| | |-::view-transition-new(clipper)
| |-::view-transition-group(auto)
| | |-::view-transition-image-pair(auto)
| | |-::view-transition-old(auto)
| | |-::view-transition-new(auto)
| |-...
Essentially, ::view-transition-groups for the items are siblings of ::view-transition-image-pair(clipper)
Now, to achieve the desired effect we need to put a clip on ::view-transition-group(clipper). This works well in a limited number of cases. The problem happens once you have a border (with maybe a rounded clip) on the original clipper element. Because the ::view-transition-group(clipper) element is sized to be the border box of the original element, and the fact that border itself is baked into ::view-transition-{old/new}(clipper), we end up in a situation where it isn't trivial to properly clip the nested groups and also not clip the border. The solution is possible, but not ergonomic where one has to copy the border to the group pseudo, and do some sizing/offset magic to account for the fact that we're sized to the border box.
@bramus put together a demo here:
https://codepen.io/bramus/full/EaxvMXG/11b9fcbe8ebf42034110d3b486db037f
One improvement we can make is to introduce a new pseudo element ::view-transition-group-container (name tbd) that's sized to the padding box of the original element (I think that's the right clipping box or is it content box?) and hosts the nested groups:
:root
|-::view-transition
| |-::view-transition-group(clipper)
| |-::view-transition-image-pair(clipper)
| | |-::view-transition-old(clipper)
| | |-::view-transition-new(clipper)
| |-::view-transition-group-container(clipper)
| | |-::view-transition-group(auto)
| | | |-::view-transition-image-pair(auto)
| | | |-::view-transition-old(auto)
| | | |-::view-transition-new(auto)
| | |-...
With that, adding a clip to ::view-transition-group-container(clipper) does not clip the captured box decorations of the original element, and there's less math required.
Cons:
- The clip won't be trivial with corner shape changes
- This clip won't clip other captured content from the original element that is not itself view transition named
- There are likely other issues, but @bramus's demo has an example of what this may look like and it seems a bit more ergonomic.
Metadata
Metadata
Assignees
Labels
Type
Projects
- StatusShow more project fieldsRegular agenda
Activity
bramus commentedon Mar 12, 2025
To give people an idea of how not trivial this is with the current implementation in which the nested groups are direct children of the outer group: when using nested View Transitions that uses a border, you don’t want a nested snapshot to appear over the border.
You can see this problem in the “Nested View Transitions (::v-t-group in ::v-t-group)” section of the playground and also in the following screenshot:
To mitigate authors need to apply the border onto the
::view-transition-group(clipper)and then reposition all contained snapshots using the following CSS:This approach is implemented in the “Nested View Transitions (
::v-t-groupin::v-t-group) + Manual Fix” (with thehotpinkborder) section of the playground and can be see in this screenshot:All this “reposition all the things” does not need to happen with a new
::view-transition-group-containerpseudo. There are two approached to positioning this new pseudo within the::view-transition-group(clipper):::view-transition-group(clipper)::view-transition-group-container(clipper)With the first approach, the
::view-transition-group-container()would be positioned inside the::view-transition-group(clipper)using an inset so that it takes up the content-box of the::view-transition-group(clipper). These styles would be generated by the User Agent:As a result, the author only needs to write these styles anymore:
The tricky thing for authors is that they need to compute the inner radius, which is equal to the outer-radius minus the border-size.
You can see this in action in the “Nested View Transitions, alt (::v-t-group in ::v-t-content)” (the one with the yellow border, right after the hotpink one) section of the playground.
With the first approach, the
::view-transition-group-container()would be positioned inside the::view-transition-group(clipper)like before, but it would be given a transparent border equal to the size of the border that was captured.The UA generated styles would then look like this:
This gives authors the benefit that they don’t need to calculate the inner radius for the
::view-transition-nested-content(clipper):You can see this in action in the “Nested View Transitions, alt (::v-t-group in ::v-t-content) + fake transparent border” (the one with the yellow and green dotted border) section of the playground. For demonstration purposes the transparent border was given the color
lime.(The green lines on the screenshot align with the left and top edge of the
::view-transition-group-container())This leads me to the following questions we should ask ourselves:
calc()the inner radius when they have onebox-sizing: border-box(perhaps with !important in UA style sheet)?overflow: clipgo in the UA stylesheet or not?My personal opinion:
::view-transition-nested-content. This aligns with::details-contentborderand also thebox-sizing– which could be made!importantin the UA stylesheet) and also taps a lot into layered capturing.overflow: clipgo in the UA stylesheet or not? I think so. If we don’t do this then by default nested contents would always overlap the border and also bleed out of the outer::view-transition-group.noamr commentedon Mar 12, 2025
This demo seems like a really good use case for scoped view transitions rather than nested as the transition is constrained to one segment.
I would like to see how necessary this is for cases where it's true nesting rather than scoping, as in the element and its children participate in a root transition.
See also #11927
css-meeting-bot commentedon Apr 23, 2025
The CSS Working Group just discussed
[css-view-transitions-2]: Nested ::view-transition-group should have a container pseudo..The full IRC log of that discussion
<ydaniv> vmpstr: in VTs we have a pseudo struct with group, under image-pair, under old and new<ydaniv> ... in group we added capability to nest groups
<ydaniv> ... we found that you'll be able to clip in VTs that are not all flat
<ydaniv> ... we found it's hard to do if element has borders, you'd clip the group instead of area that you inteded
<ydaniv> ... proposal is to add a VT content or other pseudo elemnt that acts as the parent of all nested groups
<ydaniv> ... so you have an image-pair and another child, say content
<ydaniv> ... would allow you to clip just the group and not the content
<bramus> q+
<ydaniv> ... proposal is to size to padding box which clips the padding box of the originating element that created the group
<astearns> ack bramus
<ydaniv> bramus: wanted to say that I tried this in playground by faking it real elements, and having this extra element makes it much much easier for authors to achieve this clipping
<ydaniv> ... you can see in the playground, if you compare the code of element with hotpink <missed>
<emilio> q+
<ydaniv> ... much more convinient with this extra pseudo
<astearns> ack emilio
<ydaniv> emilio: is that just a border that doesn't clip correctly?
<ydaniv> vmpstr: it would be captured in either old or new that nested under pair that's under group
<ydaniv> astearns: proposed resolution is to add a new VT container?
<ydaniv> vmpstr: happy to change the name, it's technically group container
<ntim> q+
<ydaniv> bramus: with the * that it doesn't include <missed>
<bramus> s/<missed>/the outer group
<ydaniv> flackr: I think the name content is roughly consistent with the box model
<emilio> q+
<astearns> ack ntim
<ydaniv> ntim: one question, are there any alt. solutions to address this use-case?
<ydaniv> ... content is a better name than container cuase here it's a child of the group
<ydaniv> vmpstr: both a child of the group but contains other groups
<ydaniv> ,,, as for q, bramus can answer better, not easily, can have <missed>
<astearns> ack fantasai
<ydaniv> fantasai: we got the VT group, and then for each group it contains a pair and old/new, and in each thing we animate will have its own nested
<ydaniv> ... within an existing groups
<ydaniv> ... now we're saying to separate the image-pair, and now have another grouping element around all nested transitions
<bramus> q+
<ydaniv> vmpstr: yes ....
<ydaniv> fantasai: in many cases a lot of the content will be in the image-pair
<ydaniv> ... so container make a lot of sense
<fantasai> s/a lot of/more/
<ydaniv> ... I think since a lot of content will be in the image-pair, calling it content will be confusing
<ntim> -1 to the specific word combination of `-group-container`
<ydaniv> astearns: wondering if there are no nested VTs, is this new thing going to be empty?
<ydaniv> vmpstr: good question, was thinking of having one that would be empty
<astearns> q?
<astearns> ack emilio
<vmpstr> s/having one that would/not having one that would/
<ydaniv> emilio: this would be the only pseudo that would have special size, or we don't need this special case?
<bramus> scribe+
<ydaniv> vmpstr: I only know of the VT's sizing, and yes, that would be the only case, the others are on border-box
<bramus> bramus: not having a pseudo if there are no nested groups is consistent with not having a new or old pseudo in some cases.
<ydaniv> emilio: sounds unfortunate, maybe we should allow sizing the groups?
<ydaniv> ... if you allow that then we could use them
<astearns> group-wrapper, group-parent…
<ydaniv> vmpstr: problem is if you size them then the border in the old, adding a clip will clip that
<ydaniv> emilio: doesn't that mean that the snapshot will overide the border?
<ydaniv> ... I guess the old and new are things that actually need the border
<ydaniv> ... would be nice to have a simpler use-case for this
<ydaniv> ... maybe fine..., to me seems to want to have a border and not clip
<ydaniv> ... my q, is this case worth adding the extra complexity?
<ntim> +1 to emilio, magic is annoying
<ydaniv> ... when you capture the snapshot old/new elements, the size is not necessarily size of border box, but ink overflow, so you need a size for the pseudo element that represents this
<ntim> `::view-transition-nested-*` is my probably my favorite pick for a name
<ydaniv> ... I assume it's a reasonable default, it's doable but I think it's unfortunate for special case
<ydaniv> vmpstr: it contains the nested elements in it, so what would size it to?
<ydaniv> ... if to border-box then elements at the border ...
<ydaniv> ... would you be ok with content box?
<ydaniv> emilio: groups do that
<ydaniv> ... the things you're snapshotting and the box model of the pseudo tree makes sense
<ydaniv> ... we're not discussing that
<ydaniv> vmpstr: current box model of the pseudo tree makes sense
<ydaniv> ... make the case that shouldn't be sized to border box?
<ydaniv> emilio: yea
<ydaniv> vmpstr: poissble
<ydaniv> ... overall, with nesting as it's specified right now you have the ability to clip your groups it's only possble if you clip yourself
<ydaniv> ... if the objection is only to sizing, to border box
<ydaniv> ... so probably to padding box, which is what I'd recommend, that is my sense
<ydaniv> astearns: I wonder would it make sense to have the box KWargs as params
<ydaniv> emilio: doesn't quite work, because the function selects different elements
<ydaniv> ... I do think that something along those lines that you can somehow specify how things are sized
<astearns> q?
<ydaniv> ... like ink overflow, maybe
<ydaniv> ... to me adding extra container is not big deal but feels like a smell, for a particular case
<ydaniv> ... maybe I'm misunderstanding
<ydaniv> ... feels to special for something that we should address differently
<ydaniv> ... haven't thought about this deep enough
<astearns> ack bramus
<ydaniv> bramus: wanted to address some remarks
<ydaniv> ... I will record what I'm showing in the playground and add comments
<ydaniv> ... why this extra element would make things easier
<ydaniv> ... it's a code smell but help a lot
<ydaniv> emilio: maybe we should allow the capture to be sized to padding rather than border
<ydaniv> ... feels like more general issue, maybe I'm wrong
<ydaniv> vmpstr: the part I'm missing, suppose you take clipping as an example, how else would you clip the group without clipping the content?
<ydaniv> emilio: if the container of the snapshot that sized to the border box, ...
<ydaniv> vmpstr: there's only one clip that clips both, they should have different clippings
<ydaniv> emilio: I guess I need to wrap my head around the tree
<ydaniv> flackr: another harder case is having shadows and decorations that go outside of the border box
<ydaniv> ... and shouldn't be clipped
<ydaniv> vmpstr: have a few diagrams in the issue you can respond to
<ydaniv> emilio: sounds good
<ydaniv> astearns: we'll take this back to the issue
<ydaniv> ... I suppose talk about the possiblity of changing clipping of everything else?
<ydaniv> vmpstr: yes and no (:
vmpstr commentedon Apr 29, 2025
@emilio let me know if the diagrams in the OP are sufficient to explain the situation.
We basically have a situation
(
Ais a parent ofBCandD).Aneeds to clipCandDbut not clipB, becauseBcontains the visual pixels ofAincluding border etc.The proposal is to add another container element E for this:
emilio commentedon May 5, 2025
Yeah, so I think you're right that you need some grouping pseudo to get the clipping you want. I think what I'm concerned about is the "sizing to padding-box" special-ness.
@vmpstr and I quickly chatted, and I think that the padding-box behavior:
Is not a huge improvement from using the border-box and doing:
So I think I'd prefer to add the pseudo-element with the border-box sizing so it's more consistent with all the already-existing grouping and v-t pseudo-elements.
vmpstr commentedon May 5, 2025
Proposing to add a new pseudo called
::view-transition-nested-groups(...)(thanks to @nt1m for the view-transition-nested-* suggestion; although "groups" is plural, so I'm not sure if we need to bikeshed more) that is a child of::view-transition-group(next sibling of `::view-transition-image-pair), that is created only if there are nested view transition groups and is sized to the border box of the element (same size as the parent group)bramus commentedon May 6, 2025
Would this
clip-pathbe auto-generated by the UA?If not, then I don’t think we’d ship a good default, as you’d get this behavior out of the box (recording of this demo in Chrome Canary with flags; pay close attention to the top and bottom borders of the wrapping element while the VT runs):
Kapture.2025-05-06.at.11.53.28.mp4
If it does get generated by the UA then I think I’m fine with it.
emilio commentedon May 6, 2025
If we're clipping by default, then sure, but I think I'd rather not? Don't you have the same issue with the other alternative, as you'd need to manually specify the "inner" border radius yourself (even if we
overflow: clip'd by default)?bramus commentedon May 6, 2025
By sizing the
::view-transition-nested-groupspseudo to the padding-box, then at least the base use-case of “the wrapper has borders” would be covered.When doing so, you’d get the following behavior out-of-the-box (assuming that the
::view-transition-nested-groupspseudo hasoverflow: clipapplied, as part of the UA-generated style sheet):Kapture.2025-05-06.at.14.26.24.mp4
(Recording of this demo in Chrome Canary with flags, it includes a manual (and convoluted) workaround/fix for the border issue)
The above seems like a better default behavior to ship.
It’s only when there are other things present that alter the shape of what gets rendered – such as a
border-radiusor acorner-shape– that authors need to replicate things that make sure the::view-transition-nested-groupsalso gets clipped in the same manner.For
border-radiuswe could easily generate this ourselves but for things likecorner-shapeI guess it would be more complicated. Admittedly this also taps a bit into “Layered Capture Mode”, which turned out to be very complicated – ask @noamr for details.16 remaining items
nt1m commentedon May 22, 2025
Just copying the border-width does sound a bit strange, and potentially a bit magical. I'd consider this a bit of an all or nothing situation. Maybe consider copying all the border-related things over: border-radius/border/border-image/corner-shape?
Otherwise, from the authoring side, it feels like the browser is trying to help you but hasn't really finished its job.
bramus commentedon May 22, 2025
This is a tradeoff that is being made. Copying the border is sufficient to make sure that if the original enclosing element has any border decorations, authors can copy them over without needing to do any adjusting of the values. E.g. they can copy over a
border-radiuswithout needing to adjust the value to being the inner border-radius.This aligns the behavior of nested view transition groups with that of not using view transition groups: box decorations – if any – need to be copied over if you want to them actually do anything. When copying them over, one can take the existing values without any adjusting needed.
vmpstr commentedon May 23, 2025
I guess the point here is that
border-widthalone won't change anything, it's more likeborder-width,border-styleandborder-color: transparent. I don't know aboutborder-radiusor shape and whether that is also valuable.The problem of copying borders wholesale is that the pixels of the border are already captured as old content, so as those crossfade to anything new, it will look wrong in a lot of cases. If the
border-coloristransparentthough, then we're really just preserving the clipping area which is what we're trying to accomplishcss-meeting-bot commentedon May 28, 2025
The CSS Working Group just discussed
[css-view-transitions-2]: Nested ::view-transition-group should have a container pseudo., and agreed to the following:ACTION: Clarify L1 to encourage styling ::group() and not ::image-pair()RESOLVED: add ::view-transition-group-children() (open to better names), and copy border-sizing properties to itThe full IRC log of that discussion
<emilio> vmpstr: we discussed this in the context of nested v-t groups<emilio> ... to add a container to the nested groups
<emilio> ... with the purposes of clipping the nested groups
<emilio> ... bunch of discussions on how to accomplish that
<emilio> ... for the purposes of unblocking I'd like to split some resolutions
<emilio> ... so (1) we need a new element, (2) naming and (3) sizing
<emilio> ... proposal is to size it so that if we apply a clip you make the clip apply as in the original element
<emilio> ... so size it to border-box and set border-color: transparent and copy border* properties (border-width/border-box/overflow-clip-margin)
<emilio> ... that way if the author decides to apply a clip it'd clip the same way as the original element
<emilio> ... noamr had a different proposal, to take the original element's clipping-region, and turn it into a mask, and apply that mask on top
<emilio> ... that may work in some cases better than this copying, but is more complicated
<emilio> ... so I'd prefer to resolve on copying the border props
<emilio> ... and then open an issue to the mask
<flackr> q+
<emilio> ... I'd like some resolutions to get some early version of this into developers
<astearns> ack fantasai
<Zakim> fantasai, you wanted to ask some stupid questions because she forgot the answers
<emilio> ... none of the solutions are ideal
<emilio> fantasai: so what we have right now is group() and image-pair()
<emilio> ... and we need another?
<emilio> ... why do we need a separate group() and image-pair()?
<emilio> vmpstr: we want to nest things from other elements within this group
<emilio> ... naively those would be siblings of the original group
<emilio> ... which you wouldn't clip the children
<emilio> ... so that it wouldn't affect the clipping of the image-pair
<emilio> fantasai: remind me why we added the image-pair()
<emilio> vmpstr: for isolation, we blend old+new with plus-lighter
<emilio> fantasai: so generally we don't recommend authors to style the image-pair()
<emilio> ... just pretend it doesn't exist?
<emilio> vmpstr: that is the case in my experience
<emilio> fantasai: wanna make clear in the spec that authors should style group()
<emilio> ... so if the author creates a transition between old/new versions of the grouping element, where the border is transitioning as step() rather than smooth, would this copied version of the border track that?
<emilio> ... or would it end up unsynchronized?
<emilio> vmpstr: so you mean the transition step() is the new/old() ones?
<emilio> ... I don't think it'd be synchronize
<emilio> ... in the simple case old/new they cross-fade to the other, so it doesn't animate borders, it blends the pixels
<emilio> ... you can't cross-fade clips
<emilio> ... with the mask approach you could cross-fade the masks I guess
<emilio> q+
<astearns> ack flackr
<emilio> ... but even in the default case it might not be great
<emilio> flackr: I'm supportive of the idea
<emilio> ... if we don't need to customize this we might want this to be internal in order to change the mechanics?
<emilio> vmpstr: I think that's very magical
<emilio> ... something's clipping this but there's nothing there
<emilio> ... it's maybe an option
<astearns> ack emilio
<emilio> ... maybe too magic for my taste
<flackr> emilio: Good idea to split resolutions. I thought default was not clipping.
<fantasai> emilio: Seems to me splittng resolution, allow new pseudo
<fantasai> emilio: naming, I don't care very much
<fantasai> emilio: specifics of sizing and stuff is the tricky thng
<fantasai> emilio: I'd rather keep it simple and let authors use clip-path etc.
<fantasai> emilio: especially if default case won't be great
<fantasai> emilio: If you need to clip with rounded rect with border, you could use clip-path or something else
<flackr> q+
<fantasai> emilio: should also work in the negative direction
<fantasai> emilio: seems like at least the first resolution would be uncontroversial, and would help make progress
<emilio> vmpstr: unfortunately bramus not on the call
<emilio> ... happy to resolve on adding pseudo+name for now then see if we can resolve further
<astearns> ack flackr
<emilio> flackr: Please resolve on adding the pseudo, but we need the border on it because the position of the descendants depends on having a border
<emilio> ... if developers added that border it'd shift all the descendants so we need the border size as part of the copied props
<vmpstr> +1
<emilio> q+
<astearns> ack emilio
<fantasai> emilio: would it though?
<fantasai> emilio: The groups are absposes, right?
<fantasai> emilio: and I guess the idea is that the group would always be the containing block of these? but aren't we using transform to position and size these?
<fantasai> flackr: yes but it's relative to the [missed]
<vmpstr> s/[missed]/content
<fantasai> emilio: Isn't that how all other view transition pseudos work?
<fantasai> emilio: Doesn't it mess things up if you add a border to ::view-transition-group today?
<fantasai> flackr: Yes, but you would add this border to replicate the original clip, but by adding border you would screw up the positions of the elements
<fantasai> vmpstr: You wouldn't add the borders on the group element itself, because captured the pixels that overflow the border box
<fantasai> vmpstr: not a typical use case
<fantasai> emilio: right, but you might not need a border
<fantasai> emilio: but ok, I guess, do other things affect padding vs border box?
<fantasai> emilio: does border-image do something weird?
<fantasai> noamr: no, only border-width really
<fantasai> vmpstr: I'd like to leave this a little open, as in anything that does affect it
<fantasai> vmpstr: if other things affect, we can add them
<fantasai> emilio: I guess it's ok? It feels weird to account for existing borders in this pseudo.
<fantasai> emilio: idea would be to change how positioning of inner things work, to account for the border, right?
<fantasai> emilio: but maybe we can fix it more generally, so that if you add a border to any of these pseudo-elements...?
<fantasai> emilio: I guess it's hard to know beforehand
<fantasai> emilio: The inconsistency with all the other grouping pseudoes bothers me
<fantasai> emilio: also copying the border-width is not too terrible
<fantasai> emilio: I guess you also need to set border-style...
<emilio> vmpstr: we can possibly copy border-{width,style}, set border-color to transparent
<fantasai> vmpstr: Yes, we can possibly copy border-width and border-style, and set border-color to transpraent if it was set to something on the original element
<emilio> vmpstr: border-width might not have an effect depending on the rest of the styles
<emilio> ... details tbd but the idea would be to keep the border area without drawing the border
<emilio> astearns: think I'm hearing consensus of adding the pseudo and maybe giving a name
<emilio> vmpstr: would like to resolve on copying something details tbd
<emilio> ... content-area-affecting properties like border-width, maybe border-shape-affecting props like border-radius
<emilio> astearns: proposed name?
<emilio> vmpstr: `view-transition-nested-groups()`? `view-transition-nested-content()`?
<emilio> ... no particularly strong naming
<flackr> +1 to content
<fantasai> view-transition-group-children?
<fantasai> emilio: content sounds better for some reasons, but you also want the padding area. nested-content seems fine. fantasai's suggestion also seems fine
<emilio> fantasai: I think none are particularly great
<emilio> ... I don't like nested content because a lot of the content is going to be captured outside
<noamr> view-transition-content-area?
<emilio> vmpstr: view-transition-group-children or content-area?
<emilio> fantasai: children seems slightly better because it distinguishes the image-pair
<emilio> ... content-area is weird because you want the padding
<emilio> vmpstr: let's not reference specific boxes, and do view-transition-group-children?
<emilio> PROPOSED: add ::view-transition-group-children() (open to better names), and copy border-sizing properties to it
<emilio> fantasai: want to propose extending the spec with the suggestion to style ::group() and not ::image-pair
<fantasai> ACTION: Clarify L1 to encourage styling ::group() and not ::image-pair()
<emilio> RESOLVED: add ::view-transition-group-children() (open to better names), and copy border-sizing properties to it
<fantasai> s/extending/clarifying
nested VT: Introduce ::view-transition-group-children
nested VT: Introduce ::view-transition-group-children
nested VT: Introduce ::view-transition-group-children
Bug 1966983 [wpt PR 52604] - nested VT: Introduce ::view-transition-g…
Bug 1966983 [wpt PR 52604] - nested VT: Introduce ::view-transition-g…
[css-view-transitions-2] Define the view-transition-group-children ps…
[css-view-transitions-2] Define the view-transition-group-children ps…