CSS Animation Worklet API

Editor’s Draft,

More details about this document
This version:
https://drafts.css-houdini.org/css-animation-worklet-1/
Latest published version:
https://www.w3.org/TR/css-animation-worklet-1/
Feedback:
public-houdini@w3.org with subject line “[css-animation-worklet] … message topic …” (archives)
GitHub
Inline In Spec
Editors:

Abstract

Status of this document

This is a public copy of the editors’ draft. It is provided for discussion only and may change at any moment. Its publication here does not imply endorsement of its contents by W3C. Don’t cite this document other than as work in progress.

Please send feedback by filing issues in GitHub (preferred), including the spec code “css-animation-worklet” in the title, like this: “[css-animation-worklet] …summary of comment…”. All issues and comments are archived. Alternately, feedback can be sent to the (archived) public mailing list www-style@w3.org.

This document is governed by the 2 November 2021 W3C Process Document.

1. Introduction

This section is not normative.

This document introduces a new primitive that provides extensibility in web animations and enables high performance interactive procedural animations on the web. For details on the rationale and motivation see both [explainer] and [principles].

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

This section is not normative.

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 the same Web Animation APIs.

2. Animation Worklet

Animation Worklet is a Worklet responsible for all classes related to custom animations. The worklet can be accessed via animationWorklet attribute.
[Exposed=Window]
partial namespace CSS {
    [SameObject] readonly attribute Worklet animationWorklet;
};

The animationWorklet's worklet global scope type is AnimationWorkletGlobalScope.

AnimationWorkletGlobalScope represents the global execution context of animationWorklet.

[ Global=(Worklet,AnimationWorklet), Exposed=AnimationWorklet ]
interface AnimationWorkletGlobalScope : WorkletGlobalScope {
    undefined registerAnimator(DOMString name, AnimatorInstanceConstructor animatorCtor);
};

callback AnimatorInstanceConstructor = any (any options, optional any state);

3. Animator

An Animator represents a custom animation that is running inside AnimationWorkletGlobalScope. Each Animator is associated with an animation instance (of type WorkletAnimation) in the document and determines how that animation progresses its animation effect. The animate function contains the logic responsible for translating the animation current time into appropriate progress of the animation effect. An animator can only be instantiated by construction of a WorkletAnimation in the document.

Two animators types are supported: Stateless Animator and Stateful Animator each providing a different state management strategy.

3.1. Stateless Animator

A Stateless Animator is a type of animator does not depend on any local state either stored on the instance or global scope. Effectively, the animate function of an Stateless Animator can be treated as a pure function with the expectation that given the same input, it produces the same output.

This is how an stateless animator class should look.
class FooAnimator {
    constructor(options) {
        // Called when a new animator is instantiated.
    }
    animate(currentTime, effect) {
        // Animation frame logic goes here.
    }
}

Note: The statelessness allows animation worklet to perform optimization such as producing multiple animation frames in parallel, sharing a single animator instance for multiple animations, and performing very cheap teardown and setup. Using Stateless Animator is highly recommended to enable such optimizations.

3.2. Stateful Animator

A Stateful Animator is a type of animator that can have local state and animation worklet guarantees that it maintains this state as long as the stateful animator fulfills the contract required by its interface and as described following.

Animation worklet maintains a set of WorkletGlobalScopes which may exist across different threads or processes. Animation worklet may temporarily terminate a global scope (e.g., to preserve resources) or move a running animator instance across different global scopes (e.g., if its effect is mutable only in a certain thread). Animation worklet guarantees that a stateful animator instance’s state is maintained even if the instance is respawned in a different global scope.

The basic mechanism for maintaining the state is that the animation worklet snapshots the local state that is exposed via the state function and then reifies it so that it can be passed into the constructor when the animator instance is respawned at a later time in a potentially different global scope. The migrate an animator instance algorithm specifies this process in details.

A user-defined stateful animator is expected to fulfill the required contract which is that its state function returns an object representing its state that can be serialized using structured serialized algorithm and that it can also recreate its state given that same object passed to its constructor.

