Skip to content
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

[css-view-transitions-2] Optionally capture some properties (e.g. opacity/border) as style instead of snapshot #10585

Open
noamr opened this issue Jul 17, 2024 · 10 comments
Labels
css-view-transitions-2 View Transitions; New feature requests Needs Edits

Comments

@noamr
Copy link
Collaborator

noamr commented Jul 17, 2024

The current way we capture view-transition participating elements is tuned to a flat tree of crossfade+transform animations: we save the element's relevant properties (transform from root, mix-blend-mode, color-scheme) and everything else is baked into the snapshot.

However, #10334 (nested transition groups) makes it so that this pattern might need to be expanded: to display clipping and tree effects in an expected manner, those would have to be copied into the group rather than baked into the snapshot.

The following (non-exhaustive?) list of CSS features would have a different behavior in a nested transition tree:

  • tree effects (opacity, filter, image-mask)
  • clipping (clip-path, overflow, border-radius)
  • 3D (actual transform, preserve-3d, perspective, transform-origin)

The "odd" one out of these is border-radius together with overflow: since it has a unique effect of clipping its descendant but not clipping the border.

To render a rounded-corner element with nested clipped descendants correctly in terms of paint-order, the entire set of box decorations (backgrounds & borders) would have to be captured and applied to the group.

Also as per the resolution in #8282, we sometimes capture geometry only (when the old element is out of the viewport).

  • TBD whether this needs its own bespoke thing or fits in with the capture modes concept.

So at the very least there could be 4 capture modes (each one includes the ones above it):

  • geometry
  • flat (geometry + snapshot + blend)
  • composite (clipping + 3D + tree effects)
  • layered (borders + background + shadow)
    (names bikesheddable to the bone)

Superficially, it would seem like this can be done automatically:

  • Use geometry when out of viewport
  • Use flat when in viewport
  • Use composite when has nested descendants
  • Use layered when has nested descendants and clipping border-radius

However, the issue with these capture modes is that by default they're incompatible with each other - so if we used one capture mode in the old state and one in the new state, the animation and the captured image would be out of sync, creating a buggy-looking animation like this: https://codepen.io/noamr-the-selector/pen/OJeymbe.

One path forward is to enable the author to define the capture mode (based on the above proposal or some other subdivision we decide on), and encourage authors to use this when using nested transitions, but not choose a capture mode automatically (except for geometry when out of screen).

@noamr noamr added the css-view-transitions-2 View Transitions; New feature requests label Jul 17, 2024
@noamr
Copy link
Collaborator Author

noamr commented Jul 18, 2024

A syntax alternative can be view-transition-style: motion || crossfade || composite || decoration || default where default means "motion when out of viewport, motion+crossfade when in-viewport"

@noamr
Copy link
Collaborator Author

noamr commented Jul 30, 2024

Notes from internal conversation: for starts, having two modes is enough:

  • The current mode, where elements are captured for a crossfade
  • The "max" mode: capturing the borders/background/clip/effects/3d as style and morphing them individually rather than as part of the image

Perhaps view-transition-style: crossfade | morph ?

To discuss/resolve:

  • Should this be configurable separately per-element or once for the whole view-transition? The former feels more flexible perhaps?
  • What do we do in case of mismatch?
@noamr noamr changed the title [css-view-transitions-2] Capture modes Aug 19, 2024
@noamr
Copy link
Collaborator Author

noamr commented Aug 19, 2024

Copying from #9406 for the purpose of the CSSWG breakout.

There are 3 use-cases that can benefit from changing the way we capture in certain scenarios:

  1. Groups that have nested descendant with view-transition-group: they need to capture opacity, filters, 3D, and sometimes borders, backgrounds & shadows in order to display in a way that doesn't look buggy.
  2. Sometimes, regardless of nesting, animating border and box-shadow can have a nicer effect than flattening them into the snapshot.
  3. When an element comes from far away from the viewport, or if we're moving elements around without changing their content, we could benefit from an optimization of not capturing the old content into a snapshot at all.

