@@ -224,8 +224,14 @@ function createSpring(springConstant, ratio) {
224224}
225225```
226226
227+ Note that ideally once sping simulation is finished, the worklet animation would also dispatch
228+ the ` finish ` event. Adding the necessary mechanism to enable this is tracked
229+ [ here] ( https://github.com/w3c/css-houdini-drafts/issues/808 ) .
230+
227231## Twitter Header
228232
233+ Note: This assumes experimental [ ScrollTimeline] [ scroll-timeline ] feature.
234+
229235An example of twitter profile header effect where two elements (avatar, and header) are updated in
230236sync with scroll offset.
231237
@@ -281,72 +287,137 @@ registerAnimator('twitter-header', class TwitterHeader extends StatelessAnimator
281287 effect .children [1 ].localTime = this .timing_ (this .clamp (scroll, 0 , 1 ));
282288 }
283289});
284-
285290```
286291
287- ## Parallax
292+ ## Swipe-to-Dissmiss
288293
289- ``` html
290- <style >
291- .parallax {
292- position : fixed ;
293- top : 0 ;
294- left : 0 ;
295- opacity : 0.5 ;
296- }
297- </style >
298- <div id =' scrollingContainer' >
299- <div id =" slow" class =" parallax" ></div >
300- <div id =" fast" class =" parallax" ></div >
301- </div >
302- <script >
303- await CSS .animationWorklet .addModule (' parallax-animator.js' );
294+ Another usecase for Animation Worklet is to enable interactive input-driven animation effects that
295+ are driven both by input events and time.
304296
305- const parallaxSlowEl = document .getElementById (' slow' );
306- const parallaxFastEl = document .getElementById (' fast' );
307- const scrollingContainer = = document .getElementById (' scrollingContainer' );
297+ To enable this we need a way to receive pointer events in worklet (e.g. via [ CSS custom
298+ variables] ( https://github.com/w3c/css-houdini-drafts/issues/869 ) or [ other
299+ mechanisms] [ input-for-worker ] ) and
300+ also allow [ playback controls] ( https://github.com/w3c/css-houdini-drafts/issues/808 ) inside
301+ worklets. Both of these are natural planned additions to Animation Worklet.
308302
309- const scrollTimeline = new ScrollTimeline ({
310- scrollSource: scrollingContainer,
311- orientation: ' block' ,
312- timeRange: 1000
313- });
314- const scrollRange = scrollingContainerEl .scrollHeight - scrollingContainerEl .clientHeight ;
315303
316- const slowParallax = new WorkletAnimation (
317- ' parallax' ,
318- new KeyframeEffect (parallaxSlowEl, [{' transform' : ' translateY(0)' }, {' transform' : ' translateY(' + - scrollRange + ' px)' }], scrollRange),
319- scrollTimeline,
320- {rate : 0.4 }
321- );
322- slowParallax .play ();
304+ Consider a simple swipe-to-dismiss effect which follows the user swipe gesture and when finger lifts
305+ then continues to completion (either dismissed or returned to origin) with a curve that matches the
306+ swipe gesture's velocity. (See this [ example] ( https://twitter.com/kzzzf/status/917444054887124992 ) )
323307
324- const fastParallax = new WorkletAnimation (
325- ' parallax' ,
326- new KeyframeEffect (parallaxFastEl, [{' transform' : ' translateY(0)' }, {' transform' : ' translateY(' + - scrollRange + ' px)' }], scrollRange),
327- scrollTimeline,
328- {rate : 0.8 }
329- );
330- fastParallax .play ();
331- </script >
308+ With Animation Worklet, this can be modeled as a stateful animator which consumes both time and
309+ pointer events and have the following state machines:
332310
333- ```
311+ ![ SwipeToCompletionAnimation ] ( img/swipe-to-dismiss-state.png )
334312
335- parallax-animator.js:
336313
337- ``` js
338- // Inside AnimationWorkletGlobalScope
339- registerAnimator (' parallax' , class Parallax extends StatelessAnimator {
340- constructor (options ) {
341- this .rate_ = options .rate ;
314+ Here are the three main states:
315+
316+ 1 . Animation is idle, where it is ` paused ` so that it is not actively ticking
317+ 2 . As soon as the user touches down, the animation moves the target to follow the user touchpoint
318+ while staying ` paused ` (optionally calculate the movement velocity, and overall delta).
319+ 3 . As soon as the user lift their finger the animation will the switch to 'playing' so that it is
320+ ticked by time until it reaches its finished state. The final state may be decided on overall
321+ delta and velocity and the animation curve adapts to the movement velocity.
322+
323+ Note that while in (3), if the user touches down we go back to (2) which ensures responsiveness to
324+ user touch input.
325+
326+ To make this more concrete, here is how this may be implemented (assuming strawman proposed APIs for
327+ playback controls and also receiving pointer events). Note that all the state machine transitions and
328+ various state data (velocity, phase) and internal to the animator. Main thread only needs to provide
329+ appropriate keyframes that can used to translate the element on the viewport as appropriate (e.g.,
330+ ` Keyframes(target, {transform: ['translateX(-100vw)', 'translateX(100vw)']}) ` ).
331+
332+
333+ ``` javascript
334+ registerAnimator (' swipe-to-dismiss' , class SwipeAnimator extends StatefulAnimator {
335+ constructor (options , state = {velocity: 0 , phase: ' idle' }) {
336+ this .velocity = state .velocity ;
337+ this .phase = state .phase ;
338+
339+ if (phase == ' idle' ) {
340+ // Pause until we receive pointer events.
341+ this .pause ();
342+ }
343+
344+ // Assumes we have an API to receive pointer events for our target.
345+ this .addEventListener (" eventtargetadded" , (event ) => {
346+ for (type of [" pointerdown" , " pointermove" , " pointerup" ]) {
347+ event .target .addEventListener (type,onPointerEvent );
348+ }
349+ });
350+ }
351+
352+ onpointerevent (event ) {
353+ if (event .type == " pointerdown" || event .type == " pointermove" ) {
354+ this .phase = " follow_pointer" ;
355+ } else {
356+ this .phase = " animate_to_completion" ;
357+ // Also decide what is the completion phase (e.g., hide or show)
358+ }
359+
360+ this .pointer_position = event .screenX ;
361+
362+ // Allow the animation to play for *one* frame to react to the pointer event.
363+ this .play ();
342364 }
343365
344366 animate (currentTime , effect ) {
345- effect .localTime = currentTime * this .rate_ ;
367+ if (this .phase == " follow_pointer" ) {
368+ effect .localTime = position_curve (this .pointer_position );
369+ update_velocity (currentTime, this .pointer_position );
370+ // Pause, no need to produce frames until next pointer event.
371+ this .pause ();
372+ } else if (this .phase = " animate_to_completion" ) {
373+ effect .localTime = time_curve (currentTime, velocity);
374+
375+ if (effect .localTime == 0 || effect .localTime == effect .duration ) {
376+ // The animation is complete. Pause and become idle until next user interaction.
377+ this .phase = " idle" ;
378+ this .pause ();
379+ } else {
380+ // Continue producing frames based on time until we complete or the user interacts again.
381+ this .play ();
382+ }
383+ }
384+
385+ }
386+
387+ position_curve (x ) {
388+ // map finger position to local time so we follow user's touch.
389+ }
390+
391+ time_curve (time , velocity ) {
392+ // Map current time delta and given movement velocity to appropriate local time so that over
393+ // time we animate to a final position.
394+ }
395+
396+ update_velocity (time , x ) {
397+ this .velocity = (x - last_x) / (time - last_time);
398+ this .last_time = time;
399+ this .last_x = x;
400+ }
401+
402+ state () {
403+ return {
404+ phase: this .phase ,
405+ velocity: this .velocity
406+ }
346407 }
347408});
348409```
349410
411+ ``` javascript
412+
413+ await CSS .animationWorklet .addModule (' swipe-to-dismiss-animator.js' );
414+ const target = document .getElementById (' target' );
415+ const s2d = new WorkletAnimation (
416+ ' swipe-to-dismiss' ,
417+ new KeyframeEffect (target, {transform: [' translateX(-100vw)' , ' translateX(100vw)' ]}));
418+ s2d .play ();
419+ ```
420+
350421
351422# Why Extend Animation?
352423
@@ -465,7 +536,7 @@ registerAnimator('animation-with-local-state', class FoorAnimator extends Statef
465536 return {
466537 this .velocity ,
467538 this .acceleration
468- };
539+ }
469540 }
470541
471542 curve (velocity , accerlation , t ) {
@@ -476,27 +547,34 @@ registerAnimator('animation-with-local-state', class FoorAnimator extends Statef
476547
477548# Related Concepts
478549
479- The following concepts are not part of Animation Worklet specification but animation worklet is
480- designed to take advantage of them to enable a richer set of usecases.
550+ The following concepts are not part of Animation Worklet specification but Animation Worklet is
551+ designed to take advantage of them to enable a richer set of usecases. These are still in early
552+ stages of the standardization process so their API may change over time.
481553
482554## ScrollTimeline
483- [ ScrollTimeline] ( https://wicg.github.io/ scroll-animations/#scrolltimeline ) is a concept introduced in
555+ [ ScrollTimeline] [ scroll-timeline ] is a concept introduced in
484556scroll-linked animation proposal. It defines an animation timeline whose time value depends on
485557scroll position of a scroll container. ` ScrollTimeline ` can be used an an input timeline for
486558worklet animations and it is the intended mechanisms to give read access to scroll position.
487559
560+ We can later add additional properties to this timeline (e.g., scroll phase (active, inertial, overscroll),
561+ velocity, direction) that can further be used by Animation Worklet.
562+
488563## GroupEffect
489- [ GroupEffect] ( https://w3c.github.io/web-animations/level-2/#the-animationgroup-interfaces ) is a
490- concept introduced in Web Animation Level 2 specification. It provides a way to group multiple
491- effects in a tree structure. ` GroupEffect ` can be used as the output for worklet animations. It
492- makes it possible for worklet animation to drive effects spanning multiple elements.
493-
494- ** TODO** : At the moment, ` GroupEffect ` only supports just two different scheduling models (i.e.,
495- parallel, sequence). These models governs how the group effect time is translated to its children
496- effect times by modifying the child effect start time. Animation Worklet allows a much more
497- flexible scheduling model by making it possible to to set children effect's local time directly. In
498- other words we allow arbitrary start time for child effects. This is something that needs to be
499- added to level 2 spec.
564+
565+ [ GroupEffect] [ group-effect ] is a concept introduced in Web Animation Level 2 specification. It
566+ provides a way to group multiple effects in a tree structure. ` GroupEffect ` can be used as the
567+ output for Worklet Animations making it possible for it to drive complext effects spanning multiple
568+ elements. Also with some minor [ proposed changes] ( group-effect-changes ) to Group Effect timing
569+ model, Animation Worklet can enable creation of new custom sequencing models (e.g., with conditions
570+ and state).
571+
572+ ## Event Dispatching to Worker and Worklets
573+ [ Event Dispatching to Worker/Worklets] [ input-for-worker ] is a proposal in WICG which allows workers
574+ and worklets to passively receive DOM events and in particular Pointer Events. This can be
575+ benefitial to Animation Worklet as it provides an ergonomic and low latency way for Animation
576+ Worklet to receive pointer events thus enabling it to implement input driven animations more
577+ effectively.
500578
501579# WEBIDL
502580
@@ -508,6 +586,7 @@ the animation worklet scope.
508586 - A sequence of timelines, the first one of which is considered primary timeline and passed to
509587 ` Animation ` constructor.
510588
589+
511590``` webidl
512591
513592[Constructor (DOMString animatorName,
@@ -519,10 +598,6 @@ interface WorkletAnimation : Animation {
519598}
520599```
521600
522- ** TODO** : At the moment ` GroupEffect ` constructor requires a timing but this seems unnecessary for
523- ` WorkletAnimation ` where it should be possible to directly control individual child effect local
524- times. We need to bring this up with web-animation spec.
525-
526601` AnimationEffectReadOnly ` gets a writable ` localTime ` attribute which may be used to drive the
527602effect from the worklet global scope.
528603
@@ -545,3 +620,7 @@ the most recent version.
545620[ WA ] : https://drafts.csswg.org/web-animations/
546621[ animation ] : https://drafts.csswg.org/web-animations/#animations
547622[ worklet ] : https://drafts.css-houdini.org/worklets/#worklet-section
623+ [ input-for-worker ] : https://discourse.wicg.io/t/proposal-exposing-input-events-to-worker-threads/3479
624+ [ group-effect ] : https://w3c.github.io/web-animations/level-2/#the-animationgroup-interfaces
625+ [ group-effect-changes ] : https://github.com/yi-gu/group_effects
626+ [ scroll-timeline ] : https://wicg.github.io/scroll-animations/#scrolltimeline
0 commit comments