w3c / csswg-drafts

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

[scroll-animations] playbackRate effect on non time-based animations #8816

Open ydaniv opened 1 year ago

ydaniv commented 1 year ago

A common pattern in animations based on scroll/hover is adding interpolation from last position to current position, so that a smooth movement is achieved even on abrupt progress changes, e.g. clicking on the scrollbar in a different location.

I proposed adding this effect by defining the interaction of Animation.playbackRate with non time-based animations here: https://github.com/w3c/csswg-drafts/issues/7059#issuecomment-1415913052.

The Web Animations API 1 spec of Animation's playbackRate:

Animations have a playback rate that provides a scaling factor from the rate of change of the associated timeline’s time values to the animation’s current time.

If we translate that into non time-based (non-monotonic timelines) it's effectively a linear interpolation on each frame between current progress and last progress, so on each frame the progress' update becomes:

new_progress = last_progress * (1 - playbackRate) + current_progress * playbackRate

And this maintains an effectively paused animation on playbackRate == 0 and a flipped progress for playbackRate == -1.

In respect to Scroll-driven timelines progress is described as:

The startmost scroll position represents 0% progress and the endmost scroll position represents 100% progress.

I've created a demo that I hopes at least captures the nature of this feature: https://codepen.io/ydaniv/pen/VwBojoN Notice that the effect is mostly prominent from playbackRate=~0.1 and below, because of its exponential nature.

I think this feature is orthogonal to the Transition delays feature suggested in #7059, although it creates a similar effect.

cc @birtles @flackr @fantasai @smfr

ydaniv commented 1 year ago

Had a talk with @andruud regarding using the last_progress for this and seems this is possible technically.

cc @bramus

flackr commented 1 year ago

This is a creative interpretation of playback rate which I can see how at a high level fits. However, playback rate already has a very specific definition of how it affects animation timing which is heavily used in many of the calculations. A negative playback rate is also used to reverse animations. All of this currently works as spec'd on scroll animations. This would make playback rate have special behavior for non-time based animations, and it would be really hard to avoid unexpected issues when other timing related properties change at the same time - as the proposed behavior is based on the previous state. E.g. what happens if you change the start time of the animation, or the duration, does it slowly progress towards the new time value?

it's effectively a linear interpolation on each frame between current progress and last progress

Naively, this would make the speed of the adjustment depend on the frame rate the browser is able to render or particular device framerates. I would think that if we have a mechanism for doing this it should be framerate independent, i.e. based on the time that had passed. Also while the progress per frame is a linear interpolation, the progress over time will be non-linear, e.g. it'll make 0.5, then 0.75, then 0.875 progress never technically getting to the current time value.

I do feel like this is the same high level feature as issue #7059, just approaching it from a different direction.

ydaniv commented 1 year ago

However, playback rate already has a very specific definition of how it affects animation timing which is heavily used in many of the calculations

Right, so effectively when calculating current time for ScrollTimeline:

current time = scroll offset ÷ (scrollable overflow size − scroll container size)

You get:

current time =  = last current time × (1 - playback rate) + scroll offset ÷ (scrollable overflow size − scroll container size) × playback rate

Where last current time is the last cached value for current time.

And of course it follows that:

A negative playback rate is also used to reverse animations

That still holds true.

This would make playback rate have special behavior for non-time based animations

Yes, but not something we don't already have.

and it would be really hard to avoid unexpected issues when other timing related properties change at the same time - as the proposed behavior is based on the previous state.

I tried to see how, I think the we can infer the rest of the properties from the above formula.

E.g. what happens if you change the start time of the animation, or the duration, does it slowly progress towards the new time value?

Yes. After reflow the next frame should calculate the new progress, according to the cached value and new layout, and produce the next frame. So you'll get an interpolation towards the new progress value.

Naively, this would make the speed of the adjustment depend on the frame rate the browser is able to render or particular device framerates. I would think that if we have a mechanism for doing this it should be framerate independent, i.e. based on the time that had passed.

Yes, for sure. I think one way to make it framerate independent could be normalizing the frame rate to 60fps (or the timeline's frame rate according to #7196), but I think this requires something that's not a trivial division. Otherwise, yes, we'd need to base that on timestamp deltas.

Also while the progress per frame is a linear interpolation, the progress over time will be non-linear, e.g. it'll make 0.5, then 0.75, then 0.875 progress never technically getting to the current time value.

Exactly. The result creates a nice some-sort-of-bezier curve movement. This is expected. But I think using linear interpolation for the core calculation is pretty straight forward to reason with, and the result is quite nice (: It's also a quite common method in games dev/graphic tools/etc.

I do feel like this is the same high level feature as issue https://github.com/w3c/csswg-drafts/issues/7059, just approaching it from a different direction.

Yes, the result is quite similar, but different. Some libraries use that transition method, like GSAP's scrub option, while other use lerping like locomotive scroll's lerp.