I see this as 3 styles of transition, with an auto that generates default behavior:

  • Crossfade between the old and new element snapshots, like today (crossfade?)
  • Crossfade the contents and animate the box decorations individually (morph?)
  • Display the new content only and morph only the box decorations (transpose?)

auto could mean:

  • Use morph when the group contains other groups
  • Use transpose when animating from very far from the viewport (100vw, 100vh?)
  • Otherwise, use crossfade
@Psychpsyo
Copy link
Contributor

Psychpsyo commented Aug 19, 2024

auto could mean:

* Use `morph` when the group contains other groups

* Use `shapeshift` when animating from very far from the viewport (100vw, 100vh?)

* Otherwise, use `crossfade`

Is there a reason for auto not using morph automatically if two elements with differing 3D transforms (or similar) are being transitioned between?
Because like this, transitioning one 3D transformed element to a differently 3D transformed element just crossfades between the two and it looks rather janky.

@noamr
Copy link
Collaborator Author

noamr commented Aug 19, 2024

auto could mean:

* Use `morph` when the group contains other groups

* Use `shapeshift` when animating from very far from the viewport (100vw, 100vh?)

* Otherwise, use `crossfade`

Is there a reason for auto not using morph automatically if two elements with differing 3D transforms (or similar) are being transitioned between? Because like this, transitioning one 3D transformed element to a differently 3D transformed element just crossfades between the two and it looks rather janky.