This is how a stateful animator class should look.
class BarAnimator {
    constructor(options, state) {
      // Called when a new animator is instantiated (either first time or after being respawned).
      this.currentVelocity  = state ? state.velocity : 0;
    }
    animate(currentTime, effect) {
        // Animation frame logic goes here and can rely on this.currentVelocity.
        this.currentVelocity += 0.1;
    }
    state() {
      // The returned object should be serializable using structured clonable algorithm.
      return {
        velocity: this.currentVelocity;
      }
    }
}

3.3. Animator Definition

An animator definition is a struct which describes the author defined custom animation logic. It consists of:

A stateful animator definition is an animator definition whose stateful flag is true.

A document animator definition is a struct which describes the information needed by the document about the author defined custom animation. It consists of:

3.4. Registering an Animator Definition

The document has a map of document animator definitions. The map gets populated when registerAnimator(name, animatorCtor) is called.

An AnimationWorkletGlobalScope has a map of animator definitions. The map gets populated when registerAnimator(name, animatorCtor) is called.

Note that to register a stateful animator definition it is simply enough for the registered class to have a state function.

When the registerAnimator(name, animatorCtor) method is called in a AnimationWorkletGlobalScope, the user agent must run the following steps:

  1. If name is not a valid <ident>, throw a TypeError and abort all these steps.

  2. Let animatorDefinitions be the AnimationWorkletGlobalScope's animator definitions map.

  3. If animatorDefinitions[name] exists, throw a NotSupportedError and abort all these steps.

  4. If the result of IsConstructor(animatorCtor) is false, throw a TypeError and abort all these steps.

  5. Let prototype be the result of Get(animatorCtor, "prototype").

  6. Let animateFuncValue be the result of Get(prototype, "animate").

  7. Let animateFunc be the result of converting animateFuncValue to the Function callback function type. If an exception is thrown, rethrow the exception and abort all these steps.

  8. Let stateFuncValue be the result of Get(prototype, "state").

  9. Let stateFunc be the result of converting stateFuncValue to the Function callback function type, If an exception is thrown, set stateful to be false, otherwise set stateful to be true and stateFunc to be undefined.

  10. Let definition be a new animator definition with:

  11. set the animatorDefinitions[name] to definition.

  12. Queue a task to run the following steps:

    1. Let documentAnimatorDefinitions be the associated document’s document animator definitions map.

    2. Let documentDefinition be a new document animator definition with:

    3. If documentAnimatorDefinitions[name] exists, run the following steps:

      1. Let existingDocumentDefinition be the result of get documentAnimatorDefinitions[name].

      2. If existingDocumentDefinition is "invalid", abort all these steps.

      3. If existingDocumentDefinition and documentDefinition are not equivalent, (that is their stateful flags are different), then:

        set documentAnimatorDefinitions[name] to "invalid".

        Log an error to the debugging console stating that the same class was registered with different stateful flag.

    4. Otherwise, set documentAnimatorDefinitions[name] to documentDefinition.

3.5. Animator Effect

A Animator Effect represents the underlying animation effect inside animation worklet.

It has a corresponding effect property which is a reference to the underlying animation effect. It also has corresponding properties for the following animation effect's properties:

Animator Effect is represented by the WorkletAnimationEffect interface inside AnimationWorkletGlobalScope.

[ Exposed=AnimationWorklet ]
interface WorkletAnimationEffect {
    EffectTiming         getTiming();
    ComputedEffectTiming getComputedTiming();
    attribute double? localTime;
};

Note: WorkletAnimationEffect is basically a restricted version of AnimationEffect interface which does not have updateTiming but additionally allows local time to be set.

getTiming()

Returns the specified timing properties using the corresponding properties.

getComputedTiming()

Returns the calculated timing properties using the corresponding properties.

localTime, of type double, nullable

Getting the attribute returns the corresponding local time. Setting the attribute updates the local time given this effect as effect and the attribute value as time:

  1. If the time is the same as effect’s local time then skip following steps.

  2. Set the effect’s local time to time.

  3. Set the effect’s animator instance’s sync requested flag to true.

