[css-animations] Proposal for animation triggers, timebases and additive behavior

NOTE: This isn't a very polished proposal. Since it is www-style
I'd expect a zillion comments/alternatives/complaints even if it
were polished.

This is a proposal for three new CSS animation properties in
Level 2 of the specification.

- The first, animation-trigger, determines the moment which an animation will
  consider its clock/timebase to begin and end advancing. The best example of
  this is an animation that should trigger when the page has been scrolled to a
  particular point.

- The second, animation-timebase, specifies what the animation should use as a
  clock. By default it is wall-clock time, but this property would allow the
  animation to map the scroll position of a page into the animation's time
  domain (effectively scrubbing the animation as the user scrolls), or possibly
  use a media element as the clock.

- The third, animation-behavior, specifies if concurrent animations on the same
  property should overwrite or add to the value (as defined by 4.1.3 of Web
  Animations)

We've made a prototype implementation of these features and are very happy with
the results. Not only were we able to replicate many of the currently common
trends in Web design (pages that react to scrolling), we were able to do so in
a purely declarative manner. So-called "parallax" effects are easy to design
with this feature.

There is also a performance benefit. With this feature it is possible to
determine in advance if a scroll will trigger layout, which allows an
implementation to optimize the scroll (e.g. if scrolling and animation happen
on a separate thread).

The implementation itself was also quite simple. We wouldn't expect it to be a
burden for other browser engines.

But, most importantly, it provides a significant increase in functionality for
CSS animations, while being a very small addition to the specification.

It also fits in nicely with the Web Animation model, effectively providing
some declarative syntax for what WebAnim exposes in script.

Now, for the more formal version. We're not particularly concerned with syntax
at the moment, so I expect many comments and the names to change over time.

animation-trigger
-----------------

Name: animation-trigger
Value: auto | scroll(<snap point> [, <snap point>]+)
Initial: auto
Inherited: no
Animatable: no

Defines the point at which the time input to the animation will "tick",
effectively controlling when the start time of the animation will be, and when
the animation clock will stop advancing.

A value of "auto" means the animation will use its timebase as a trigger (which
is the wall-clock time by default). This is the current behaviour, where an
animation will start at a time determined by animation-delay after the moment
the animation style was applied.

A value of "scroll" means the animation will begin advancing its time
value/timeline once the element's scrolling container has scrolled to a
particular position. It uses the same syntax as CSS Scroll Snap Points,
although we might need to tweak the syntax a bit because I think people will
want expressions like calc(elements + 10%), so the animation starts slightly
before the element comes onto the page (see NOTE1 below).

The scroll function takes a list of values. The odd values define starting
triggers. The even values define a corresponding ending trigger. An ending
trigger simply freezes the timeline once it is hit (although see the
animation-timebase property below).

Each trigger fires once as the container scrolls from above the point
to below the point (See NOTE3).

NOTE1: Just the scroll snap point is not enough. We also need the
scroll-snap-coordinate and scroll-snap-destination. It's a shame there is no
shorthand for the scroll snaps.

NOTE2: there are so many ways to define the syntax here
that we expect it to change a lot. The important features
are:

- starting an animation at a point in the page
- a point is able to be specified in relation to an element's position
- it's possible to specify an end point, which would stop the animation
  (although this could be split into two properties, so you
  could have on/off animations as you scroll).

NOTE3: We'll probably need something to define if the trigger should refire if
you scroll past the point again.

NOTE4: Should there be a way to have something inside overflow:scroll react
to the page's scroll? (without crossing frame boundaries)

Example 1. Fade elements in when they are scrolled into view.

.reveal-section {
  animation-name: fade-in;
  animation-duration: 1s;
  animation-fill-mode: backwards;
  animation-trigger: scroll(elements);
}

@keyframes fade-in {
  from { opacity: 0; }
  to { opacity: 1; }
}

Example 2. Spin an element while the page is between two points, assuming the
element is not inside a child scrolling container.

.spins-on-position {
  animation-name: spin;
  animation-duration: 300ms;
  animation-fill-mode: both;
  animation-iteration-count: infinite;
  animation-timing-function: linear;
  animation-trigger: scroll(100px, 500px);
}

@keyframes spin {
  from { transform: rotate(0); }
  to { transform: rotate(1turn); }
}


NOTE: The syntax is designed to be extensible, so that future triggers
could be easily added. For example:

- some point in a media stream
- after a resource load
- before page navigation
- after another animation ends or iterates


animation-timebase
-----------------

Name: animation-timebase
Value: auto | scroll | url(<media element>)
Initial: auto
Inherited: no
Animatable: no

The animation-timebase property defines how the time value will advance.

The default value is "auto", which means the animation uses the normal document
timeline (as it does today).

A value of "scroll" means the timeline of the animation will be driven from the
scrolling container's position. This works in conjunction with the
animation-trigger, such that the duration of the animation is mapped onto the
length of the trigger. In Example 2 above, 100px would be equivalent to 0s and
500px would be equivalent to 300ms, meaning the animation would play through
once as you scrolled.

Obviously scrolling up the page would effectively reverse the time.

If the animation does not have an end trigger, "scroll" acts like "auto" (or
should there be a defined end? My example below uses the end of the page as the
end trigger.).

A value of "url()" references a media element that acts as the timebase. The
duration of the media maps to the duration of the animation.

NOTE: There should be a way to map the duration *range* of the media to the
duration of the animation, so that you don't need to know the media duration in
order to make a progress bar.

Example 3. Draw a reading progress bar along the top of the page as the user scrolls.

#progress {
  position: fixed;
  top: 0;
  width: 0;
  height: 2px;
  background-color: red;
  animation-name: progress;
  animation-duration: 1s;
  animation-timing-function: linear;
  animation-trigger: scroll(0px);
  animation-timebase: scroll;
}

@keyframes progress {
  from { width: 0vw; }
  to { width: 100vw; }
}


animation-behavior
-----------------

Name: animation-behavior
Value: replace | add
Initial: replace
Inherited: no
Animatable: no

Defines how multiple animations on the same property interact with each other.

The default value is "replace", where the last animation will overwrite all
other animations in definition order.

A value of "additive" means that the animation will add to the value provided
by the previous animation. This term, and how it works on various properties
like transform and color, is defined in lots of other specs. For example,
section 4.1.3 of the Web Animations spec lists a lot of cases (color, transforms,
lengths).


Example 4. Animate translation and rotation separately.

#woohoo {
  animation-name: move, spin;
  animation-duration: 3s, 7s;
  animation-iteration-count: infinite;
  animation-direction: alternate;
  animation-behaviour: replace, add;
}

@keyframes move {
  from { -webkit-transform: translate(0); }
  to { -webkit-transform: translate(500px, 500px); }
}

@keyframes spin {
  from { -webkit-transform: rotate(0); }
  to { -webkit-transform: rotate(2turn); }
}

Received on Tuesday, 9 September 2014 22:58:35 UTC