If an element is flat (doesn't have nested descendants), there shouldn't be a difference regarding the internal 3D transform (e.g. perspective), as the image is flattened anyway. The external transform is part of transitioning the geometry in all the styles.

@bramus
Copy link
Contributor

bramus commented Aug 20, 2024

(Got my bikeshedding hat on …)

Perhaps view-transition-style: crossfade | morph ?

The conversation here uses “capture mode“ as a term a lot, so maybe view-transition-capture-mode seems better?

  • Crossfade between the old and new element snapshots, like today (crossfade?)

crossfade as a value feels weird here because authors can override the animation to something entirely different. IUC this is the current behavior where you get a flattened surface, so maybe flat as a value is better here?

  • Crossfade the contents and animate the box decorations individually (morph?)

layered, maybe?

  • Display the new content only and morph only the box decorations (transpose?)

This can already be controlled by authors, no?

::view-transition-old(x) { display: none; }
::view-transition-new(x) { animation-name: none; }

Most likely I’m missing something here … 🤔


All combined, I’m leaning to a view-transition-capture-mode: flat | layered | auto syntax.

@noamr
Copy link
Collaborator Author

noamr commented Aug 20, 2024

(Got my bikeshedding hat on …)

Perhaps view-transition-style: crossfade | morph ?

The conversation here uses “capture mode“ as a term a lot, so maybe view-transition-capture-mode seems better?

  • Crossfade between the old and new element snapshots, like today (crossfade?)

crossfade as a value feels weird here because authors can override the animation to something entirely different. IUC this is the current behavior where you get a flattened surface, so maybe flat as a value is better here?

Yea, it's whether this property implies "what default transition would this generate" whether "what does it capture".
I thought that referring to the default transition style is more design-oriented and referring to the capture is more technical, but perhaps more precise. This is definitely arguable!

  • Display the new content only and morph only the box decorations (transpose?)

This can already be controlled by authors, no?

::view-transition-old(x) { display: none; }
::view-transition-new(x) { animation-name: none; }

Yea, but doing this in advance is an optimization, see #9406. We can also decide to not fold this optimization in the solution for this issue.

@noamr
Copy link
Collaborator Author

noamr commented Aug 20, 2024

Summary from internal sync, we concluded that we'd defer the no-content-change to later.
There are 3 options on the table:

  1. Change the capture mode by default. Capture box-decorations and tree effects as style, and cross-fade only the contents. This asserts that morphing rather than cross-fading is mostly superior. However, it might make some of today's view transition animate differently, e.g. a different background image between the old and new state would animate discretely.
  2. Make this an opt-in with a new property (view-transition-style or view-transition-capture-mode), one option is like today, and the other one captures box-decorations & tree effects as style.
  3. Add a new property, but give it an auto value. The capture-mode will be the same as today, unless the element is a containing group (has nested group descendants). This asserts that flat capturing doesn't make sense at all for a nested group tree, and it allows us to change that without breaking existing content.

Note that there are current cases where cross-fading the whole image would be smoother than animating the backgrounds or clip-paths, see #10759 and #10760. However, this is also achievable by adding a container element, where the external container would have the view-transition-name, and the internal container would have the backgrounds etc.

@css-meeting-bot
Copy link
Member

css-meeting-bot commented Aug 21, 2024

The CSS Working Group just discussed [css-view-transitions-2] Optionally capture some properties (e.g. opacity/border) as style instead of snapshot, and agreed to the following:

  • RESOLVED: Change the capture mode for all view-transitions and specify how each property is affected by this capture mode change
  • RESOLVED: Blink will experiment and come back with changes needed if there are compat concerns
  • RESOLVED: eventually describe categorization of properties in the Module Interactions sections of each spec
The full IRC log of that discussion <bramus> noamr: biggest issue we want to discuss today
<bramus> … how we capture and display nested componets
<bramus> … but also applies to non-nested vt elements
<bramus> … derived frm the nested conversation
<bramus> … when we nest groups, some css properties that were previously not that important to capture are now very important because otherwise it looks broken
<bramus> … two groups
<bramus> … - tree effects like opacity, mask, clip-path, filters, perspective
<bramus> … these apply to entire tree
<bramus> … - borders and border-radius because once you have a hierarchy of groups and you have overflow then the overflow affects the origin where you draw the borders and shadows
<bramus> … these also paint after backgrounds
<bramus> … so it comes down to when doing just default capture, nesting is visually broken
<bramus> … but this also was something we discussed when vt started
<bramus> … that animating ?? directly looks better, e.g border and border-radius
<bramus> … than just capturing it as one image and cross-fading it
<bramus> … otoh we already shipped the old thing, so its a compatibility issue
<bramus> … we see three options
<bramus> … 1. change everything by default and dont just capture snapshot but add more things that get captured as ?? instead of a flat snapshot (opacity, filter, transform, bg borders, )
<bramus> … will change things because these styles are part of the group
<bramus> … but have changed things before (but this is different as it changes observable computed style)
<bramus> … 2. add new property `v-t-style` or `v-t-capture-mode`
<bramus> … fane of the first as it reminds me of `transform-style`
<bramus> … 3. to have this new property but give it auto value
<bramus> … if group contains other groups when you get the new mode
<bramus> … so users using nesting get the new mode
<bramus> … but can have a property to change the behavior
<khush> q?
<khush> q+
<astearns> q+
<bramus> … if people want the old crossfade behavior theycan always do so by regular DOM nesting
<astearns> ack khush
<bramus> khush: hoping we can split this in 2 resolutions
<bramus> … 1 on the new capture mode
<bramus> … and 2 to change the default or not
<bramus> q+
<bramus> astearns: confused about third option (adding current capture for non-nested but auto new nested for grouping without giving opt out)
<bramus> noamr: there is an opt out
<bramus> astearns: not entirely sure about backwards compat concern since we ar estill working on this
<bramus> … i’d have to have somebody look at the data
<bramus> … changing mode for non-grouped things seems OK if its better
<astearns> ack astearns
<astearns> ack bramus
<flackr> +1
<fantasai> scribe+
<fantasai> bramus: Yes, this would be breaking, but it would break in a good way
<fantasai> bramus: Regarding the name of the property, one of the values proposed is cross-fade, which is a value I wouldn't recommend
<fantasai> bramus: because authors can change the animation, e.g. to scale-up/scale-down, etc.
<noamr> q+
<fantasai> bramus: I would suggest a different name for the property, view-transition-capture-mode: flat | layered
<astearns> ack noamr
<bramus> noamr: fine with any type of bikeshedding
<bramus> … if we choose option 1 we dont need new property
<bramus> astearns: yet
<bramus> noamr: propably wouldnt need one
<bramus> … if we change default then optoin of using crossfade is always possible with nesting DOM
<khush> q+
<bramus> … defers the conversation about naming it
<astearns> ack khush
<bramus> khush: my vote is for option 1, is risky but can try it with a flag
<ntim> q+
<bramus> … one instance i have seen where it could break things, and partner let us know that they would welcome the change
<astearns> ack ntim
<bramus> ntim: is this changing by default for all capture modes or just nested ones?
<bramus> … i mean, nested or everything?
<bramus> noamr: option 1 is everything, option 3 only for groups
<bramus> … option 1 would be “breaking change” / modification to view transitions 1
<bramus> astearns: and option 3 would not be
<bramus> noamr: i proposed that one as i am very skiddish about backwards compat
<vmpstr> q+
<bramus> vmpstr: also supportive of option 1
<bramus> … future looking this mode seems to be strictly better in a lot of cases (e.g. border radius that animate instead of cross-fade)
<bramus> … one concern is breakages and specifically if Apple is also on board with making this change then we want to do this
<bramus> … want to avoid interop problem where blink switches modes and webkit doesnt
<bramus> … hard to feature detect
<bramus> … and sites would have to deal with two different modes
<bramus> … that is the biggest risk I see
<bramus> astearns: tim, do you have preference?
<bramus> ntim: dont know how fast if we can adopt this
<bramus> … guess if there is a compat concern it maybe is possible?
<bramus> … cant give definitive answer
<bramus> fantasai: we can reasonable say that if this is the direction to go we should … but cant commit to a timescale though
<bramus> noamr: there is some sentiment to 1 but I feel ppl need to think about this more?
<bramus> astearns: could resolve on option 1 and have blink try it out to see how much breakage there is
<bramus> … and if its manageable then we’re good and come back to this
<bramus> … would be resolving one 1 unless it’s not possible
<bramus> … i’d rather not define a new capture mode without a switch
<khush> q+
<vmpstr> q-
<bramus> ntim: do you know how much work this will be for you?
<bramus> noamr: I am prototyping it right now, can report back
<bramus> … doesnt seem like huge amount of work
<bramus> … mainly adding more things to list of captured props and not painting them when element is being captured
<bramus> ntim: OK
<astearns> ac khush
<astearns> ack khush
<bramus> khush: when we prototype we]ll find edge cases
<bramus> … we will take those back to the WG in that case
<bramus> … want to get this right
<bramus> noamr: it involves a lot of CSS props
<bramus> … some of them are captured and not painted, while others are painted
<flackr> q+
<bramus> … the ones specifically would all be specified
<astearns> ack flackr
<bramus> flackr: is it that we need a specific property list or is it that your children are captured as a painting and only props that affect the child dont matter instead of the box that is captured?
<bramus> noamr: depends on how handwavey we want to be
<bramus> … we would need WPTs
<bramus> flackr: just want to avoid situation where it is hard to guess
<vmpstr> q+
<bramus> noamr: there could be general wording plus exceptions
<bramus> astearns: as goes for specifying it should be about its characteristics so that it extends to new future properties
<astearns> ack vmpstr
<bramus> noamr: agreed
<bramus> vmpstr: agree as well
<bramus> … dont want to maintain a list of props for all eternity
<bramus> noamr: could we add it to a property descriptor?
<ntim> q+
<bramus> astearns: we have a lot of things in description tables … if we can avoid addig a field there I would prefer that
<astearns> ack ntim
<bramus> noamr: no strong opinion
<khush> q+
<bramus> ntim: from impl POV it would be good to have list well defined in some way. new field in prop table or whatever workds
<bramus> … to make sure impls dont miss things
<bramus> astearns: for most part weshould be able to define groups of props with some exceptions and then encode things in WPTs
<bramus> … having a list of props in the WPT seems better than in the spec
<astearns> ack khush
<bramus> khush: gonna second what ntim said
<vmpstr> q+
<bramus> … right now spec has UA stylesheet that is very well defined so there is no interop issue there
<bramus> … when having a property descriptor in the prop table makes it easier for interop too
<bramus> … e.g. animatable
<astearns> ack vmpstr
<bramus> … you dont miss it
<ntim> I'm also OK with the list being maintained into a WPT, it just needs to be maintained somewhere
<noamr> q+
<bramus> vmpstr: middle ground could be to define groups of props such as “clipping properties”
<bramus> … and props should add themselves to those groups
<astearns> ack noamr
<bramus> … that might be a nice middle ground, but that’s only a guess right now
<bramus> noamr: can have a default per spec
<bramus> … eg box shadow spec to include sth that says all the props work in one way or the other
<bramus> astearns: elika, do you have an idea?
<bramus> fantasai: like the idea of doing it per spec. will simplify thinking about it
<fantasai> https://www.w3.org/TR/css-backgrounds-3/#placement
<bramus> … other option to put it in propdef table
<bramus> … we current have a ?? so we could have a statement in one of those sections
<astearns> s/??/module interaction sections/
<bramus> s/??/some boilerplate text in module interactions
<bramus> … how x interacts with other modules
<bramus> … or “this also applies to first letter”
<bramus> … can add another paragraph for VTs
<bramus> astearns: how about we specify this in VTs with an explicit list to begin with and a note saying that this info needs to move to individual modules and module interaction section?
<bramus> fantasai: and if vt editors want to work on some PRs to update specs that need that section, that would be welcome in a single PR
<bramus> noamr: OK
<khush> q+
<bramus> fantasai: some specs are missind that section though, so youll need to add it
<bramus> khush: so is the conclusion we have an explicit list but it is define by each module?
<bramus> … it describes which group it belongs to, and then vt handles the group behvior
<astearns> ack khush
<khush> +1
<vmpstr> +1
<bramus> fantasai: yes, but at first you can have an explicity list in the vt spec and then later on group them
<fantasai> once you feel confident in your groupings/descriptions
<bramus> astearns: so propsed resolution is to change capture mode for all view-transitions and specify how each property is affected by this capture mode change
<bramus> … does that sound right?
<bramus> … astearns any concerns?
<bramus> bramus: should we also add that blink will experiemnt and come back with compat concerns?
<bramus> astearns: sounds good
<bramus> RESOLVED: Change the capture mode for all view-transitions and specify how each property is affected by this capture mode change
<bramus> RESOLVED: Blink will experiment and come back with changes needed if there are compat concerns
<fantasai> RESOLVED: describe categorization of properties in the Module Interactions sections of each spec
@noamr
Copy link
Collaborator Author

