[csswg-drafts] Mixing scroll & time-based delay & durations (scroll-triggered animations) (#4339)

AmeliaBR has just created a new issue for https://github.com/w3c/csswg-drafts:

== Mixing scroll & time-based delay & durations (scroll-triggered animations) ==
Regarding the section on [scroll-triggered animations](https://wicg.github.io/scroll-animations/#scroll-triggered-animations), which says:

> The design space for triggering animations is still open. We welcome input on this subject.

I think this is a very important use case & it shouldn't be left behind, as the overall design of the spec will be different if you're considering scroll-triggered and scroll-driven animations together.

In w3c/scroll-animations#39, @stephenmcgruer reports feedback complaining about the missing use case of "scroll into view" or "scroll out of view" animations, which are one type of scroll-triggered animations.  From Stephen's perspective:

> The common problem shared between these use cases is that they don't want to animate based on the absolute scroll position, but instead based on the location of an element within the scroller.

Stephen goes on to propose modifications to the scroll timeline that would make it possible to define a target scroll position or duration relative to the position of an element within the scroller.  I think those are important suggestions, and discussion of them should continue in w3c/scroll-animations#39.

But, there is another aspect of these use cases: the designer often wants to combine a scroll-based start time with a fixed-time animation or transition, and maybe also a scroll-based end to a repeating animation.  That's what I'm discussing here.

For a scripted animation, the use case is somewhat addressed by using IntersectionObserver to define the trigger, and then initiating (or cancelling) a regular time-based web animation call.  But it would be preferable to be able to define the trigger & animation in the same API, without callbacks.  And a solution is still needed for declarative animations.

The concern raised in the current spec draft is that:

> it’s not possible to trigger CSS transitions from the compositor thread (because triggering a transition requires style resolution…)

I assume this perspective is based on how scroll-triggered are currently implementing, by adding a class to the element that triggers a style recalc which triggers a transition.

But another way of thinking about a scroll-trigger is to look at it as a _delay_.  The transition or animation has already been calculated and queued up, but it is waiting for the correct time to start running.

CSS animations and web animations already have built-in separation of _delay_ times versus _duration_ times.  Why not define the scroll timelines in a way that you can make one of these times scroll-based but not the other?

Requirements
---------------

(Using the CSS/declarative API for discussion, because that's what I'm most familiar with and it's the more difficult use case currently.  And anything that can be defined in CSS can be represented in JS APIs, while the reverse isn't always true.)

Using a single [`animation-timeline`](https://wicg.github.io/scroll-animations/#animation-timeline) property doesn't make sense if different aspects of the animation can be tied to different timelines.

Instead, we need values that can be used directly in the `animation` and `transition` `-delay` and `-duration` properties.  These values would represent times that cannot be resolved until runtime.

However, we don't want to have to repeat all the scroll timeline parameters in each property; you should be able to reuse the same timeline for multiple animation effects.

For _delays_, you should be able to combine these unresolved time functions with literal times in a `calc()` expression.  There's precedent for this in the all-encompasing web animations conceptual model: event-based timings in SMIL, which allow the start and end times of an animation to be defined as a certain time after (or before) an event.  The ideal model should be extensible to support these types of unresolved triggers, too. (E.g., to delay a transition on an image until its `load` event fires).

For animation/transition _durations_, combining times and scroll positions is more complicated: scroll position is a representation of progress, not just a trigger event that occurs at a single point in time. If you wanted to combine the scroll progress with a time duration, you would need a conversion factor like the `timeRange` that's in the current spec.  But I think it might turn out to be unneccessary: durations should logically be either scroll based or time based, but not some combination of the two.   Position within duration can be defined as a percentage, rather than by mapping to a time value.

This I think would also cover some of the usability suggestions @bradkemper makes in w3c/scroll-animations#44, although in a different way.

Proposal
----------

A new `@timeline` rule allows scroll timelines to be defined independent of any element.  The rule would take an identifier and then a list of descriptors that replace the parameters to the `scroll()` function in the current spec.  One of the descriptors would be a `timeline-type: scroll`, to allow other types of timelines to be added later.

Define a new `timepoint()` function (name to be debated) for use in the `-delay` properties, which takes a timeline identifier and an offset position, and a keyword that indicates if the timepoint should be triggered when tranversing the timeline forward, backward, or both (default to be debated).  A timepoint function is treated as an unresolved time value and can be combined with literal times in `calc()` expressions.

Define a `timeline()` function for use in the `-duration` properties which takes a timeline identifier and optionally a percentage (default 100%) or some keyword that indicates "however much is left of the timeline after the delay".

Extend the `animation-iteration-count` property to include a "repeat until `<timepoint>`" syntax.

Example:

```css
@timeline root-scroller {
  timeline-type: scroll;
  timeline-node: selector(:root) ; /* the element that controls this timeline;
         the selector would match a single element using document.querySelector() rules */
  scroll-direction: block; /* descriptors that are specific to scroll timelines */
  scroll-offset-start: 5rem;
  scroll-offset-end: calc(100% - 5rem); /* scroll-offset-end instead of end-scroll-offset
          so that we could have a scroll-offset shorthand */ 
}
.progress-bar {
  animation-name: progress;
  animation-duration: timeline(root-scroller);
}
.footer-effect {
  animation-name: almost-done, finished;
  animation-delay: timepoint(root-scroller 95% forwards), calc(timepoint(root-scroller 100%) + 0.5s);
  animation-duration: 0.5s, 1s, 
}
```

Possible extensions to other types of timelines:

-  something like the "viewport" timeline proposed in https://github.com/WICG/scroll-animations/issues/39#issuecomment-472903337 for defining the position of an element within the viewport or its scrolling container, if it turns out that it's easier to treat that as a separate timeline type instead of an extra descriptor/option on a scroll timeline.

- a media playback timeline, where the `timeline-node` is an audio or video element that you want to sync the animations to.

- an event-based timeline, like the SMIL model, where time runs as normal but the timeline start (and maybe end) time is sync'd to a specific event.  `timeline-node` would need a keyword to represent "the same element as the animation" instead of a selected node.  It would also need a keyword for events on the `window` — which would finally allow us to create CSS animations where all animations are sync'd to a master timeline (e.g., based on the window's `DOMContentLoaded` event), instead of being staggered by when each element gets added to the DOM.

Remaining issue:

To use this for transition delays, we still need to have a separate style calc for representing the "pre-transition" value.  Which currently means that you need to have the element in the DOM & style calculated, and then trigger a state change that changes the styles, like adding a class to the root element after the first user interaction event.  Which cancels out the benefit of doing this all declaratively.

This issue isn't specific to scroll effects: it's a general limitation for any transition that you want to trigger after loading a page or adding an element to it.  Either you need to force a style calc in your script, or you need to use a keyframes animation instead of a transition.  I wonder if we could define a "before first render" pseudoclass that allows the author to declare a set of styles that are used solely for calculating initial transitions?

Please view or discuss this issue at https://github.com/w3c/csswg-drafts/issues/4339 using your GitHub account

Received on Thursday, 19 September 2019 05:41:40 UTC