w3c / csswg-drafts

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

[scroll-animations-1] Transition delays in scroll-linked animations #7059

Open fantasai opened 2 years ago

fantasai commented 2 years ago

Copying out @flackr's comment https://github.com/w3c/csswg-drafts/issues/6674#issuecomment-945845884

Here's a good example where animations a kinda-scroll-linked but not directly: https://codepen.io/isladjan/pen/abdyPBw

@smfr For these use cases I propose properties similar to transition timing functions (transition-duration, transition-timing-function, transition-delay) which will specify a delay over which the scroll input will plug in to the timeline.

E.g. for view-timeline:

view-timeline-transition-duration: <time>
view-timeline-transition-timing-function: <easing-function>
view-timeline-transition-delay: <time>

Then for the above use case the developer would simply specify a view-timeline-transition-duration: 2s and the position of the animation time would smoothly move towards the scroll position over 2s.

[...]

The duration in my proposed property is the length of time it takes the scroll based timeline to catch up to scroll position changes. Here's an example hacked together with javascript using a custom property transition to represent the delayed transition value being plugged in to the timeline:

https://jsbin.com/qekodug/edit?css,js,output

css-meeting-bot commented 2 years ago

The CSS Working Group just discussed transition delays in scroll-linked animations.

The full IRC log of that discussion <TabAtkins> Topic: transition delays in scroll-linked animations
<TabAtkins> github: https://github.com/w3c/csswg-drafts/issues/7059
<TabAtkins> flackr: in reponse to smfr comment, many scroll-linked aniamtions today don't immediately update to the scroll position, they have a delay in time
<TabAtkins> flackr: whether that's due to ipml issues is an open question
<dbaron> s/ipml/implementation/
<TabAtkins> flackr: in order to support this, could add something similar to transitions, where the timeline would target the currnet scroll position, but take an easing function to progress to it
<TabAtkins> flackr: I put together a jsbin to show off constructing this with a custom prop and some jS
<bramus> https://jsbin.com/qekodug/edit?css,js,output
<TabAtkins> flackr: so should we do this now, and if so do the properties make sense, or do this as a l2 feature?
<TabAtkins> fantasai: suggest we wait for smfr, suspect he'll have an opinion
<bramus> q+
<TabAtkins> fantasai: re: level, seems easy to defer, so maybe level 2? but we dont' have an fpwd of l1 yet, so we can include it and split it later if needed
<bramus> q-
<TabAtkins> ydaniv: you might be able to do some of this now with easing, or like the new linear() function
<TabAtkins> flackr: you can't do this using any existing easing function - right now, if you scroll to a position we immediately update the time to match the position
<TabAtkins> flackr: you can use easing to control how you respond to time, but you can't delay the update
<TabAtkins> flackr: if smfr shows up today, i'd love to get this in if possible
<TabAtkins> astearns: so let's table for now and see if we can get simon on the call today
smfr commented 2 years ago

It feels like adding transition-related things to an animation timeline is trying to make it do too much, and makes it really hard to reason about what the duration and timing function really do.

Logically, it feels to me like the best approach is to allow authors to link "timeline modifiers" together in a list, where each modifier can alter the delay, rate and offset of the input progress, and output a new progress. This feels like it should be a new feature of the Web Animations API, rather than being bolted onto scroll timelines.

flackr commented 2 years ago

So to put it in more concrete terms, would the author create a new timeline which modifies an existing one? E.g. You might create something like a DelayedTimeline which sourced another timeline:

element.animate(parallaxKeyframes, {
  timeline: new DelayedTimeline(new ScrollTimeline(), {ease: 'ease'})
});

or in css:

#target {
  animation: parallax delayed(scroll() 500ms ease);
}
flackr commented 2 years ago

Though I think the TLDR is that such an approach would be an improvement to animations support generally rather than part of scroll-animations-1. If so, should we close / repurpose this to be e.g. web-animations-2?

ydaniv commented 2 years ago

@smfr

makes it really hard to reason about what the duration and timing function really do.

That's not an issue here since in*-linked animations, as opposed to time-based animations, the duration is always 100%, and the delay is just an offset of the start. So, there's nothing to reason about here, it's just an extra interpolation between last value and the current one, described by a transition.

If it's not clear, I'll try to quickly create a demo.

Your suggestion for progress modifiers sounds like it's taking the same idea, but allowing authrs to provide a custom function for the easing. So might as well use cubic-bezier() or linear() now, or whatever we'll have next?

flackr commented 2 years ago

If it's not clear, I'll try to quickly create a demo.

FWIW I wrote a demo of this in the initial post (by setting a custom var to the scroll progress and applying a transition on it): https://jsbin.com/qekodug/edit?css,js,output

It is exactly as you say, a transition from the old scroll / time value to the new scroll / time value, the same as transitions work.

ydaniv commented 1 year ago

I'd like to take a shot at revisiting this issue, because this pattern is used quite often. I think we can take a different angle at this, by defining a friction factor to the interpolation on each frame between last and current positions, instead of having a transition with a defined duration on the animation, which is in fact the implicit way of calculating this factor.

Friction is actually 1 - playbackRate, so for the default playbackRate=1 we get friction=0 and we get the default link between scroll and effect. For 0 < playbackRate < 1 we start getting friction>0 and the effect starts lagging behind the scroll progress in a sort of a transition and we get this drag. I think this is what @smfr was referring to above, and I think this might also simplify implementation.

The way the playbackRate is used here is by interpolating on each frame from last position to current position using a simple lerp: a * (1 - t) + b * t, where t is the playbackRate. So you can see how for t=0 the animation is paused, and we get default behavior for t=1.

Applying easing on this effect is less common. Since the lerp already provides a nice curve, adding an extra easing on top is a bit redundant, but may be possible, I'm not quite sure yet whether this should be expressed in the interpolation function (i.e. instead of a simple lerp) or perhaps in some other way, modifying the progress that is calculated by it.

Still an open question, if we consider the above, is if/how we can expose a declarative syntax/API for this.

@flackr @birtles WDYT?

birtles commented 1 year ago

I read through the issue but I'm not sure I quite follow where / how the friction factor is applied or where the playbackRate gets updated.

ydaniv commented 1 year ago

@birtles ok, here's a demo of what I mean: https://codepen.io/ydaniv/pen/VwBojoN

Now that I'm looking at it, I'm not sure I'm convinced myself, since the effect is sort of exponential in relation to the playbackRate and not linear, so the effect starts being prominent only from about 0.1 and below.

ydaniv commented 1 year ago

@birtles @flackr @fantasai I think this can still prove very useful for non-time-based timelines, this is a very common pattern used in the wild, adding a some lerp on the progress, which becomes just sync progress for playbackRate=1. I think it could make sense to consider it regardless of transition delays, as an effect that's tied to playbackRate. WDYT?