w3c / csswg-drafts

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

[css-view-transitions-2] Optimize no content change animations #9406

Open khushalsagar opened 11 months ago

khushalsagar commented 11 months ago

There are a couple of patterns where an element's position from the old state needs to be retained and animated but not its content. For example:

  1. The user clicks a playing video which then animates to a different spot. Details here.
  2. There is a list of items where the list is re-sorted.

The cross-fade animation for these cases is an unnecessary perf hit: both because we captured pixels which weren't needed and then ran an animation using them. In case 1, the developer explicitly adds CSS to not display the old pixels. But the browser still has to do the work to capture and retain them until the transition ends.

A better way to do this would be a native API for the author to explicitly tell the browser that the content doesn't need to be animated. So instead of doing a cross-fade, just show the new content directly. Perhaps a new CSS property or addition to the existing view-transition-name like view-transition-name: target no-content-animation. If the developer says "no-content-animation", everything else works the same except no old image is captured. The old element contents are still removed from its parent snapshot. The pseudo-DOM won't generate a ::view-transition-old and there will be no fade-in animation on ::view-transition-new but rest of the UA CSS is the same.

A few questions right now:

argyleink commented 11 months ago

here's a demo that would benefit from the optimization https://codepen.io/argyleink/pen/VwBKjwj, there's multiple instances where the browser is capturing more than is needed.

here's an example that's similar, but a little different https://codepen.io/argyleink/pen/PoxgOZz. there's a decent amount of javascript to manage whether or not to give a number a view-transition-name, since I wanted it to "do nothing" if it was the same number between old and new. in this case, i interject and hand manage the VT name to get the effect I wanted, but it also happens to help the browser capture less.

both scenarios, I'd love a property that let me articulate these cases.

bramus commented 11 months ago

Big +1 to not capturing/animating unnecessarily.

Perhaps a new CSS property or addition to the existing view-transition-name like view-transition-name: target no-content-animation

Tacking that onto view-transition-name seems weird. Best to have a different property to indicate what aspects needs to be captured.

Something like this (🚲🛖):

view-transition-capture: all | position-only;

The pseudo-DOM won't generate a ::view-transition-old and there will be no fade-in animation on ::view-transition-new but rest of the UA CSS is the same.

I agree that a ::view-transition-old makes no sense, but adding only a ::view-transition-new can cause conflicts with authors relying ::view-transition-new(x):only-child. Because that selector matches in this case, it will result in a wrong animation. Simply ignoring all properties defined by the author in this situation seems counterintuitive.

What is none of those two pseudos was generated at all? The snapshotted view would still need to be inserted into the pseudo tree (to have something visual on the screen), but since its not matched by any of the existing pseudos authors can not target it.

khushalsagar commented 11 months ago

What is none of those two pseudos was generated at all? The snapshotted view would still need to be inserted into the pseudo tree (to have something visual on the screen), but since its not matched by any of the existing pseudos authors can not target it.

This becomes very hard to define because we need a replaced element to show the snapshot. That said, the ambiguity with :only-child is a fair point. Documenting @flackr's idea about this from today's discussion.

We could generate both the old/new pseudos with the following aspects:

bcole808 commented 11 months ago

+1 to optimizing the performance when there is no content change. I tested View Transitions in Chrome with a list of about 800 elements and found the performance to slow down a bit when starting transitions.

Here's a fork of the Isotype style animation, with about 600 elements, to illustrate an extremely large number of elements being transitioned: https://codepen.io/bcole808/pen/VwqGoZm In this example, what I'm observing is that when clicking on one of the filters, there is a very long pause after the the pointer input occurs until the animation actually starts. Once the animation starts running, playback is pretty smooth. But the long pause before the animation starts makes it seem like there is a lot of work being done before it starts.

Just showing the newly rendered element and applying a transition to the positioning (without a crossfade) is a common use case. So having a CSS property where developers can specify if there should be a crossfade from the old element to the new element sounds like a good idea.

I agree that just tacking it onto view-transition-name seems weird (or perhaps there could be multiple CSS properties, and then there could be some shorthand way to specify all the values on one property).

noamr commented 1 month ago

I wonder if we can fold this into #10585. The same way we have a cross-fade or morph view-transition-style, we can have a motion view-transition-style which doesn't capture nor display the old element content.

khushalsagar commented 1 month ago

^ yea, view-transition-style could be a good spot to express this. Would the syntax allow combining with both cross-fade and morph? Maybe the content is the same but the shape of the box changes?

Psychpsyo commented 1 month ago

^ yea, view-transition-style could be a good spot to express this. Would the syntax allow combining with both cross-fade and morph? Maybe the content is the same but the shape of the box changes?