4. Animator Instance

An animator instance is a struct which describes a fully realized custom animator in an AnimationWorkletGlobalScope. It has a reference to an animator definition and owns the instance specific state such as animation effect and timeline. It consists of:

A stateful animator instance is an animator instance whose corresponding definition is a stateful animator definition.

4.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:

  1. Let the definition be the result of looking up name on the workletGlobalScope’s animator definitions.

    If definition does not exist abort the following steps.

  2. Let animatorCtor be the class constructor of definition.

  3. Let options be StructuredDeserialize(serializedOptions).

  4. Let state be StructuredDeserialize(serializedState).

  5. Let animatorInstance be the result of constructing animatorCtor with «options, state» as arguments. If an exception is thrown, rethrow the exception and abort all these steps.

  6. Let animatorEffect be the result of constructing a WorkletAnimationEffect with its corresponding effect being effect.

  7. Set the following on animatorInstance with:

  8. Add animatorInstance to workletGlobalScope’s animator instance set.

4.2. Running Animators

When the user agent wants to produce a new animation frame, if for any animator instance the associated frame requested flag is true then the the user agent must run animators for the current frame in all its associated global scopes.

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 run the following steps:

  1. Iterate over all animator instances in the workletGlobalScope’s animator instance set. For each such animator the user agent must perform the following steps:

    1. Let animatorName be animator’s animator name

    2. Let the definition be the result of looking up animatorName on the workletGlobalScope’s animator definitions.

    If definition does not exist then abort the following steps.

    1. If the frame requested flag for animator is false or the effect belonging to the animator 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 individual animator instances to throttle slow animators.

    2. Let animateFunction be definition’s animate function.

    3. Let currentTime be animator current time of animator.

    4. Let effect be effect of animator.

    5. Invoke animateFunction with arguments «currentTime, effect», and with animator as the callback this value.

  2. If any animator instances in the workletGlobalScope’s animator instance set has its sync requested flag set to true then sync local times to document given workletGlobalScope.

Note: Although inefficient, it is legal for the user agent to run animators multiple times in the same frame.

should be explicit as to what happens if the animateFunction throws an exception. At least we should have wording that the localTime values of the effects are ignored to avoid incorrect partial updates.

4.3. Removing an Animator Instance

To remove an animator instance given animator and workletGlobalScope the user agent must run the following steps:

  1. Remove animator from workletGlobalScope’s animator instance set.

4.4. Migrating an Animator Instance

The migration process allows stateful animator instance to be migrated to a different AnimationWorkletGlobalScope without losing their local state.

To migrate an animator instance from one AnimationWorkletGlobalScope to another, given animator, sourceWorkletGlobalScope, destinationWorkletGlobalScope, the user agent must run the following steps :

  1. Let serializedState be undefined.

  2. Queue a task on sourceWorkletGlobalScope to run the following steps:

    1. Let animatorName be animator’s animator name

    2. Let definition be the result of looking up animatorName on sourceWorkletGlobalScope’s animator definitions.

      If definition does not exist then abort the following steps.

    3. Let stateful be the stateful flag of definition.

    4. If stateful is false then abort the following steps.

    5. Let stateFunction be definition’s state function.

    6. Let state be the result of Invoke stateFunction with animator as the callback this value. If any exception is thrown, rethrow the exception and abort the following steps.

    7. Set serializedState to be the result of StructuredSerialize(state). If any exception is thrown, then abort the following steps.

    8. Run the procedure to remove an animator instance given animator, and sourceWorkletGlobalScope.

  3. Wait for the above task to complete. If the task is aborted, abort the following steps.

  4. Queue a task on destinationWorkletGlobalScope to run the following steps:

    1. Run the procedure to create a new animator instance given:

If an animator state getter throws the user agent will remove the animator but does not recreate it. This effectively removes the animator instance.

4.5. Requesting Animation Frames

Each animator instance has an associated frame requested flag. It is initially set to false. Different circumstances can cause the frame requested flag to be set to true. These include the following:

Performing run animators resets the frame requested flag on animators to false.

5. Web Animations Integration

5.1. Worklet Animation

Worklet animation is a kind of animation that delegates animating its animation effect 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:

The existence of corresponding animator instance for a worklet animation depends on the animation play state. See § 5.4 Web Animations Overrides for details on when and this correspondence changes.

[Exposed=Window]
interface WorkletAnimation : Animation {
        constructor(DOMString animatorName,
              optional (AnimationEffect or sequence<AnimationEffect>)? effects = null,
              optional AnimationTimeline? timeline,
              optional any options);
        readonly attribute DOMString animatorName;
};
Overview of the WorkletAnimation timing model.
Overview of the worklet animation timing model.
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 animation output and update the visuals in parallel.

5.2. Creating a Worklet Animation

WorkletAnimation(animatorName, effects, timeline, options)

Creates a new WorkletAnimation object using the following procedure:

  1. Let documentAnimatorDefinitions be the associated document’s document animator definitions map.

  2. If documentAnimatorDefinitions[animatorName] does not exists, throw an TypeError and abort the following steps.

  3. If documentAnimatorDefinitions[animatorName] is "invalid", throw an TypeError and abort the following steps.

  4. Let workletAnimation be a new WorkletAnimation object.

  5. 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 the Window that is the current global object.

  6. 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.

  7. Let serializedOptions be the result of StructuredSerialize(options). Rethrow any exceptions.

  8. Set the serialized options of workletAnimation to serializedOptions.

  9. Set the animation animator name of workletAnimation to animatorName.

  10. Run the procedure to set the target effect of an animation on workletAnimation passing effect as the new effect. Note that this may trigger action to set animator instance of worklet animation. See § 5.4 Web Animations Overrides for more details.

5.3. Worklet Animation Timing and Sync Model

This section describes how worklet animation’s timing model differs from other animations.

As described in § 5.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:

If a Worklet Animation animation is executing in a parallel worklet execution context, the last known state of its Animator 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 by 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.

To sync local times to document for a given workletGlobalScope the user agent must perform the action that corresponds to the first matching condition from the following:

If the workletGlobalScope is not running in a parallel execution context

perform the following steps immediately:

If the workletGlobalScope is running in a parallel execution context

queue a task to run the following steps before running the animation frame callbacks as part of the document lifecycle:

  1. Iterate over all animator instances in the animation worklet’s global scope animator instance set. For each such animator perform the following steps:

    1. If animator’s sync requested flag is false skip the rest of the steps.

    2. Let animatorEffect be animator’s effect.

    3. Let effect be animatorEffect’s corresponding effect.

    4. Set effect’s local time to animatorEffect’s local time.

    5. Set animator’s sync requested flag to false.

To sync animation timings to worklet for a given workletAnimation the user agent must perform the following steps:

  1. If workletAnimation does not have a corresponding animator instance, abort the following steps.

  2. Let animator be workletAnimation’s corresponding animator instance.

  3. Let workletGlobalScope be the AnimationWorkletGlobalScope associated with workletAnimation.

  4. If the workletGlobalScope is not running in a parallel execution context

    perform the following steps immediately.

    If the workletGlobalScope is running in a parallel execution context

    queue a task to run the following steps:

    1. Set animator’s animator current time to workletAnimation’s current time

    2. Let animatorEffect be animator’s effect.

    3. Let effect be animatorEffect’s corresponding effect.

    4. Set the following properties on animatorEffect to be the same as effect:

Note: Notice that the local time is not synced from the document to animation worklet.

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. [Issue #811]

5.4. Web Animations Overrides

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:

When a given worklet animation’s play state changes from idle to finished, running, or paused, run the procedure to associate animator instance of worklet animation given the worket animation as workletAnimation.

When a given worklet animation’s play state changes from finished, running or paused to idle, run the procedure to disassociate animator instance of worklet animationgiven the worklet animation as workletAnimation.

When a given worklet animation’s replace state changes from active to either persisted or removed run the procedure to [=disassociate animator instance of worklet animation]= given the worklet animation as workletAnimation.

In web-animation play state is updated before the actual change in particular some operations such as play() are asynchronous. We should really invoke these Animator related operation after the appropriate animation operation is complete instead of when play state has changed. This will require either finding (or introducing) q new hook in web animation or having override for each such async operation.

When the procedure to set the target effect of an animation for a given worklet animation is called, then set animator instance of worklet animation given the worklet animation as 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 the worklet animation as workletAnimation.

When the procedure to set the current time or set the start time for a given worklet animation is called, then sync animation timings to worklet given the worklet animation as workletAnimation.

When the procedure to update the timing properties of an animation effect for a given effect is called and that effect is owned be a worklet animation, then sync animation timings to worklet given that worklet animation as workletAnimation.

To associate animator instance of worklet animation given workletAnimation, the user agent must run the following steps:

  1. If workletAnimation has a corresponding animator instance, abort the following steps.

  2. Let workletGlobalScope be the AnimationWorkletGlobalScope associated with workletAnimation.

  3. Queue a task on workletGlobalScope to run the procedure to create a new animator instance, passing:

  4. If the procedure was successful, set the resulting animator instance to be the corresponding animator instance of workletAnimation.

To disassociate animator instance of worklet animation given workletAnimation, the user age must run the following steps:

  1. If workletAnimation does not have a corresponding animator instance, abort the following steps.

  2. Let workletGlobalScope be the AnimationWorkletGlobalScope associated with workletAnimation.

  3. Let animatorInstance be workletAnimation’s corresponding animator instance.

  4. Queue a task on the workletGlobalScope to run the procedure to remove an animator instance, passing animatorInstance as instance and workletGlobalScope as workletGlobalScope.

  5. Set workletAnimation’s corresponding animator instance as undefined.

To set animator instance of worklet animation given workletAnimation, the user agent must run the following steps:

  1. disassociate animator instance of worklet animation given workletAnimation.

  2. associate animator instance of worklet animation given workletAnimation.

5.5. 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.

6.1. Worklet Group Effect

This section is not normative.

Group effect is a type of animation effect that enbales multiple child animation effects to be animated together as a group.

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 worklet animation, its corresponding animator instance can directly control the child effects' local times. This allows a single worklet animation to coordinate multiple effects - see § 9.2 Example 2: Twitter header. for an example of such a use-case.

[Exposed=AnimationWorklet]
interface WorkletGroupEffect {
  sequence<WorkletAnimationEffect> getChildren();
};

The above interface exposes a conservative subset of GroupEffect proposed as part of web-animation-2. We should instead move this into a delta spec against the web-animation. [Issue #w3c/csswg-drafts#2071]

Note: Group Effect is currently an experimental feature and not well specified in web animations. So the concept of WorkletGroupEffect may change dramatically as Group Effect get specified. See https://github.com/yi-gu/group_effect/blob/master/README.md for more details.

6.2. ScrollTimeline

This section is not normative.

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.

7. Security Considerations

There are no known security issues introduced by these features.

8. Privacy Considerations

There are no known privacy issues introduced by these features.

9. Examples

9.1. Example 1: Spring timing.

Here we use Animation Worklet to create animation with a custom spring timing.
<div id='target'></div>

<script>
await CSS.animationWorklet.addModule('spring-animator.js');
targetEl = document.getElementById('target');

const effect = new KeyframeEffect(
  targetEl,
  {transform: ['translateX(0)', 'translateX(50vw)']},
  {duration: 1000}
);
const animation = new WorkletAnimation('spring', effect, document.timeline, {k: 2, ratio: 0.7});
animation.play();
</script>
registerAnimator('spring', class SpringAnimator {
  constructor(options = {k: 1, ratio: 0.5}) {
    this.timing = createSpring(options.k, options.ratio);
  }

  animate(currentTime, effect) {
    let delta = this.timing(currentTime);
    // scale this by target duration
    delta = delta * (effect.getTimings().duration / 2);
    effect.localTime = delta;
    // TODO: Provide a method for animate to mark animation as finished once
    // spring simulation is complete, e.g., this.finish()
    // See issue https://github.com/w3c/css-houdini-drafts/issues/808
  }
});

function createSpring(springConstant, ratio) {
  // Normalize mass and distance to 1 and assume a reasonable init velocit
  // but these can also become options to this animator.
  const velocity = 0.2;
  const mass = 1;
  const distance = 1;

  // Keep ratio < 1 to ensure it is under-damped.
  ratio = Math.min(ratio, 1 - 1e-5);

  const damping = ratio * 2.0 * Math.sqrt(springConstant);
  const w = Math.sqrt(4.0 * springConstant - damping * damping) / (2.0 * mass);
  const r = -(damping / 2.0);
  const c1 = distance;
  const c2 = (velocity - r * distance) / w;

  // return a value in [0..distance]
  return function springTiming(timeMs) {
    const time = timeMs / 1000; // in seconds
    const result = Math.pow(Math.E, r * time) *
                  (c1 * Math.cos(w * time) + c2 * Math.sin(w * time));
    return distance - result;
  }
}

9.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 with an additional feature where avatar can have additional physic based movement based on the velocity and acceleration of the scrolling.
<div id='scrollingContainer'>
  <div id='header' style='height: 150px'></div>
  <div id='avatar'><img></div>
</div>

// In document scope.
<script>
const headerEl = document.getElementById('header');
const avatarEl = document.getElementById('avatar');
const scrollingContainerEl = document.getElementById('scrollingContainer');


const scrollTimeline  = new ScrollTimeline({
  scrollSource: scrollingContainerEl,
  orientation: 'block',
  timeRange: 1000,
  startScrollOffset: 0,
  endScrollOffset: headerEl.clientHeight
});

const effects = [
    /* avatar scales down as we scroll up */
    new KeyframeEffect(avatarEl,
                    {transform: ['scale(1)', 'scale(0.5)']},
                    {duration: scrollTimeline.timeRange}),
    /* header loses transparency as we scroll up */
    new KeyframeEffect(headerEl,
                    {opacity: [0, 0.8]},
                    {duration: scrollTimeline.timeRange})
];

await CSS.animationWorklet.addModule('twitter-header-animator.js');
const animation = new WorkletAnimation('twitter-header', effects, scrollTimeline);

animation.play();
</script>
// Inside AnimationWorkletGlobalScope.
registerAnimator('twitter-header', class HeaderAnimator {
  constructor(options, state = {velocity: 0, acceleration: 0}) {
    // `state` is either undefined (first time) or it is the previous state (after an animator
    // is migrated between global scopes).
    this.velocity = state.velocity;
    this.acceleration = state.acceleration;
  }


  animate(currentTime, effect) {
    const scroll = currentTime;  // scroll is in [0, 1000] range

    if (this.prevScroll) {
      this.velocity = scroll - this.prevScroll;
      this.acceleration = this.velocity - this.prevVelocity;
    }
    this.prevScroll = scroll;
    this.prevVelocity = velocity;


    // Drive the output group effect by setting its children local times individually.
    effect.children[0].localTime = scroll;

    effect.children[1].localTime = curve(velocity, acceleration, scroll);
  }

  state() {
    // Invoked before any migration attempts. The returned object must be structure clonable
    // and will be passed to constructor to help animator restore its state after migration to the
    // new scope.
    return {
      this.velocity,
      this.acceleration
    }
  }

});

curve(scroll, velocity, acceleration) {

 return /* compute an return a physical movement curve based on scroll position, and per frame
           velocity and acceleration. */ ;
}

9.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 parallaxSlowEl = document.getElementById('slow');
const parallaxFastEl = document.getElementById('fast');
const scrollingContainerEl = document.getElementById('scrollingContainer');

const scrollTimeline = new ScrollTimeline({
  scrollSource: scrollingContainerEl,
  orientation: 'block',
  timeRange: 1000
});
const scrollRange = scrollingContainerEl.scrollHeight - scrollingContainerEl.clientHeight;

const slowParallax = new WorkletAnimation(
    'parallax',
    new KeyframeEffect(parallaxSlowEl,
        {'transform': ['translateY(0)', 'translateY(' + -scrollRange + 'px)']},
        {duration: scrollTimeline.timeRange}),
    scrollTimeline,
    {rate : 0.4}
);
slowParallax.play();

const fastParallax = new WorkletAnimation(
    'parallax',
    new KeyframeEffect(parallaxFastEl,
        {'transform': ['translateY(0)', 'translateY(' + -scrollRange + 'px)']},
        {duration: scrollTimeline.timeRange}),
    scrollTimeline,
    {rate : 0.8}
);
fastParallax.play();
</script>
// Inside AnimationWorkletGlobalScope.
registerAnimator('parallax', class ParallaxAnimator {
  constructor(options) {
    this.rate_ = options.rate;
  }

  animate(currentTime, effect) {
    effect.localTime = currentTime * this.rate_;
  }
});

Conformance

Document conventions

Conformance requirements are expressed with a combination of descriptive assertions and RFC 2119 terminology. The key words “MUST”, “MUST NOT”, “REQUIRED”, “SHALL”, “SHALL NOT”, “SHOULD”, “SHOULD NOT”, “RECOMMENDED”, “MAY”, and “OPTIONAL” in the normative parts of this document are to be interpreted as described in RFC 2119. However, for readability, these words do not appear in all uppercase letters in this specification.

All of the text of this specification is normative except sections explicitly marked as non-normative, examples, and notes. [RFC2119]

Examples in this specification are introduced with the words “for example” or are set apart from the normative text with class="example", like this:

This is an example of an informative example.

Informative notes begin with the word “Note” and are set apart from the normative text with class="note", like this:

Note, this is an informative note.

Advisements are normative sections styled to evoke special attention and are set apart from other normative text with <strong class="advisement">, like this: UAs MUST provide an accessible alternative.

Conformance classes

Conformance to this specification is defined for three conformance classes:

style sheet
A CSS style sheet.
renderer
A UA that interprets the semantics of a style sheet and renders documents that use them.
authoring tool
A UA that writes a style sheet.

A style sheet is conformant to this specification if all of its statements that use syntax defined in this module are valid according to the generic CSS grammar and the individual grammars of each feature defined in this module.

A renderer is conformant to this specification if, in addition to interpreting the style sheet as defined by the appropriate specifications, it supports all the features defined by this specification by parsing them correctly and rendering the document accordingly. However, the inability of a UA to correctly render a document due to limitations of the device does not make the UA non-conformant. (For example, a UA is not required to render color on a monochrome monitor.)

An authoring tool is conformant to this specification if it writes style sheets that are syntactically correct according to the generic CSS grammar and the individual grammars of each feature in this module, and meet all other conformance requirements of style sheets as described in this module.

Partial implementations

So that authors can exploit the forward-compatible parsing rules to assign fallback values, CSS renderers must treat as invalid (and ignore as appropriate) any at-rules, properties, property values, keywords, and other syntactic constructs for which they have no usable level of support. In particular, user agents must not selectively ignore unsupported component values and honor supported values in a single multi-value property declaration: if any value is considered invalid (as unsupported values must be), CSS requires that the entire declaration be ignored.

Implementations of Unstable and Proprietary Features

To avoid clashes with future stable CSS features, the CSSWG recommends following best practices for the implementation of unstable features and proprietary extensions to CSS.

Non-experimental implementations

Once a specification reaches the Candidate Recommendation stage, non-experimental implementations are possible, and implementors should release an unprefixed implementation of any CR-level feature they can demonstrate to be correctly implemented according to spec.

To establish and maintain the interoperability of CSS across implementations, the CSS Working Group requests that non-experimental CSS renderers submit an implementation report (and, if necessary, the testcases used for that implementation report) to the W3C before releasing an unprefixed implementation of any CSS features. Testcases submitted to W3C are subject to review and correction by the CSS Working Group.

Further information on submitting testcases and implementation reports can be found from on the CSS Working Group’s website at http://www.w3.org/Style/CSS/Test/. Questions should be directed to the public-css-testsuite@w3.org mailing list.

Index

Terms defined by this specification

Terms defined by reference

References

Normative References

[CSS-TRANSITIONS-1]
David Baron; et al. CSS Transitions. URL: https://drafts.csswg.org/css-transitions/
[CSS-VALUES-4]
Tab Atkins Jr.; Elika Etemad. CSS Values and Units Module Level 4. URL: https://drafts.csswg.org/css-values-4/
[CSSOM-1]
Daniel Glazman; Emilio Cobos Álvarez. CSS Object Model (CSSOM). URL: https://drafts.csswg.org/cssom/
[DOM]
Anne van Kesteren. DOM Standard. Living Standard. URL: https://dom.spec.whatwg.org/
[HTML]
Anne van Kesteren; et al. HTML Standard. Living Standard. URL: https://html.spec.whatwg.org/multipage/
[INFRA]
Anne van Kesteren; Domenic Denicola. Infra Standard. Living Standard. URL: https://infra.spec.whatwg.org/
[RFC2119]
S. Bradner. Key words for use in RFCs to Indicate Requirement Levels. March 1997. Best Current Practice. URL: https://datatracker.ietf.org/doc/html/rfc2119
[SCROLL-ANIMATIONS]
Scroll-linked Animations. cg-draft. URL: https://wicg.github.io/scroll-animations/
[WEB-ANIMATIONS-1]
Brian Birtles; et al. Web Animations. URL: https://drafts.csswg.org/web-animations-1/
[WEB-ANIMATIONS-2]
Web Animations Module Level 2 URL: https://drafts.csswg.org/web-animations-2/
[WEBIDL]
Edgar Chen; Timothy Gu. Web IDL Standard. Living Standard. URL: https://webidl.spec.whatwg.org/

Informative References

[EXPLAINER]
Animation Worklet Explainer. URL: https://github.com/w3c/css-houdini-drafts/blob/master/css-animation-worklet-1/README.md
[PRINCIPLES]
Animation Worklet Design Principles and Goals. URL: https://github.com/w3c/css-houdini-drafts/blob/master/css-animation-worklet-1/principles.md

IDL Index

[Exposed=Window]
partial namespace CSS {
    [SameObject] readonly attribute Worklet animationWorklet;
};

[ Global=(Worklet,AnimationWorklet), Exposed=AnimationWorklet ]
interface AnimationWorkletGlobalScope : WorkletGlobalScope {
    undefined registerAnimator(DOMString name, AnimatorInstanceConstructor animatorCtor);
};

callback AnimatorInstanceConstructor = any (any options, optional any state);

[ Exposed=AnimationWorklet ]
interface WorkletAnimationEffect {
    EffectTiming         getTiming();
    ComputedEffectTiming getComputedTiming();
    attribute double? localTime;
};


[Exposed=Window]
interface WorkletAnimation : Animation {
        constructor(DOMString animatorName,
              optional (AnimationEffect or sequence<AnimationEffect>)? effects = null,
              optional AnimationTimeline? timeline,
              optional any options);
        readonly attribute DOMString animatorName;
};

[Exposed=AnimationWorklet]
interface WorkletGroupEffect {
  sequence<WorkletAnimationEffect> getChildren();
};

Issues Index

Consider giving user agents permission to skip running individual animator instances to throttle slow animators.
should be explicit as to what happens if the animateFunction throws an exception. At least we should have wording that the localTime values of the effects are ignored to avoid incorrect partial updates.
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. [Issue #811]
In web-animation play state is updated before the actual change in particular some operations such as play() are asynchronous. We should really invoke these Animator related operation after the appropriate animation operation is complete instead of when play state has changed. This will require either finding (or introducing) q new hook in web animation or having override for each such async operation.
The above interface exposes a conservative subset of GroupEffect proposed as part of web-animation-2. We should instead move this into a delta spec against the web-animation. [Issue #w3c/csswg-drafts#2071]