noamr commented Sep 19, 2024

As part of experimentation, we've identified which properties need to participate in this and how, and also the potential compatibility-breaking parts (though it's not shipped yet, so we don't know if the compatibility breakage happens in practice).

Categorization

We divide CSS properties into the following categories:

Copied

Properties that should be captured, and also be rendered into the snapshot

  • overflow
  • overflow-clip-margin
  • contain (potentially just convert contain: paint into overflow: clip)
  • transform-style
  • perspective
    (the last two actually don't have any effect on a flattened snapshot)

Delegated

Properties that should be captured and delegated to the group, and the snapshot should be rendered without them (note that some of these are shorthands):

  • background
  • border
  • border-image
  • border-radius
  • box-shadow
  • clip
  • clip-path
  • filter
  • mask
  • opacity

Notes

  • The transform of the element should be copied in 3D space rather than flattened, to preserve the 3D effect of the origin.
  • Applying overflow to the snapshot is a design choice, to avoid capturing very big overflowing content. This means that the author cannot use the pseudo-elements to animate scrolling an overflow: scroll DIV. However, the overflow is captured without the border-radius, which is applied separately as CSS and can be animated, as well as clip-path and clip.
  • If we don't render the border, this affects the border box of the pseudo-elements, and its relationship with nested groups, as the border-box is what's used to determine the pseudo element's position. Suggesting that this change means that we need to project the padding box instead.

Backwards compatibility

The main way this change affect existing pages is likely to be the fact that the ::view-transition-group pseudo-element would now have keyframes that animate many things, which would override styles put directly on the group pseudo. One way to mitigate this can be to add a pseudo element (::view-transition-effect?) between the group and the image-pair that animates these, and keep the pseudo animating only geometry.

This can anyway be good ergonomically as authors can customize one without breaking the other.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
css-view-transitions-2 View Transitions; New feature requests Needs Edits
5 participants