1. Introduction
- This section is not normative. -This document introduces a new primitive for creating scroll-linked and other high performance -procedural animations on the web. For details on the rationale and motivation see [explainer].
-The Animation Worklet API provides a method to create scripted animations that control a set -of animation effects. The API is designed to make it possible for user agents to run such -animations in their own dedicated thread to provide a degree of performance isolation from main -thread.
-1.1. Relationship to the Web Animations API
-Animations running inside an Animation Worklet execution context expose the Animation
interface from the Web Animations specification on the main javascript execution context. This means
-they can be controlled and inspected from main thread using many of the Web Animation APIs. However
-Animation Worklet animations follow a different timing model that enables them to be script-driven,
-stateful, and runnable in a parallel worklet execution context. As such Web Animation APIs that seek
-or alter the input time (reverse, finish, etc.) have different semantics for Animation Worklet
-animations.
2. Threading Model
- This section is not normative. -Animation Worklet is designed to be thread-agnostic. Rendering engines may create one or more -parallel worklet execution contexts separate from the main javascript execution context, e.g., on -their own dedicated threads. Rendering engines may then choose to assign Animation Worklet -animations to run in such contexts. Doing so allows Animation Worklet animations to avoid being -impacted by main thread jank.
-Rendering engines may wish to make a best-effort attempt to execute animate callbacks synchronously -with visual frame production to ensure smooth animation. However it is legal for rendering engines -to produce visual frames without blocking to receive animation updates from a worklet (i.e., letting -the effects slip behind). For example, this could occur when the animate function callback is -unable to complete before the frame deadline.
-We believe that scripted animations which are run in a parallel execution environment and which -limit themselves to animating properties which do not require the user agent to consult main thread -will have a much better chance of meeting the strict frame budgets required for smooth playback.
-If a Worklet Animation animation is executing in a parallel worklet execution context, the last -known state of its animation effects should be periodically synced back to the main javascript -execution context. The synchronization of effect values from the parallel worklet execution -context to the main javascript execution context must occur before running the animation -frame callbacks as part of the document lifecycle. Note that due to the asynchronous nature of -this animation model a script running in the main javascript execution context may see a stale value -when reading a target property that is being animated in a Worklet Animation, compared to the -value currently being used to produce the visual frame that is visible to the user. This is similar -to the effect of asynchronous scrolling when reading scroll offsets in the main javascript execution -context.
--
A simplified visualization of how animators running in a parallel execution environment can sync - their update to main thread while remaining in sync with visual frame production.
-3. Animation Worklet
- Animation Worklet is aWorklet
responsible for all classes related to custom
-animations. The worklet can be accessed via animationWorklet
attribute.
- The animationWorklet
's worklet global scope type is AnimationWorkletGlobalScope
.
AnimationWorkletGlobalScope
represents the global execution context of animationWorklet
.
-partial namespace CSS { - [SameObject ]readonly attribute Worklet ; -}; -
animationWorklet
[-Exposed =AnimationWorklet ,Global =AnimationWorklet ] -interface :
AnimationWorkletGlobalScope WorkletGlobalScope { -void (
registerAnimator DOMString ,
name VoidFunction ); -}; -
animatorCtor
-class FooAnimator{ - constructor( options) { -// Called when a new animator is instantiated. -} - animate( currentTime, effect) { -// Animation frame logic goes here. -} -} -
4. Animator Definition
- An animator definition is a struct which describes the author defined custom -animation as needed byAnimationWorkletGlobalScope
. It consists of:
- -
-
-
-
An animator name <ident>#.
- -
-
A class constructor which is a VoidFunction callback function type.
- -
-
An animate function which is a Function callback function type.
- -
-
A destroy function which is a Function callback function type.
-
4.1. Registering an Animator Definition
- AnAnimationWorkletGlobalScope
has a animator name to animator definition map.
-The map gets populated when registerAnimator(name, animatorCtorValue)
is called.
- When the registerAnimator(name, animatorCtorValue)
method is called in a AnimationWorkletGlobalScope
, the user agent must run the
-following steps:
-
-
-
-
If name is not a valid <ident>, throw a TypeError and abort all these -steps.
- -
-
If name exists as a key in the animator name to animator definition map, throw a NotSupportedError and abort all these steps.
- -
-
If the result of IsConstructor(animatorCtorValue) is false, throw a TypeError and abort all these steps.
- -
-
Let animatorCtor be the result of converting animatorCtorValue to the VoidFunction callback function type. If an exception is thrown, rethrow the -exception and abort all these steps.
- -
-
Let prototype be the result of Get(animatorCtorValue, "prototype").
- -
-
If the result of Type(prototype) is not Object, throw a TypeError and abort all these steps.
- -
-
Let animateValue be the result of Get(prototype, "animate").
- -
-
Let animate be the result of converting animateValue to the Function callback function type. If an exception is thrown, rethrow the exception and abort -all these steps.
- -
-
Let destroyValue be the result of Get(prototype, "onDestroy").
- -
-
Let destroy be the result of converting destroyValue to the Function callback function type. If an exception is thrown, rethrow the exception and abort -all these steps.
- -
-
Let definition be a new animator definition with:
--
-
-
-
animator name being name
- -
-
class constructor being animatorCtor
- -
-
animate function being animate
- -
-
destroy function being destroy
-
-
-
-
-
Add the key-value pair (name - definition) to the animator name to animator -definition map.
-
5. Animator Instance
-An animator instance is a struct which describes a fully realized custom animation
-instance in an AnimationWorkletGlobalScope
. It has a reference to an animator definition and owns the instance specific state such as animation effect and timelines. It consists of:
-
-
-
-
An animator name.
- - - -
-
-
An animator effect which is an animation effect.
- -
-
An animator current time which is the corresponding worklet animation’s current - time.
- -
-
An animator timeline which is a timeline.
- -
-
An animator attached timelines which is list of attached timelines
- -
-
An animator serialized options which is a serializable object.
-
5.1. Creating an Animator Instance
-Each animator instance lives in an AnimationWorkletGlobalScope
.
Each AnimationWorkletGlobalScope
has an animator instance set. The set is populated
-when the user agent constructs a new animator instance in the AnimationWorkletGlobalScope
scope. Each animator instance corresponds to a worklet animation in the document scope.
To create a new animator instance given a name, timeline, effect, serializedOptions, serializedState, and workletGlobalScope, the user agent must run the following steps:
--
-
-
-
Let the definition be the result of looking up name on the workletGlobalScope’s animator name to animator definition map.
-If definition does not exist abort the following steps.
- -
-
Let animatorCtor be the class constructor of definition.
- -
-
Let timelineList be a new list with timeline added to it.
- -
-
Let options be StructuredDeserialize(serializedOptions).
- -
-
Let state be StructuredDeserialize(serializedState).
- -
-
Let animatorInstance be the result of constructing animatorCtor with -[options, state as args. If an exception is thrown, rethrow the exception and abort all -these steps.
- -
-
Set the following on animatorInstance with:
--
-
-
-
animator name being name
- - - -
-
-
animator current time being unresolved
- -
-
animator effect being effect
- -
-
animator timeline being timeline
- -
-
animator attached timelines being timelineList
- -
-
animator serialized options being options
-
-
-
-
-
Add animatorInstance to workletGlobalScope’s animator instance set.
-
5.2. Running Animators
-When a user agent wants to produce a new animation frame, if for any animator instance the -associated animation requested flag is frame-requested then the the user agent must run animators for the current frame.
-Note: The user agent is not required to run animations on every visual frame. It is legal to defer - generating an animation frame until a later frame. This allow the user agent to - provide a different service level according to their policy.
-When the user agent wants to run animators in a given workletGlobalScope, it must iterate over all animator instances in the workletGlobalScope’s animator -instance set. For each such instance the user agent must perform the following steps:
--
-
-
-
Let animatorName be instance’s animator name
- -
-
Let the definition be the result of looking up animatorName on the workletGlobalScope’s animator name to animator definition map.
-If definition does not exist then abort the following steps.
- -
-
If the animation requested flag for instance is frame-current or the effect - belonging to the instance will not be visible within the visual viewport of the current - frame the user agent may abort all the following steps.
-Consider giving user agents permission to skip running animator instances to - throttle slow animators.
- -
-
Let animateFunction be definition’s animate function.
- -
-
Let currentTime be animator current time of instance.
- -
-
Let effect be animator effect of instance.
- -
-
Invoke animateFunction with arguments «currentTime, effect», - and with instance as the callback this value.
-
5.3. Removing an Animator Instance
-To remove an animator instance given instance and workletGlobalScope the user agent must run the following steps:
--
-
-
-
Remove instance from workletGlobalScope’s animator instance set.
-
5.4. Migrating an Animator Instance
-User agents are responsible for assigning an animator instance to a WorkletGlobalScope
.
-There can be many such WorkletGlobalScope
s, which may exist across different threads or
-processes. To give the most flexibility to user agents in this respect, we allow migration of an animator instance while it is running. The basic mechanism is to serialize the internal state
-of any author-defined effect, and restore it after migration.
To migrate an animator instance from one WorkletGlobalScope
to another, given instance, sourceWorkletGlobalScope, destinationWorkletGlobalScope, the user agent must run the following steps :
-
-
-
-
Let serializedState be undefined.
- -
-
Queue a task on sourceWorkletGlobalScope to run the following steps:
--
-
-
-
Let animatorName be instance’s animator name
- -
-
Let definition be the result of looking up animatorName on sourceWorkletGlobalScope’s animator name to animator definition map.
-If definition does not exist then abort the following steps.
- -
-
Let destroyFunction be the destroy function of definition.
- -
-
Invoke destroyFunction with instance as the callback this value and -let state be the result of the invocation. If any exception is thrown, rethrow the -exception and abort the following steps.
- -
-
Set serializedState to be the result of StructuredSerialize(state). -If any exception is thrown, then abort the following steps.
- -
-
Run the procedure to remove an animator instance given instance, and sourceWorkletGlobalScope.
-
-
-
-
-
Wait for the above task to complete. If the task is aborted, abort the following steps.
- -
-
Queue a task on destinationWorkletGlobalScope to run the following steps:
--
-
-
-
Run the procedure to create a new animator instance given:
--
-
-
-
The instance’s animator name as name.
- -
-
The instance’s animator timeline as timeline.
- -
-
The instance’s animator effect as effect.
- -
-
The instance’s animator serialized options as options.
- -
-
The serializedState as state.
- -
-
The destinationWorkletGlobalScope as workletGlobalScope.
-
-
-
-
-
5.5. Requesting Animation Frames
-Each animator instance has an associated animation requested flag. It must be -either frame-requested or frame-current. It is initially set to frame-current. Different circumstances can cause the animation requested flag to be -set to frame-requested. These include the following:
--
-
-
-
Changes in the current time of any timeline in the animator’s animator attached timelines
- -
-
Changes in the current time of the animator’s corresponding Worklet Animation
-
§5.2 Running Animators resets the animation requested flag on animators to frame-current.
-6. Web Animations Integration
-6.1. Worklet Animation
- Worklet animation is a kind of animation that delegates the animation playback to -an animator instance. It controls the lifetime and playback state of its corresponding animator instance. -Being an animation, worklet animation has an animation effect and a timeline. However unlike other animations the worklet animation’s current time does -not directly determine the animation effect’s local time (via its inherited time). -Instead the associated animator instance controls the animation effect’s local time directly. Note that this means that the timeline’s current time does not fully determine the -animation’s output.
-Worklet animation has the following properties in addition to the Animation
interface:
-
-
-
-
an animation animator name which identifies its animator definition.
- -
-
a serialized options which is serializable object that is used when -constructing a new animator instance.
-
-
The animation current time is input to the animator instance, which produces a local time value - for the animation effect. If the animator instance is running in a parallel global scope the - implementation may also choose to use the local time value to produce the final effect value and - update the visuals in parallel.
-6.2. Creating a Worklet Animation
-[-Constructor (DOMString , -
animatorName optional (AnimationEffect or sequence <AnimationEffect >)?=
effects null , -optional AnimationTimeline ?, -
timeline optional any )] -
options interface :
WorkletAnimation Animation { -readonly attribute DOMString ; -}; - -
animatorName
WorkletAnimation(animatorName, effects, timeline, options)
- Creates a new WorkletAnimation
object using the following procedure.
-
-
-
-
Let workletAnimation be a new
-WorkletAnimation
object. -
-
Run the procedure to set the timeline of an animation on workletAnimation passing timeline as the new timeline or, if a timeline argument is not provided, -passing the default document timeline of the
-Document
associated with theWindow
that is the current global object. -
-
Let effect be the result corresponding to the first matching condition from below.
--
-
- If effects is a
AnimationEffect
object, - -
-
Let effect be effects.
- - If effects is a list of
AnimationEffect
objects, - -
-
Let effect be a new
-WorkletGroupEffect
with its children set to effects. - Otherwise, -
-
-
Let effect be undefined.
-
- If effects is a
-
-
Run the procedure to set the target effect of an animation on workletAnimation passing effect as the new effect.
- -
-
Let serializedOptions be the result of StructuredSerialize(options). -Rethrow any exceptions.
- -
-
Set the serialized options of workletAnimation to serializedOptions.
- -
-
Set the animation animator name of workletAnimation to animatorName.
-
6.3. Worklet Animation timing model
-This section describes how worklet animation’s timing model differs from other animations.
-In addition to the existing conditions on when the animation is considered ready, a worklet animation is only considered ready when the following condition is also true:
--
-
-
-
the user agent has completed any setup required to create the worklet animation’s corresponding animator instance.
-
As described in §6.1 Worklet Animation, the worklet animation’s current time does
-not determine its animation effect’s local time. Instead the associated animator
-instance controls the animation effect’s local time directly. This means that the
-animation effect’s local time is controlled from a WorkletGlobalScope
which may be in a parallel
-execution context.
Here are a few implications of the above semantics:
--
-
-
-
Setting the current time or start time of a worklet animation does not -necessarily change its output, but may change the animation play state.
- -
-
Similarly, invoking
-finish()
or updating a worklet animation’s playback -rate will only change the animation play state and may not change the output. -
-
Querying the animation effect’s local time using
-getComputedTiming()
may return stale information, in the case where the animator instance is running in a -parallel execution context.
Come with appropriate mechanism’s for animator instance to get notified when its - animation currentTime is changing e.g., via reverse(), finish() or playbackRate change. So that - it can react appropriately. <https://github.com/wicg/animation-worklet/issues/63>
-6.4. Interaction with Animator Instances
-A worklet animation corresponds to at most one animator instance at any time, and may -have no current corresponding animator instance. The correspondance of an animator -instance for a worklet animation depends on the animation play state.
-To associate animator instance of worklet animation given workletAnimation, -the user agent must run the following steps:
--
-
-
-
If workletAnimation has a corresponding animator instance, abort the following steps.
- -
-
Let workletGlobalScope be the
-AnimationWorkletGlobalScope
associated with workletAnimation. -
-
Queue a task on workletGlobalScope to run the procedure to create a new animator - instance, passing:
--
-
-
-
The workletAnimation’s animation animator name as name.
- -
-
The workletAnimation’s timeline as timeline.
- -
-
The workletAnimation’s animation effect as effect.
- -
-
The workletAnimation’s serialized options as options.
- -
-
The workletGlobalScope as workletGlobalScope.
-
-
-
-
-
If the procedure was successful, set the resulting animator instance as corresponding to workletAnimation.
-
To disassociate animator instance of worklet animation given workletAnimation, the user age must run the following steps:
--
-
-
-
If workletAnimation does not have a corresponding animator instance, abort the -following steps.
- -
-
Let workletGlobalScope be the
-AnimationWorkletGlobalScope
associated with workletAnimation. -
-
Let animatorInstance be workletAnimation’s corresponding animator instance.
- -
-
Queue a task on the workletGlobalScope to run the procedure to remove an animator - instance, passing animatorInstance as instance and workletGlobalScope as - workletGlobalScope.
- -
-
Set workletAnimation as having no corresponding animator instance.
-
To set animator instance of worklet animation given workletAnimation, the user agent must run the following steps:
--
-
-
-
disassociate animator instance of worklet animation given workletAnimation.
- -
-
associate animator instance of worklet animation given workletAnimation.
-
When a given workletAnimation’s play state changes to pending, running, or paused, run the procedure to associate animator instance of worklet animation given workletAnimation.
-When a given workletAnimation’s play state changes to idle or finished, -run the procedure to disassociate animator instance of worklet animation given workletAnimation.
-When the procedure to set the target effect of an animation for a given workletAnimation is called, then set animator instance of worklet animation given workletAnimation.
-When the procedure to set the timeline of an animation for a given workletAnimation is called, then set animator instance of worklet animation given workletAnimation.
-6.5. Timeline Attachment
-Define semantics of attachment and detachment. <https://github.com/wicg/animation-worklet/issues/61>
-6.6. ScrollTimeline
-ScrollTimeline
is a new concept being proposed for addition to web animation API. It defines
-an animation timeline whose time value depends on the scroll position of a scroll container. Worklet animations can have a scroll timeline and thus drive their scripted effects based
-on a scroll offset.
- Note: Access to input: We are interested on exposing additional user input beside -scrolling (e.g., touch/pointer input) to these animations so that authors can create jank-free -input driven animations which are not really possible today. We are still trying to figure out the -right abstractions and mechanisms to do this.
-6.7. WorkletGroupEffect
-WorkletGroupEffect
is a type of group effect that allows its child effect’s local times to be mutated individually.
When a WorkletGroupEffect
is set as the animation effect of a WorkletAnimation
, the
-corresponding animator instance can directly control the child effects' local
-times. This allows a single worklet animation to coordinate multiple effects - see §7.2 Example 2: Twitter header. for an example of such a use-case.
-interface { -
WorkletGroupEffect sequence <AnimationEffect >(); -}; - -[
getChildren Exposed =AnimationWorklet ] -partial interface AnimationEffect { - // Intended for use inside Animation Worklet scope to drive the effect. -attribute double ; -}; -
localTime
To set the localTime
property on a effect to value t, the user agent should perform the
-action that corresponds to the first matching condition from the following:
-
-
- If the effect does not have a parent group, -
-
-
Set the effect local time to t.
- - If the effect has a parent group and it is of
WorkletGroupEffect
type, - -
-
Set the effect start time to (parent’s transformed time - t). Note this effectively set’s the effect’s local time to t.
- - Otherwise -
-
-
Throw an exception indicating that the child effect time can only be controlled by -its parent group.
-
The above interface exposes a conservative subset -of GroupEffect proposed as part of web-animation-2. Once that is available we -should switch to it. <https://github.com/w3c/csswg-drafts/issues/2071>
-6.8. Effect Stack and Composite Order
-As with other animations, worklet animations participate in the effect stack. A -worklet animation does not have a specific animation class which means it has the same -composite order as other Javascript created web animations.
-7. Examples
-7.1. Example 1: Hidey Bar.
- An example of header effect where a header is moved with scroll and as soon as finger is lifted it -animates fully to close or open position depending on its current position. --< div id = 'scrollingContainer' > -< div id = 'header' > Some header</ div > -< div > content</ div > -</ div > - -< script > -await CSS. animationWorklet. addModule( 'hidey-bar-animator.js' ); -const scrollTimeline= new ScrollTimeline( $scrollingContainer, { timeRange: 1000 }); -const documentTimeline= document. timeline; - -// Note we pass in two timelines in the options bag which allows the animation to read their -// currenTime values directly. -const animation= new WorkletAnimation( -'hidey-bar' , -new KeyFrameEffect( $header, -[{ transform: 'translateX(100px)' }, { transform: 'translateX(0px)' }], -{ duration: 1000 , iterations: 1 , fill: 'both' }]), - scrollTimeline, -{ scrollTimeline, documentTimeline}); - -animation. play(); -</ script > -
-// Inside AnimationWorkletGlobalScope - -registerAnimator( 'hidey-bar' , class { - constructor( options) { -this . scrollTimeline_= options. scrollTimeline; -this . documentTimeline_= options. documentTimeline; -} - - animate( currentTime, effect) { -const scroll= this . scrollTimeline_. currentTime; // [0, 100] -const time= this . documentTimeline_. currentTime; - -const activelyScrolling= this . scrollTimeline_. phase== 'active' ; - -let localTime; -if ( activelyScrolling) { -this . startTime_= undefined ; - localTime= scroll; -} else { -this . startTime_= this . startTime_|| time; -// Decide on close/open direction depending on how far we have scrolled the header -// This can even do more sophisticated animation curve by computing the scroll velocity and -// using it. -this . direction_= scroll>= 50 ? + 1 : - 1 ; - localTime= this . direction_* ( time- this . startTime_); -} - -// Drive the output effect by setting its local time. - effect. localTime= localTime; -} -}); -
This example uses a hypothetical "phase" property on timeline as a way to detect when user -is no longer actively scrolling. This is a reasonable thing to have on scroll timeline. A simple -fallback can emulate this by detecting when timeline time (i.e. scroll offset) has not changed in -the last few frames.
-7.2. Example 2: Twitter header.
- An example of twitter profile header effect where two elements (avatar, and header) are updated in -sync with scroll offset. -// In document scope. --< div id = 'scrollingContainer' > -< div id = 'header' style = 'height: 150px' ></ div > -< div id = 'avatar' >< img ></ div > -</ div > - -< script > -await CSS. animationWorklet. addModule( 'twitter-header-animator.js' ); -const animation= new WorkletAnimation( -'twitter-header' , -[ new KeyFrameEffect( $avatar, /* scales down as we scroll up */ -[{ transform: 'scale(1)' }, { transform: 'scale(0.5)' }], -{ duration: 1000 , iterations: 1 }), -new KeyFrameEffect( $header, /* loses transparency as we scroll up */ -[{ opacity: 0 }, { opacity: 0.8 }], -{ duration: 1000 , iterations: 1 })], -new ScrollTimeline( $scrollingContainer, { timeRange: 1000 , startScrollOffset: 0 , endScrollOffset: $header. clientHeight})); -animation. play(); - -// Since this animation is using a group effect, the same animation instance -// is accessible via different handles: $avatarEl.getAnimations()[0], $headerEl.getAnimations()[0] - -</ script > -
-// Inside AnimationWorkletGlobalScope. -registerAnimator( 'twitter-header' , class { - constructor( options) { -this . timing_= new CubicBezier( 'ease-out' ); -} - - clamp( value, min, max) { -return Math. min( Math. max( value, min), max); -} - - animate( currentTime, effect) { -const scroll= currentTime; // scroll is in [0, 1000] range - -// Drive the output group effect by setting its children local times individually. - effect. children[ 0 ]. localTime= scroll; - effect. children[ 1 ]. localTime= this . timing_( clamp( scroll, 0 , 500 )); -} -}); -
7.3. Example 3: Parallax backgrounds.
- A simple parallax background example. --< style > -. parallax { -position : fixed ; -top : 0 ; -left : 0 ; -opacity : 0.5 ; -} -</ style > -< div id = 'scrollingContainer' > -< div id = "slow" class = "parallax" ></ div > -< div id = "fast" class = "parallax" ></ div > -</ div > - -< script > -await CSS. animationWorklet. addModule( 'parallax-animator.js' ); -const scrollTimeline= new ScrollTimeline( $scrollingContainer, { timeRange: 1000 }); -const scrollRange= $scrollingContainer. scrollHeight- $scrollingContainer. clientHeight; - -const slowParallax= new WorkletAnimation( -'parallax' , -new KeyframeEffect( $parallax_slow, [{ 'transform' : 'translateY(0)' }, { 'transform' : 'translateY(' + - scrollRange+ 'px)' }], { duration: 1000 }), - scrollTimeline, -{ rate: 0.4 } -); -slowParallax. play(); - -const fastParallax= new WorkletAnimation( -'parallax' , -new KeyframeEffect( $parallax_fast, [{ 'transform' : 'translateY(0)' }, { 'transform' : 'translateY(' + - scrollRange+ 'px)' }], { duration: 1000 }), - scrollTimeline, -{ rate: 0.8 } -); -fastParallax. play(); -</ script > -
-// Inside AnimationWorkletGlobalScope. -registerAnimator( 'parallax' , class { - constructor( options) { -this . rate_= options. rate; -} - - animate( currentTime, effect) { - effect. localTime= currentTime* this . rate_; -} -}); -