It probably should since you would want to be able to mark either type of transition as optimized. Maybe through a keyword like static or so, similar to the safe keyword for flex alignments. That'd make static crossfade and static morph, but I think, since crossfade is the default it might make sense to also allow static on its own, as a shorthand for static crossfade.

Like so: view-transition-style: crossfade - crossfade transition view-transition-style: morph - morph transition view-transition-style: static - optimized crossfade transition view-transition-style: static crossfade - optimized crossfade transition view-transition-style: static morph - optimized morph transition

noamr commented 4 weeks ago

^ yea, view-transition-style could be a good spot to express this. Would the syntax allow combining with both cross-fade and morph? Maybe the content is the same but the shape of the box changes?

It probably should since you would want to be able to mark either type of transition as optimized. Maybe through a keyword like static or so, similar to the safe keyword for flex alignments. That'd make static crossfade and static morph, but I think, since crossfade is the default it might make sense to also allow static on its own, as a shorthand for static crossfade.

Like so: view-transition-style: crossfade - crossfade transition view-transition-style: morph - morph transition view-transition-style: static - optimized crossfade transition view-transition-style: static crossfade - optimized crossfade transition view-transition-style: static morph - optimized morph transition

Hmm this makes me think that perhaps this is indeed a separate feature, since you should be able to morph the borders and still not capture the contents.

noamr commented 4 weeks ago

I was thinking that's what static morph would do. The contents do not get captured but the borders and transforms and stuff do get captured and morphed. I'm not sure there's much of a point to a morph transition if capturing the borders and transforms gets optimized away > since it'd be the same as a crossfade then.

Is there a point in static cross-fade though? If we're not capturing the old contents, we could probably still capture the borders etc and morph them. They're unlikely to change anyway and capturing them is cheap.

noamr commented 4 weeks ago

(Oops deleted your comment by mistake @Psychpsyo, sorry, don't know how to undo)

SebastianZ commented 3 weeks ago

Not sure if the original comment can be recovered, but here is a copy of it from my mails. @Psychpsyo wrote:

Hmm this makes me think that perhaps this is indeed a separate feature, since you should be able to morph the borders and still not capture the contents.

I was thinking that's what static morph would do. The contents do not get captured but the borders and transforms and stuff do get captured and morphed. I'm not sure there's much of a point to a morph transition if capturing the borders and transforms gets optimized away since it'd be the same as a crossfade then.

Psychpsyo commented 3 weeks ago

Not sure if the original comment can be recovered, but here is a copy of it from my mails. @Psychpsyo wrote:

Hmm this makes me think that perhaps this is indeed a separate feature, since you should be able to morph the borders and still not capture the contents.

That is what @noamr said, not me. (I also don't remember what I commented.) But yes, that is what static morph would do.

Is there a point in static cross-fade though? If we're not capturing the old contents, we could probably still capture the borders etc and morph them. They're unlikely to change anyway and capturing them is cheap.

static cross-fade essentially means "slide this element over but don't capture the contents and also don't capture the borders etc." So it indicates that you only want the absolute minimum amount of transition. The author is given the choice of whether or not they want to capture the borders etc. for regular transitions, why should that choice not exist for optimized ones?

Overall: static controls whether or not element content needs to be captured. crossfade and morph control whether or not transforms and borders and such need to get captured.

Now that I've said it like this, yes, two properties might make more sense. (but then I think they might both need more specific names than just view-transition-style)

noamr commented 3 weeks ago

Not sure if the original comment can be recovered, but here is a copy of it from my mails. @Psychpsyo wrote:

Hmm this makes me think that perhaps this is indeed a separate feature, since you should be able to morph the borders and still not capture the contents.

That is what @noamr said, not me. (I also don't remember what I commented.) But yes, that is what static morph would do.

Is there a point in static cross-fade though? If we're not capturing the old contents, we could probably still capture the borders etc and morph them. They're unlikely to change anyway and capturing them is cheap.

static cross-fade essentially means "slide this element over but don't capture the contents and also don't capture the borders etc." So it indicates that you only want the absolute minimum amount of transition. The author is given the choice of whether or not they want to capture the borders etc. for regular transitions, why should that choice not exist for optimized ones?

Probably because this doesn't add much as an optimization. If we're not capturing the old snapshot, capturing the box decorations only is cheap, and if they don't change they also wouldn't animate.

Note also that the word static is confusing here: we're actually showing a dynamic image of the new content. I think view-transition-style is still good because it focuses on the UX result and not on the mechanics of how they're achieved, but this is arguable.

I see this as 3 styles of transition:

We can also have auto that means something like:

khushalsagar commented 3 weeks ago

Given the resolution here: https://github.com/w3c/csswg-drafts/issues/10585#issuecomment-2302433008, looks like we should have a standalone property for this to skip capturing the contents of the old element. Capturing styles is cheap and doesn't need to be optimized out.