Skip to content

Commit 2e1d91d

Browse files
committed
[css-animationworklet] Update README
- Add new SpringTiming example - Make sure all classes in example extend StatelessAnimator - Add Animator to core concept and move GroupEffect and ScrollTimeline to their own section - other minor edits
1 parent b4a12da commit 2e1d91d

File tree

1 file changed

+97
-107
lines changed

1 file changed

+97
-107
lines changed

css-animationworklet/README.md

+97-107
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
# Overview
55

66
Animation Worklet is a new primitive that provides extensibility in web animations and enables high
7-
performance procedural animations on the web. The feature is developed as part of the
7+
performance procedural animations on the web. The feature is developed as part of the
88
[CSS Houdini task force](https://github.com/w3c/css-houdini-drafts/wiki).
99

1010
The Animation Worklet API provides a method to create scripted animations that control a set of
@@ -43,7 +43,6 @@ address this problem.
4343
* Custom scrollbars.
4444
* High-fidelity location tracking and positioning
4545
* [More examples](https://github.com/w3c/css-houdini-drafts/blob/master/scroll-customization-api/UseCases.md) of scroll-driven effects.
46-
4746
* Gesture driven effects:
4847
* [Image manipulator](https://github.com/w3c/csswg-drafts/issues/2493#issuecomment-422153926) that scales, rotates etc.
4948
* Swipe to dismiss.
@@ -65,11 +64,11 @@ address this problem.
6564

6665
Not all of these usecases are immediately enabled by the current proposed API. However Animation
6766
Worklet provides a powerfull primitive (off main-thread scriped animation) which when combined with
68-
other upcoming features (e.g.,
67+
other upcoming features (e.g.,
6968
[Event in Worklets](https://github.com/w3c/css-houdini-drafts/issues/834),
7069
[ScrollTimeline](https://wicg.github.io/scroll-animations/),
71-
[GroupEffect](https://github.com/w3c/csswg-drafts/issues/2071)) can address all these usecases and
72-
allows many of currently main-thread rAF-based animations to move off thread with significant
70+
[GroupEffect](https://github.com/w3c/csswg-drafts/issues/2071)) can address all these usecases and
71+
allows many of currently main-thread rAF-based animations to move off thread with significant
7372
improvement to their smoothness.
7473
See [Animation Worklet design principles and goals](principles.md) for a more extended discussion
7574
of this.
@@ -86,21 +85,29 @@ flag) otherwise they fallback to using main thread rAF to emulate the behaviour.
8685
## Animation Worklet Global Scope
8786
A [worklet global scope](https://drafts.css-houdini.org/worklets/#the-global-scope) that is created
8887
by Animation Worklet. Note that Animation Worklet creates multiple such scopes and uses them to
89-
execute user defined effects.
88+
execute user defined effects. In particular global scopes are regularly switched to enforce
89+
stateless and stateful animator contracts.
90+
91+
92+
## Animator
93+
94+
Animator is a Javascript class that encapsulates the custom animation logic. Similar to other
95+
Houdinig worklets, animators are registered inside the worklet global scope with a unique name which
96+
can be used to uniquely identify them.
97+
9098

9199
## WorkletAnimation
92-
`WorkletAnimation` is a subclass of Animation that can be used to create an custom animation effect
93-
that runs inside a standalone animation worklet scope. A worklet animation has a corresponding
94-
animator instance in a animation worklet scope which is responsible to drive its keyframe effects.
95-
Here are the key differences compared to a regular web animation:
96-
- AnimationId should match a specific animator class registered in the animation worklet scope.
97-
- `WorkletAnimation` may have multiple timelines (including `ScrollTimeline`s).
98-
- `WorkletAnimation` may have a custom properties bag that can be cloned and provided to animator
99-
constructor when it is being instantiated.
100+
`WorkletAnimation` is a subclass of Animation that can be used to create an custom animation that
101+
runs inside a standalone animation worklet scope. A worklet animation has a corresponding animator
102+
instance in a animation worklet scope which is responsible to drive its keyframe effects. Here are
103+
the key differences compared to a regular web animation:
104+
- Name: The name identifies the custom animator class registered in the animation worklet scope.
105+
- Options: `WorkletAnimation` may have a custom properties bag that is cloned and provided to the
106+
corresponding animator constructor when it is being instantiated.
100107

101108
Note that worklet animations expose same API surface as other web animations and thus they may be
102-
created, played, paused, inspected, and generally controlled from main document scope. Here is how
103-
various methods roughly translate:
109+
created, played, paused, inspected, and generally controlled from the main document scope. Here is
110+
how various methods roughly translate:
104111

105112
- `cancel()`: cancels the animation and the corresponding animator instance is removed.
106113
- `play()`: starts the animation and the corresponding animator instance gets constructed and
@@ -111,39 +118,8 @@ various methods roughly translate:
111118
the animator instance. (We are considering possiblity of having a `onPlaybackRateChanged`
112119
callback)
113120

114-
## ScrollTimeline
115-
[ScrollTimeline](https://wicg.github.io/scroll-animations/#scrolltimeline) is a concept introduced in
116-
scroll-linked animation proposal. It defines an animation timeline whose time value depends on
117-
scroll position of a scroll container. `ScrollTimeline` can be used an an input timeline for
118-
worklet animations and it is the intended mechanisms to give read access to scroll position.
119-
120-
## GroupEffect
121-
[GroupEffect](https://w3c.github.io/web-animations/level-2/#the-animationgroup-interfaces) is a
122-
concept introduced in Web Animation Level 2 specification. It provides a way to group multiple
123-
effects in a tree structure. `GroupEffect` can be used as the output for worklet animations. It
124-
makes it possible for worklet animation to drive effects spanning multiple elements.
125-
126-
**TODO**: At the moment, `GroupEffect` only supports just two different scheduling models (i.e.,
127-
parallel, sequence). These models governs how the group effect time is translated to its children
128-
effect times by modifying the child effect start time. Animation Worklet allows a much more
129-
flexible scheduling model by making it possible to to set children effect's local time directly. In
130-
other words we allow arbitrary start time for child effects. This is something that needs to be
131-
added to level 2 spec.
132-
133-
## ~~Multiple Timelines~~
134-
Unlike typical animations, worklet animations can be attached to multiple timelines. This is
135-
necessary to implement key usecases where the effect needs to smoothly animate across different
136-
timelines (e.g., scroll and wall clock).
137-
138-
**NOTE**: We have decided to drop this piece in favor of alternative ideas. Most recent
139-
[promising idea](https://docs.google.com/document/d/1byDy6IZqvaci-FQoiRzkeAmTSVCyMF5UuaSeGJRHpJk/edit#heading=h.dc7o68szgx2r)
140-
revolves around allowing worklet and workers to receive input events directly. (here are some
141-
earlier alternative design: [1](https://docs.google.com/document/d/1-APjTs9fn4-E7pFeFSfiWV8tYitO84VpmKuNpE-25Qk/edit), [2](https://github.com/w3c/csswg-drafts/issues/2493), [3](https://github.com/w3c/csswg-drafts/issues/2493#issuecomment-422109535)
142-
143-
144121
## Statefull and Statelss Animators
145122

146-
147123
Sometimes animation effects require maintaining internal state (e.g., when animation needs to depend
148124
on velocity). Such animators have to explicitly declare their statefulness but by inheritting from
149125
`StatefulAnimator` superclass.
@@ -153,7 +129,6 @@ lifetime duration. For example user agents are free to initially run the animato
153129
but later decide to migrate it off main thread to get certain performance optimizations or to tear
154130
down scopes to save resources.
155131

156-
157132
Animation Worklet helps stateful animators to maintain their state across such migration events.
158133
This is done through a state() function which is called and animator exposes its state. Here is
159134
an example:
@@ -199,82 +174,98 @@ registerAnimator('animation-with-local-state', class FoorAnimator extends Statef
199174
});
200175
```
201176

202-
# Examples
177+
# Related Concepts
178+
179+
The following concepts are not part of Animation Worklet specification but animation worklet is
180+
designed to take advantage of them to enable a richer set of usecases.
181+
182+
## ScrollTimeline
183+
[ScrollTimeline](https://wicg.github.io/scroll-animations/#scrolltimeline) is a concept introduced in
184+
scroll-linked animation proposal. It defines an animation timeline whose time value depends on
185+
scroll position of a scroll container. `ScrollTimeline` can be used an an input timeline for
186+
worklet animations and it is the intended mechanisms to give read access to scroll position.
187+
188+
## GroupEffect
189+
[GroupEffect](https://w3c.github.io/web-animations/level-2/#the-animationgroup-interfaces) is a
190+
concept introduced in Web Animation Level 2 specification. It provides a way to group multiple
191+
effects in a tree structure. `GroupEffect` can be used as the output for worklet animations. It
192+
makes it possible for worklet animation to drive effects spanning multiple elements.
203193

204-
**TODO**: Add gifs that visualize these effects
194+
**TODO**: At the moment, `GroupEffect` only supports just two different scheduling models (i.e.,
195+
parallel, sequence). These models governs how the group effect time is translated to its children
196+
effect times by modifying the child effect start time. Animation Worklet allows a much more
197+
flexible scheduling model by making it possible to to set children effect's local time directly. In
198+
other words we allow arbitrary start time for child effects. This is something that needs to be
199+
added to level 2 spec.
205200

206-
## Hidey Bar
207-
An example of header effect where a header is moved with scroll and as soon as finger is lifted
208-
it animates fully to close or open position depending on its current position.
209201

202+
# Examples
210203

211-
``` html
212-
<div id='scrollingContainer'>
213-
<div id='header'>Some header</div>
214-
<div>content</div>
215-
</div>
204+
## Custom Spring Timing
216205

217-
<script>
218-
await CSS.animationWorklet.addModule('hidey-bar-animator.js');
206+
Use Animation Worklet to create animation with a custom spring timing.
219207

220-
const scrollTimeline = new ScrollTimeline({
221-
scrollSource: $scrollingContainer,
222-
orientation: 'block',
223-
timeRange: 100
224-
});
225-
const documentTimeline = document.timeline;
226208

209+
```html
227210

228-
const animation = new WorkletAnimation('hidey-bar',
229-
new KeyframeEffect($header,
230-
[{transform: 'translateX(100px)'}, {transform: 'translateX(0px)'}],
231-
{duration: 100, iterations: 1, fill: 'both' })
232-
scrollTimeline,
233-
{scrollTimeline, documentTimeline},
234-
);
211+
<div id='target'></div>
212+
213+
<script>
214+
await CSS.animationWorklet.addModule('spring-animator.js');
215+
216+
const effect = new KeyframeEffect($target,{transform: ['translateX(0)', 'translateX(50vw)'}], {duration: 1000});
217+
const animation = new WorkletAnimation('spring', effect, document.timeline, {k: 2, ratio: 0.7);
235218
animation.play();
236219
</script>
237220
```
238221
239-
hidey-bar-animator.js:
240-
```js
241-
registerAnimator('hidey-bar', class {
222+
spring-animator.js:
242223
243-
constructor(options) {
244-
this.scrollTimeline_ = options.scrollTimeline;
245-
this.documentTimeline_ = options.documentTimeline;
224+
```js
225+
registerAnimator('spring', class SpringAnimator extends StatelessAnimator {
226+
constructor(options = {k: 1, ratio: 0.5}) {
227+
this.timing = createSpring(options.k, options.ratio);
246228
}
247229
248230
animate(currentTime, effect) {
249-
const scroll = this.scrollTimeline_.currentTime; // [0, 100]
250-
const time = this.documentTimeline_.currentTime;
251-
252-
// **TODO**: use a hypothetical 'phase' property on timeline as a way to detect when user is no
253-
// longer actively scrolling. This is a reasonable thing to have on scroll timeline but we can
254-
// fallback to using a timeout based approach as well.
255-
const activelyScrolling = this.scrollTimeline_.phase == 'active';
256-
257-
let localTime;
258-
if (activelyScrolling) {
259-
this.startTime_ = undefined;
260-
localTime = scroll;
261-
} else {
262-
this.startTime_ = this.startTime_ || time;
263-
// Decide on close/open direction depending on how far we have scrolled the header
264-
// This can even do more sophisticated animation curve by computing the scroll velocity and
265-
// using it.
266-
this.direction_ = scroll >= 50 ? +1 : -1;
267-
localTime = this.direction_ * (time - this.startTime_);
268-
}
269-
270-
// Drive the output effects by setting its local time.
271-
effect.localTime = localTime;
231+
let delta = this.timing(currentTime);
232+
// scale this by target duration
233+
delta = delta * (effect.getTimings().duration / 2);
234+
effect.localTime = delta;
235+
// TODO: Provide a method for animate to mark animation as finished once
236+
// spring simulation is complete, e.g., this.finish()
237+
// See issue https://github.com/w3c/css-houdini-drafts/issues/808
238+
}
272239
});
273240
241+
function createSpring(springConstant, ratio) {
242+
// Normalize mass and distance to 1 and assume a reasonable init velocit
243+
// but these can also become options to this animator.
244+
const velocity = 0.2;
245+
const mass = 1;
246+
const distance = 1;
247+
248+
// Keep ratio < 1 to ensure it is under-damped.
249+
ratio = Math.min(ratio, 1 - 1e-5);
250+
251+
const damping = ratio * 2.0 * Math.sqrt(springConstant);
252+
const w = Math.sqrt(4.0 * springConstant - damping * damping) / (2.0 * mass);
253+
const r = -(damping / 2.0);
254+
const c1 = distance;
255+
const c2 = (velocity - r * distance) / w;
256+
257+
// return a value in [0..distance]
258+
return function springTiming(timeMs) {
259+
const time = timeMs / 1000; // in seconds
260+
const result = Math.pow(Math.E, r * time) *
261+
(c1 * Math.cos(w * time) + c2 * Math.sin(w * time));
262+
return distance - result;
263+
}
264+
}
274265
```
275266
276-
277267
## Twitter Header
268+
278269
An example of twitter profile header effect where two elements (avatar, and header) are updated in
279270
sync with scroll offset.
280271
@@ -307,7 +298,7 @@ animation.play();
307298
308299
twitter-header-animator.js:
309300
```js
310-
registerAnimator('twitter-header', class {
301+
registerAnimator('twitter-header', class TwitterHeader extends StatelessAnimator {
311302
constructor(options) {
312303
this.timing_ = new CubicBezier('ease-out');
313304
}
@@ -329,6 +320,7 @@ registerAnimator('twitter-header', class {
329320
```
330321
331322
### Parallax
323+
332324
```html
333325
<style>
334326
.parallax {
@@ -375,7 +367,7 @@ parallax-animator.js:
375367
376368
```js
377369
// Inside AnimationWorkletGlobalScope.
378-
registerAnimator('parallax', class {
370+
registerAnimator('parallax', class Parallax extends StatelessAnimator{
379371
constructor(options) {
380372
this.rate_ = options.rate;
381373
}
@@ -386,7 +378,6 @@ registerAnimator('parallax', class {
386378
});
387379
```
388380
389-
390381
# WEBIDL
391382
392383
`WorkletAnimation` extends `Animation` and adds a getter for its timelines.
@@ -424,7 +415,6 @@ partial interface AnimationEffectReadOnly {
424415
425416
```
426417
427-
428418
# Specification
429419
The [draft specification](https://drafts.css-houdini.org/css-animationworklet) is
430420
the most recent version.

0 commit comments

Comments
 (0)