w3c / csswg-drafts

CSS Working Group Editor Drafts
https://drafts.csswg.org/
Other
4.43k stars 656 forks source link

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

Open noamr opened 1 month ago

noamr commented 1 month ago

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:

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).

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

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

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 commented 1 month ago

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 commented 1 month ago

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

Perhaps view-transition-style: crossfade | morph ?

To discuss/resolve:

noamr commented 3 weeks ago

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:

auto could mean:

Psychpsyo commented 3 weeks ago

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 commented 3 weeks ago

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 commented 3 weeks ago

(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 commented 3 weeks ago

(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 commented 3 weeks ago

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 commented 3 weeks ago

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:

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