├── CONTRIBUTING.md ├── LICENSE.md ├── Makefile ├── README.md ├── WIP.md ├── img ├── AnimationWorklet-threading-model.svg └── WorkletAnimation-timing-model.svg ├── index.bs ├── index.html └── w3c.json /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Web Platform Incubator Community Group 2 | 3 | This repository is being used for work in the Web Platform Incubator Community Group, governed by the [W3C Community License 4 | Agreement (CLA)](http://www.w3.org/community/about/agreements/cla/). To contribute, you must join 5 | the CG. 6 | 7 | If you are not the sole contributor to a contribution (pull request), please identify all 8 | contributors in the pull request's body or in subsequent comments. 9 | 10 | To add a contributor (other than yourself, that's automatic), mark them one per line as follows: 11 | 12 | ``` 13 | +@github_username 14 | ``` 15 | 16 | If you added a contributor by mistake, you can remove them in a comment with: 17 | 18 | ``` 19 | -@github_username 20 | ``` 21 | 22 | If you are making a pull request on behalf of someone else but you had no part in designing the 23 | feature, you can remove yourself with the above syntax. 24 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | All Reports in this Repository are licensed by Contributors under the 2 | [W3C Software and Document 3 | License](http://www.w3.org/Consortium/Legal/2015/copyright-software-and-document). Contributions to 4 | Specifications are made under the [W3C CLA](https://www.w3.org/community/about/agreements/cla/). 5 | 6 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # $Id: Makefile,v 1.5 2008/02/06 14:05:15 mike Exp $ 2 | # 3 | # FIXME: New documentation needed. 4 | # 5 | # Use "make REMOTE=1" to use remote bikeshed 6 | 7 | SOURCEFILE=index.bs 8 | OUTPUTFILE=index.html 9 | PREPROCESSOR=bikeshed.py 10 | REMOTE_PREPROCESSOR_URL=https://api.csswg.org/bikeshed/ 11 | 12 | all: $(OUTPUTFILE) 13 | 14 | $(OUTPUTFILE): $(SOURCEFILE) 15 | ifneq (,$(REMOTE)) 16 | curl $(REMOTE_PREPROCESSOR_URL) -F file=@$(SOURCEFILE) > "$@" 17 | else 18 | $(PREPROCESSOR) -f spec "$<" "$@" 19 | endif 20 | 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # New Home! 2 | 3 | This repository has been merged into [css-houdini-drafts](https://github.com/w3c/css-houdini-drafts) repository of [CSS-TAG Houdini Task Force](https://github.com/w3c/css-houdini-drafts/wiki). 4 | 5 | Please file issues and send PRs there. 6 | 7 | * [Draft Specifications](https://drafts.css-houdini.org/css-animationworklet/) 8 | * [Issues](https://github.com/w3c/css-houdini-drafts/labels/css-workletanimation-1) -------------------------------------------------------------------------------- /WIP.md: -------------------------------------------------------------------------------- 1 | # Open API Questions 2 | --- 3 | 4 | 5 | ## Creation/Registration timing 6 | 7 | What should happen if an animation is created before the animator is registered? 8 | 9 | ## Timelines 10 | 11 | * observe-only timelines? i.e., have access to timeline but the animate is not triggered when its 12 | value changes. 13 | 14 | * Should we have a Timeline.currentTime and Timeline.localTime, where the latter is 15 | the former but offset by startTime & scaled by playbackRate? 16 | 17 | * Access to the actual scroll position in the ScrollTimeline 18 | 19 | * Access to scroll phase (inactive, active, inertial etc.) 20 | 21 | 22 | ## Updating Elements 23 | 24 | For some effects we need to be able to add new participating elements without 25 | restarting the effect. Here is an initial idea on how this can work. 26 | 27 | ```js 28 | // Effects and data can change after some time. 29 | // We might want to break this out into separate functions or optional 30 | // updates so you can just update options or just effects and options 31 | // without having to pass other parameters again. 32 | anim.update({ 33 | [ /* new list of effects? */], 34 | [ /* new list of timelines */], 35 | {/* options */} 36 | }); 37 | ``` 38 | 39 | ```js 40 | // In worklet scope 41 | class MyAnimator{ 42 | update(options) { 43 | // this is a V2 concept, 44 | } 45 | } 46 | ``` 47 | 48 | ## CSS Notation 49 | 50 | We are not proposing including this in the initial spec, but including some 51 | preliminary thoughts here so that we can keep the eventual declarative CSS 52 | specification in mind. 53 | 54 | index.html: 55 | ```html 56 | <-- animator instance is declared here, with its timelines --> 57 |
58 | <-- effect timing is declared here and assigned to above animator --> 59 |
60 |
61 |
62 |
63 | ``` 64 | 65 | style.css: 66 | ```css 67 | #main { 68 | animation: worklet('twitter-header') 69 | animation-timeline: scroll(#scroller_element.....) /* https://wicg.github.io/scroll-animations/#animation-timeline */ 70 | } 71 | 72 | /* These are descendants of the animation */ 73 | #main .header { 74 | animation-group: 'twitter-header' 'header' #... 75 | /* This syntax should be similar to what the plan is for Web Animation Group Effects */ 76 | } 77 | 78 | #main .avatar { 79 | animation-group: 'twitter-header' 'avatar' #... 80 | } 81 | ``` 82 | 83 | This is equivalent to calling: 84 | 85 | ```js 86 | new WorkletAnimation('twitter-header', 87 | [ 88 | new KeyFrameEffect(.header[0], [], {}), 89 | new KeyFrameEffect(.avatar, [], {}), 90 | new KeyFrameEffect(.header[1], [], {}), 91 | ], 92 | [new ScrollingTimeline(#selector, {...})], 93 | { elements: [ 94 | /* This is admittedly a bit magical. */ 95 | {'name': 'header'}, 96 | {'name': 'avatar'}, 97 | {'name': 'header'}, 98 | ]} 99 | ).play(); 100 | 101 | ``` 102 | 103 | Adding new elements that match the selector will be equivalent to invoking `update`. 104 | -------------------------------------------------------------------------------- /img/AnimationWorklet-threading-model.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /img/WorkletAnimation-timing-model.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /index.bs: -------------------------------------------------------------------------------- 1 | 15 | 16 | 20 | 21 |
 22 | urlPrefix: https://heycam.github.io/webidl/; type: dfn;
 23 |     text: NotSupportedError
 24 |     urlPrefix: #dfn-;
 25 |         text: callback this value
 26 |         text: exception
 27 |         text: throw
 28 |         url: throw; text: thrown
 29 |     urlPrefix: #;
 30 |         url: Function; text: Function
 31 |         url: VoidFunction; text: VoidFunction
 32 |     url: invoke-a-callback-function; text: Invoke
 33 |     url: construct-a-callback-function; text: constructing
 34 |     url: es-type-mapping; text: converting
 35 | urlPrefix: https://html.spec.whatwg.org/#; type: dfn;
 36 |     url: run-the-animation-frame-callbacks; text: running the animation frame callbacks
 37 | urlPrefix: http://w3c.github.io/html/infrastructure.html#; type: dfn;
 38 |     text: structuredserialize
 39 |     text: structureddeserialize
 40 | urlPrefix: https://www.w3.org/TR/css3-transitions/#; type: dfn;
 41 |     text: animatable properties
 42 | urlPrefix: https://w3c.github.io/web-animations/#; type: dfn;
 43 |     url: the-documents-default-timeline; text: default document timeline
 44 |     url: concept-animation; text: animation
 45 |     text: effect value
 46 |     text: effect stack
 47 |     text: target property
 48 |     text: timeline
 49 |     text: animation effect
 50 |     text: current time
 51 |     text: local time
 52 |     text: inherited time
 53 |     text: ready
 54 |     text: play state
 55 |     text: playback rate
 56 |     text: set the target effect of an animation
 57 |     text: set the timeline of an animation
 58 |     text: finished
 59 |     text: idle
 60 |     text: paused
 61 |     text: pending
 62 |     text: running
 63 |     text: composite operation
 64 |     text: animation class
 65 | urlPrefix: https://w3c.github.io/web-animations/level-2/#;
 66 |     type: dfn;
 67 |         text: group effect
 68 |         text: child effect
 69 | urlPrefix: https://tc39.github.io/ecma262/#sec-; type: dfn;
 70 |     text: IsCallable
 71 |     text: IsConstructor
 72 |     text: HasProperty
 73 |     url: ecmascript-data-types-and-values; text: Type
 74 |     url: map-objects; text:map object
 75 |     url: get-o-p; text: Get
 76 |     url: set-o-p-v-throw; text: Set
 77 |     urlPrefix: native-error-types-used-in-this-standard-
 78 |         text: TypeError
 79 | urlPrefix: https://www.w3.org/TR/hr-time-2/#dom-; type: dfn
 80 |     text: DOMHighResTimeStamp
 81 | urlPrefix: https://wicg.github.io/scroll-animations/#; type: interface
 82 |     url: scrolltimeline; text: ScrollTimeline
 83 |     url: dictdef-scrolltimelineoptions; text: ScrollTimelineOptions
 84 |     url: dom-scrolltimeline-scrollsource; text: scrollSource
 85 | urlPrefix: https://wicg.github.io/scroll-animations/#; type: dfn
 86 |     url: current-time-algorithm; text: current time of the ScrollTimeline;
 87 | 
88 | 89 |
 90 | {
 91 |     "explainer": {
 92 |         "href": "https://github.com/WICG/animation-worklet/blob/gh-pages/README.md",
 93 |         "title": "Animation Worklet Explainer",
 94 |         "status": "CR",
 95 |         "publisher": "WICG",
 96 |         "deliveredBy": [
 97 |             "https://github.com/WICG/animation-worklet//"
 98 |         ]
 99 |     }
100 | }
101 | 
102 | 103 | Introduction {#intro} 104 | ===================== 105 | This section is not normative. 106 | 107 | This document introduces a new primitive for creating scroll-linked and other high performance 108 | procedural animations on the web. For details on the rationale and motivation see [[explainer]]. 109 | 110 | The Animation Worklet API provides a method to create scripted animations that control a set 111 | of animation effects. The API is designed to make it possible for user agents to run such 112 | animations in their own dedicated thread to provide a degree of performance isolation from main 113 | thread. 114 | 115 | Relationship to the Web Animations API {#relationship-to-web-animations} 116 | ------------------------------------------------------------------------ 117 | 118 | Animations running inside an Animation Worklet execution context expose the {{Animation}} 119 | interface from the Web Animations specification on the main javascript execution context. This means 120 | they can be controlled and inspected from main thread using many of the Web Animation APIs. However 121 | Animation Worklet animations follow a different timing model that enables them to be script-driven, 122 | stateful, and runnable in a parallel worklet execution context. As such Web Animation APIs that seek 123 | or alter the input time (reverse, finish, etc.) have different semantics for Animation Worklet 124 | animations. 125 | 126 | 127 | Threading Model {#threading-model} 128 | ================================== 129 | This section is not normative. 130 | 131 | Animation Worklet is designed to be thread-agnostic. Rendering engines may create one or more 132 | parallel worklet execution contexts separate from the main javascript execution context, e.g., on 133 | their own dedicated threads. Rendering engines may then choose to assign Animation Worklet 134 | animations to run in such contexts. Doing so allows Animation Worklet animations to avoid being 135 | impacted by main thread jank. 136 | 137 | Rendering engines may wish to make a best-effort attempt to execute animate callbacks synchronously 138 | with visual frame production to ensure smooth animation. However it is legal for rendering engines 139 | to produce visual frames without blocking to receive animation updates from a worklet (i.e., letting 140 | the effects slip behind). For example, this could occur when the animate function callback is 141 | unable to complete before the frame deadline. 142 | 143 | We believe that scripted animations which are run in a parallel execution environment and which 144 | limit themselves to animating properties which do not require the user agent to consult main thread 145 | will have a much better chance of meeting the strict frame budgets required for smooth playback. 146 | 147 | If a Worklet Animation animation is executing in a parallel worklet execution context, the last 148 | known state of its animation effects should be periodically synced back to the main javascript 149 | execution context. The synchronization of effect values from the parallel worklet execution 150 | context to the main javascript execution context must occur before running the animation 151 | frame callbacks as part of the document lifecycle. Note that due to the asynchronous nature of 152 | this animation model a script running in the main javascript execution context may see a stale value 153 | when reading a target property that is being animated in a Worklet Animation, compared to the 154 | value currently being used to produce the visual frame that is visible to the user. This is similar 155 | to the effect of asynchronous scrolling when reading scroll offsets in the main javascript execution 156 | context. 157 | 158 | 159 |
160 | Overview of the animation worklet threading model. 162 |
163 | Overview of the animation worklet threading model.
164 | 165 | A simplified visualization of how animators running in a parallel execution environment can sync 166 | their update to main thread while remaining in sync with visual frame production. 167 |
168 |
169 | 170 | Animation Worklet {#animation-worklet-desc} 171 | ============================== 172 | Animation Worklet is a {{Worklet}} responsible for all classes related to custom 173 | animations. The worklet can be accessed via {{animationWorklet}} attribute. 174 | 175 | The {{animationWorklet}}'s worklet global scope type is {{AnimationWorkletGlobalScope}}. 176 | 177 | {{AnimationWorkletGlobalScope}} represents the global execution context of {{animationWorklet}}. 178 | 179 | 180 | [Exposed=Window] 181 | partial namespace CSS { 182 | [SameObject] readonly attribute Worklet animationWorklet; 183 | }; 184 | 185 | 186 | 187 | [Exposed=AnimationWorklet, Global=AnimationWorklet] 188 | interface AnimationWorkletGlobalScope : WorkletGlobalScope { 189 | void registerAnimator(DOMString name, VoidFunction animatorCtor); 190 | }; 191 | 192 | 193 | 194 |
195 | Note: This is how the class should look. 196 |
197 |         class FooAnimator {
198 |             constructor(options) {
199 |                 // Called when a new animator is instantiated.
200 |             }
201 |             animate(currentTime, effect) {
202 |                 // Animation frame logic goes here.
203 |             }
204 |         }
205 |     
206 |
207 | 208 | 209 | 210 | Animator Definition {#animator-definition-desc} 211 | ==================== 212 | An animator definition is a struct which describes the author defined custom 213 | animation as needed by {{AnimationWorkletGlobalScope}}. It consists of: 214 | 215 | - An animator name <>#. 216 | 217 | - A class constructor which is a VoidFunction callback function type. 218 | 219 | - An animate function which is a Function callback function type. 220 | 221 | - A destroy function which is a Function callback function type. 222 | 223 | 224 | Registering an Animator Definition {#registering-animator-definition} 225 | ------------------------------------- 226 | An {{AnimationWorkletGlobalScope}} has a animator name to animator definition map. 227 | The map gets populated when {{registerAnimator(name, animatorCtorValue)}} is called. 228 | 229 |
230 | 231 | When the registerAnimator(|name|, |animatorCtorValue|) 232 | method is called in a {{AnimationWorkletGlobalScope}}, the user agent must run the 233 | following steps: 234 | 235 | 1. If |name| is not a valid <>, throw a TypeError and abort all these 236 | steps. 237 | 238 | 2. If |name| exists as a key in the animator name to animator definition map, 239 | throw a NotSupportedError and abort all these steps. 240 | 241 | 3. If the result of IsConstructor(|animatorCtorValue|) is false, throw a 242 | TypeError and abort all these steps. 243 | 244 | 4. Let |animatorCtor| be the result of converting animatorCtorValue to the 245 | VoidFunction callback function type. If an exception is thrown, rethrow the 246 | exception and abort all these steps. 247 | 248 | 4. Let |prototype| be the result of Get(|animatorCtorValue|, "prototype"). 249 | 250 | 5. If the result of Type(|prototype|) is not Object, throw a TypeError 251 | and abort all these steps. 252 | 253 | 6. Let |animateValue| be the result of Get(|prototype|, "animate"). 254 | 255 | 7. Let |animate| be the result of converting |animateValue| to the Function 256 | callback function type. If an exception is thrown, rethrow the exception and abort 257 | all these steps. 258 | 259 | 8. Let |destroyValue| be the result of Get(|prototype|, "onDestroy"). 260 | 261 | 9. Let |destroy| be the result of converting |destroyValue| to the Function 262 | callback function type. If an exception is thrown, rethrow the exception and abort 263 | all these steps. 264 | 265 | 266 | 8. Let |definition| be a new animator definition with: 267 | 268 | - animator name being |name| 269 | 270 | - class constructor being |animatorCtor| 271 | 272 | - animate function being |animate| 273 | 274 | - destroy function being |destroy| 275 | 276 | 277 | 9. Add the key-value pair (|name| - |definition|) to the animator name to animator 278 | definition map. 279 |
280 | 281 | 282 | Animator Instance {#animator-instance-section} 283 | ====================================== 284 | 285 | An animator instance is a struct which describes a fully realized custom animation 286 | instance in an {{AnimationWorkletGlobalScope}}. It has a reference to an animator definition 287 | and owns the instance specific state such as animation effect and timelines. It consists of: 288 | 289 | - An animator name. 290 | 291 | - An animation requested flag. 292 | 293 | - An animator effect which is an animation effect. 294 | 295 | - An animator current time which is the corresponding worklet animation's current 296 | time. 297 | 298 | - An animator timeline which is a timeline. 299 | 300 | - An animator attached timelines which is list of attached timelines 301 | 302 | - An animator serialized options which is a serializable object. 303 | 304 | 305 | Creating an Animator Instance {#creating-animator-instance} 306 | ----------------------------------------------------------- 307 | 308 | Each animator instance lives in an {{AnimationWorkletGlobalScope}}. 309 | 310 | Each {{AnimationWorkletGlobalScope}} has an animator instance set. The set is populated 311 | when the user agent constructs a new animator instance in the {{AnimationWorkletGlobalScope}} 312 | scope. Each animator instance corresponds to a worklet animation in the document scope. 313 | 314 |
315 | 316 | To create a new animator instance given a |name|, |timeline|, |effect|, |serializedOptions|, 317 | |serializedState|, and |workletGlobalScope|, the user agent must run the following steps: 318 | 319 | 1. Let the |definition| be the result of looking up |name| on the |workletGlobalScope|'s 320 | animator name to animator definition map. 321 | 322 | If |definition| does not exist abort the following steps. 323 | 324 | 2. Let |animatorCtor| be the class constructor of |definition|. 325 | 326 | 3. Let |timelineList| be a new list with |timeline| added to it. 327 | 328 | 4. Let |options| be StructuredDeserialize(|serializedOptions|). 329 | 330 | 5. Let |state| be StructuredDeserialize(|serializedState|). 331 | 332 | 6. Let |animatorInstance| be the result of constructing |animatorCtor| with 333 | [|options|, |state| as args. If an exception is thrown, rethrow the exception and abort all 334 | these steps. 335 | 336 | 7. Set the following on |animatorInstance| with: 337 | - animator name being |name| 338 | - animation requested flag being frame-current 339 | - animator current time being unresolved 340 | - animator effect being |effect| 341 | - animator timeline being |timeline| 342 | - animator attached timelines being |timelineList| 343 | - animator serialized options being |options| 344 | 345 | 8. Add |animatorInstance| to |workletGlobalScope|'s animator instance set. 346 | 347 |
348 | 349 | 350 | Running Animators {#running-animators} 351 | -------------------------------------- 352 | 353 | When a user agent wants to produce a new animation frame, if for any animator instance the 354 | associated animation requested flag is frame-requested then the the user agent 355 | must run animators for the current frame. 356 | 357 | Note: The user agent is not required to run animations on every visual frame. It is legal to defer 358 | generating an animation frame until a later frame. This allow the user agent to 359 | provide a different service level according to their policy. 360 | 361 |
362 | 363 | When the user agent wants to run animators in a given |workletGlobalScope|, it 364 | must iterate over all animator instances in the |workletGlobalScope|'s animator 365 | instance set. For each such |instance| the user agent must perform the following steps: 366 | 367 | 1. Let |animatorName| be |instance|'s animator name 368 | 369 | 2. Let the |definition| be the result of looking up |animatorName| on the |workletGlobalScope|'s 370 | animator name to animator definition map. 371 | 372 | If |definition| does not exist then abort the following steps. 373 | 374 | 3. If the animation requested flag for |instance| is frame-current or the effect 375 | belonging to the |instance| will not be visible within the visual viewport of the current 376 | frame the user agent may abort all the following steps. 377 | 378 | Issue: Consider giving user agents permission to skip running animator instances to 379 | throttle slow animators. 380 | 381 | 4. Let |animateFunction| be |definition|'s animate function. 382 | 383 | 5. Let |currentTime| be animator current time of |instance|. 384 | 385 | 6. Let |effect| be animator effect of |instance|. 386 | 387 | 7. Invoke |animateFunction| with arguments «|currentTime|, |effect|», 388 | and with |instance| as the callback this value. 389 | 390 |
391 | Note: Although inefficient, it is legal for the user agent to run animators multiple times 392 | in the same frame. 393 | 394 | Removing an Animator Instance {#removing-animator} 395 | ----------------------------------------- 396 | 397 |
398 | 399 | To remove an animator instance given |instance| and |workletGlobalScope| the user agent 400 | must run the following steps: 401 | 402 | 1. Remove |instance| from |workletGlobalScope|'s animator instance set. 403 | 404 |
405 | 406 | 407 | Migrating an Animator Instance {#migrating-animator} 408 | ----------------------------------------- 409 | 410 | User agents are responsible for assigning an animator instance to a {{WorkletGlobalScope}}. 411 | There can be many such {{WorkletGlobalScope}}s, which may exist across different threads or 412 | processes. To give the most flexibility to user agents in this respect, we allow migration of an 413 | animator instance while it is running. The basic mechanism is to serialize the internal state 414 | of any author-defined effect, and restore it after migration. 415 | 416 |
417 | 418 | To migrate an animator instance from one {{WorkletGlobalScope}} to another, given 419 | |instance|, |sourceWorkletGlobalScope|, |destinationWorkletGlobalScope|, the user agent 420 | must run the following steps : 421 | 422 | 1. Let |serializedState| be undefined. 423 | 424 | 2. Queue a task on |sourceWorkletGlobalScope| to run the following steps: 425 | 426 | 1. Let |animatorName| be |instance|'s animator name 427 | 428 | 2. Let |definition| be the result of looking up |animatorName| on |sourceWorkletGlobalScope|'s 429 | animator name to animator definition map. 430 | 431 | If |definition| does not exist then abort the following steps. 432 | 433 | 3. Let |destroyFunction| be the destroy function of |definition|. 434 | 435 | 436 | 4. Invoke |destroyFunction| with |instance| as the callback this value and 437 | let |state| be the result of the invocation. If any exception is thrown, rethrow the 438 | exception and abort the following steps. 439 | 440 | 5. Set |serializedState| to be the result of StructuredSerialize(|state|). 441 | If any exception is thrown, then abort the following steps. 442 | 443 | 6. Run the procedure to remove an animator instance given |instance|, and 444 | |sourceWorkletGlobalScope|. 445 | 446 | 2. Wait for the above task to complete. If the task is aborted, abort the following steps. 447 | 448 | 3. Queue a task on |destinationWorkletGlobalScope| to run the following steps: 449 | 450 | 1. Run the procedure to create a new animator instance given: 451 | - The |instance|'s animator name as name. 452 | - The |instance|'s animator timeline as timeline. 453 | - The |instance|'s animator effect as effect. 454 | - The |instance|'s animator serialized options as options. 455 | - The |serializedState| as state. 456 | - The |destinationWorkletGlobalScope| as workletGlobalScope. 457 | 458 |
459 | 460 | 461 | Requesting Animation Frames {#requesting-animation-frames} 462 | ---------------------------------------------------------- 463 | 464 | Each animator instance has an associated animation requested flag. It must be 465 | either frame-requested or frame-current. It is initially set to 466 | frame-current. Different circumstances can cause the animation requested flag to be 467 | set to frame-requested. These include the following: 468 | - Changes in the current time of any timeline in the animator's animator attached timelines 469 | - Changes in the current time of the animator's corresponding Worklet Animation 470 | 471 | [[#running-animators]] resets the animation requested flag on animators to 472 | frame-current. 473 | 474 | 475 | Web Animations Integration {#web-animation-integration} 476 | =============================== 477 | 478 | 479 | Worklet Animation {#worklet-animation-desc} 480 | ------------------------------------------- 481 | Worklet animation is a kind of animation that delegates the animation playback to 482 | an animator instance. It controls the lifetime and playback state of its corresponding 483 | animator instance. 484 | 485 | Being an animation, worklet animation has an animation effect and a 486 | timeline. However unlike other animations the worklet animation's current time does 487 | not directly determine the animation effect's local time (via its inherited time). 488 | Instead the associated animator instance controls the animation effect's local time 489 | directly. Note that this means that the timeline's current time does not fully determine the 490 | animation's output. 491 | 492 | Worklet animation has the following properties in addition to the {{Animation}} interface: 493 | - an animation animator name which identifies its animator definition. 494 | - a serialized options which is serializable object that is used when 495 | constructing a new animator instance. 496 | 497 |
498 | Overview of the WorkletAnimation timing model. 500 |
501 | Overview of the WorkletAnimation timing model.
502 | 503 | The animation current time is input to the animator instance, which produces a local time value 504 | for the animation effect. If the animator instance is running in a parallel global scope the 505 | implementation may also choose to use the local time value to produce the final effect value and 506 | update the visuals in parallel. 507 | 508 |
509 |
510 | 511 | 512 | Creating a Worklet Animation {#creating-worklet-animation} 513 | ----------------------------------------------------------- 514 | 515 | 516 | [Exposed=Window, 517 | Constructor (DOMString animatorName, 518 | optional (AnimationEffect or sequence<AnimationEffect>)? effects = null, 519 | optional AnimationTimeline? timeline, 520 | optional any options)] 521 | interface WorkletAnimation : Animation { 522 | readonly attribute DOMString animatorName; 523 | }; 524 | 525 | 526 | 527 | 528 |
529 | WorkletAnimation(|animatorName|, |effects|, |timeline|, |options|) 530 | 531 | Creates a new {{WorkletAnimation}} object using the following procedure. 532 | 533 | 1. Let |workletAnimation| be a new {{WorkletAnimation}} object. 534 | 535 | 2. Run the procedure to set the timeline of an animation on |workletAnimation| passing 536 | |timeline| as the new timeline or, if a |timeline| argument is not provided, 537 | passing the default document timeline of the {{Document}} associated with the 538 | {{Window}} that is the current global object. 539 | 540 | 3. Let |effect| be the result corresponding to the first matching condition from below. 541 | : If |effects| is a {{AnimationEffect}} object, 542 | :: Let effect be |effects|. 543 | : If |effects| is a list of {{AnimationEffect}} objects, 544 | :: Let |effect| be a new {{WorkletGroupEffect}} with its children set to |effects|. 545 | : Otherwise, 546 | :: Let |effect| be undefined. 547 | 548 | 4. Run the procedure to set the target effect of an animation on |workletAnimation| 549 | passing |effect| as the new effect. 550 | 551 | 5. Let |serializedOptions| be the result of StructuredSerialize(|options|). 552 | Rethrow any exceptions. 553 | 554 | 6. Set the serialized options of |workletAnimation| to |serializedOptions|. 555 | 556 | 7. Set the animation animator name of |workletAnimation| to |animatorName|. 557 |
558 | 559 | 560 | Worklet Animation timing model {#timing-model} 561 | ------------------------------------ 562 | 563 | This section describes how worklet animation's timing model differs from other 564 | animations. 565 | 566 | In addition to the existing conditions on when the animation is considered ready, a 567 | worklet animation is only considered ready when the following condition is also true: 568 | 569 | - the user agent has completed any setup required to create the worklet animation's 570 | corresponding animator instance. 571 | 572 | As described in [[#worklet-animation-desc]], the worklet animation's current time does 573 | not determine its animation effect's local time. Instead the associated animator 574 | instance controls the animation effect's local time directly. This means that the 575 | animation effect's local time is controlled from a {{WorkletGlobalScope}} which may be in a parallel 576 | execution context. 577 | 578 | Here are a few implications of the above semantics: 579 | 580 | - Setting the current time or start time of a worklet animation does not 581 | necessarily change its output, but may change the animation play state. 582 | - Similarly, invoking {{Animation/finish()}} or updating a worklet animation's playback 583 | rate will only change the animation play state and may not change the output. 584 | - Querying the animation effect's local time using {{AnimationEffect/getComputedTiming()}} 585 | may return stale information, in the case where the animator instance is running in a 586 | parallel execution context. 587 | 588 | Issue(63): Come with appropriate mechanism's for animator instance to get notified when its 589 | animation currentTime is changing e.g., via reverse(), finish() or playbackRate change. So that 590 | it can react appropriately. 591 | 592 | 593 | Interaction with Animator Instances {#worklet-animation-animator-instances} 594 | ----------------------------------- 595 | 596 | A worklet animation corresponds to at most one animator instance at any time, and may 597 | have no current corresponding animator instance. The correspondance of an animator 598 | instance for a worklet animation depends on the animation play state. 599 | 600 |
601 | 602 | To associate animator instance of worklet animation given |workletAnimation|, 603 | the user agent must run the following steps: 604 | 605 | 1. If |workletAnimation| has a corresponding animator instance, abort the following steps. 606 | 2. Let |workletGlobalScope| be the {{AnimationWorkletGlobalScope}} associated with 607 | |workletAnimation|. 608 | 3. Queue a task on |workletGlobalScope| to run the procedure to create a new animator 609 | instance, passing: 610 | * The |workletAnimation|'s animation animator name as name. 611 | * The |workletAnimation|'s timeline as timeline. 612 | * The |workletAnimation|'s animation effect as effect. 613 | * The |workletAnimation|'s serialized options as options. 614 | * The |workletGlobalScope| as workletGlobalScope. 615 | 4. If the procedure was successful, set the resulting animator instance as corresponding to 616 | |workletAnimation|. 617 | 618 |
619 | 620 |
621 | 622 | To disassociate animator instance of worklet animation given 623 | |workletAnimation|, the user age must run the following steps: 624 | 625 | 1. If |workletAnimation| does not have a corresponding animator instance, abort the 626 | following steps. 627 | 2. Let |workletGlobalScope| be the {{AnimationWorkletGlobalScope}} associated with 628 | |workletAnimation|. 629 | 3. Let |animatorInstance| be |workletAnimation|'s corresponding animator instance. 630 | 4. Queue a task on the |workletGlobalScope| to run the procedure to remove an animator 631 | instance, passing |animatorInstance| as instance and |workletGlobalScope| as 632 | workletGlobalScope. 633 | 5. Set |workletAnimation| as having no corresponding animator instance. 634 | 635 |
636 | 637 | 638 |
639 | 640 | To set animator instance of worklet animation given 641 | |workletAnimation|, the user agent must run the following steps: 642 | 643 | 1. disassociate animator instance of worklet animation given |workletAnimation|. 644 | 2. associate animator instance of worklet animation given |workletAnimation|. 645 | 646 |
647 | 648 | When a given |workletAnimation|'s play state changes to pending, running, or 649 | paused, run the procedure to 650 | associate animator instance of worklet animation given |workletAnimation|. 651 | 652 | 653 | When a given |workletAnimation|'s play state changes to idle or finished, 654 | run the procedure to 655 | disassociate animator instance of worklet animation given |workletAnimation|. 656 | 657 | When the procedure to set the target effect of an animation for a given |workletAnimation| 658 | is called, then set animator instance of worklet animation given |workletAnimation|. 659 | 660 | When the procedure to set the timeline of an animation for a given |workletAnimation| 661 | is called, then set animator instance of worklet animation given |workletAnimation|. 662 | 663 | 664 | Timeline Attachment {#timeline-attachment} 665 | ------------------- 666 | 667 | Issue(61): Define semantics of attachment and detachment. 668 | 669 | ScrollTimeline {#scroll-timeline} 670 | --------------------------------- 671 | {{ScrollTimeline}} is a new concept being proposed for addition to web animation API. It defines 672 | an animation timeline whose time value depends on the scroll position of a scroll container. 673 | Worklet animations can have a scroll timeline and thus drive their scripted effects based 674 | on a scroll offset. 675 | 676 | Note: Access to input: We are interested on exposing additional user input beside 677 | scrolling (e.g., touch/pointer input) to these animations so that authors can create jank-free 678 | input driven animations which are not really possible today. We are still trying to figure out the 679 | right abstractions and mechanisms to do this. 680 | 681 | WorkletGroupEffect {#worklet-group-effect} 682 | ------------------ 683 | 684 | {{WorkletGroupEffect}} is a type of group effect that allows its child effect's 685 | local times to be mutated individually. 686 | 687 | When a {{WorkletGroupEffect}} is set as the animation effect of a {{WorkletAnimation}}, the 688 | corresponding animator instance can directly control the child effects' local 689 | times. This allows a single worklet animation to coordinate multiple effects - see 690 | [[#example-2]] for an example of such a use-case. 691 | 692 | 693 | [Exposed=AnimationWorklet] 694 | interface WorkletGroupEffect { 695 | sequence<AnimationEffect> getChildren(); 696 | }; 697 | 698 | [Exposed=AnimationWorklet] 699 | partial interface AnimationEffect { 700 | // Intended for use inside Animation Worklet scope to drive the effect. 701 | attribute double localTime; 702 | }; 703 | 704 | 705 |
706 | 707 | To set the {{localTime}} property on a |effect| to value |t|, the user agent should perform the 708 | action that corresponds to the first matching condition from the following: 709 | 710 | : If the |effect| does not have a parent group, 711 | :: Set the |effect| local time to |t|. 712 | : If the |effect| has a parent group and it is of {{WorkletGroupEffect}} type, 713 | :: Set the effect start time to (parent's transformed time - t). Note this effectively set's the 714 | |effect|'s local time to t. 715 | : Otherwise 716 | :: Throw an exception indicating that the child effect time can only be controlled by 717 | its parent group. 718 | 719 |
720 | 721 | Issue(w3c/csswg-drafts#2071): The above interface exposes a conservative subset 722 | of GroupEffect proposed as part of web-animation-2. Once that is available we 723 | should switch to it. 724 | 725 | 726 | Effect Stack and Composite Order {#effect-stack-composite-order} 727 | -------------------------------- 728 | 729 | As with other animations, worklet animations participate in the effect stack. A 730 | worklet animation does not have a specific animation class which means it has the same 731 | composite order as other Javascript created web animations. 732 | 733 | 734 | Examples {#examples} 735 | ==================== 736 | 737 | Example 1: Hidey Bar. {#example-1} 738 | ----------------------------------------- 739 | An example of header effect where a header is moved with scroll and as soon as finger is lifted it 740 | animates fully to close or open position depending on its current position. 741 | 742 | 743 | 744 | <div id='scrollingContainer'> 745 | <div id='header'>Some header</div> 746 | <div>content</div> 747 | </div> 748 | 749 | <script> 750 | await CSS.animationWorklet.addModule('hidey-bar-animator.js'); 751 | const scrollTimeline = new ScrollTimeline($scrollingContainer, {timeRange: 1000}); 752 | const documentTimeline = document.timeline; 753 | 754 | // Note we pass in two timelines in the options bag which allows the animation to read their 755 | // currenTime values directly. 756 | const animation = new WorkletAnimation( 757 | 'hidey-bar', 758 | new KeyframeEffect($header, 759 | [{transform: 'translateX(100px)'}, {transform: 'translateX(0px)'}], 760 | {duration: 1000, iterations: 1, fill: 'both' }]), 761 | scrollTimeline, 762 | {scrollTimeline, documentTimeline}); 763 | 764 | animation.play(); 765 | </script> 766 | 767 | 768 | 769 | 770 | // Inside AnimationWorkletGlobalScope 771 | 772 | registerAnimator('hidey-bar', class { 773 | constructor(options) { 774 | this.scrollTimeline_ = options.scrollTimeline; 775 | this.documentTimeline_ = options.documentTimeline; 776 | } 777 | 778 | animate(currentTime, effect) { 779 | const scroll = this.scrollTimeline_.currentTime; // [0, 100] 780 | const time = this.documentTimeline_.currentTime; 781 | 782 | const activelyScrolling = this.scrollTimeline_.phase == 'active'; 783 | 784 | let localTime; 785 | if (activelyScrolling) { 786 | this.startTime_ = undefined; 787 | localTime = scroll; 788 | } else { 789 | this.startTime_ = this.startTime_ || time; 790 | // Decide on close/open direction depending on how far we have scrolled the header 791 | // This can even do more sophisticated animation curve by computing the scroll velocity and 792 | // using it. 793 | this.direction_ = scroll >= 50 ? +1 : -1; 794 | localTime = this.direction_ * (time - this.startTime_); 795 | } 796 | 797 | // Drive the output effect by setting its local time. 798 | effect.localTime = localTime; 799 | } 800 | }); 801 | 802 | 803 | 804 | Issue: This example uses a hypothetical "phase" property on timeline as a way to detect when user 805 | is no longer actively scrolling. This is a reasonable thing to have on scroll timeline. A simple 806 | fallback can emulate this by detecting when timeline time (i.e. scroll offset) has not changed in 807 | the last few frames. 808 | 809 | 810 | Example 2: Twitter header. {#example-2} 811 | -------------------------- 812 | An example of twitter profile header effect where two elements (avatar, and header) are updated in 813 | sync with scroll offset. 814 | 815 | 816 | 817 | // In document scope. 818 | <div id='scrollingContainer'> 819 | <div id='header' style='height: 150px'></div> 820 | <div id='avatar'><img></div> 821 | </div> 822 | 823 | <script> 824 | await CSS.animationWorklet.addModule('twitter-header-animator.js'); 825 | const animation = new WorkletAnimation( 826 | 'twitter-header', 827 | [new KeyframeEffect($avatar, /* scales down as we scroll up */ 828 | [{transform: 'scale(1)'}, {transform: 'scale(0.5)'}], 829 | {duration: 1000, iterations: 1}), 830 | new KeyframeEffect($header, /* loses transparency as we scroll up */ 831 | [{opacity: 0}, {opacity: 0.8}], 832 | {duration: 1000, iterations: 1})], 833 | new ScrollTimeline($scrollingContainer, {timeRange: 1000, startScrollOffset: 0, endScrollOffset: $header.clientHeight})); 834 | animation.play(); 835 | 836 | // Since this animation is using a group effect, the same animation instance 837 | // is accessible via different handles: $avatarEl.getAnimations()[0], $headerEl.getAnimations()[0] 838 | 839 | </script> 840 | 841 | 842 | 843 | 844 | // Inside AnimationWorkletGlobalScope. 845 | registerAnimator('twitter-header', class { 846 | constructor(options) { 847 | this.timing_ = new CubicBezier('ease-out'); 848 | } 849 | 850 | clamp(value, min, max) { 851 | return Math.min(Math.max(value, min), max); 852 | } 853 | 854 | animate(currentTime, effect) { 855 | const scroll = currentTime; // scroll is in [0, 1000] range 856 | 857 | // Drive the output group effect by setting its children local times individually. 858 | effect.children[0].localTime = scroll; 859 | effect.children[1].localTime = this.timing_(clamp(scroll, 0, 500)); 860 | } 861 | }); 862 | 863 | 864 | Example 3: Parallax backgrounds. {#example-3} 865 | ----------------------------------------- 866 | A simple parallax background example. 867 | 868 | 869 | <style> 870 | .parallax { 871 | position: fixed; 872 | top: 0; 873 | left: 0; 874 | opacity: 0.5; 875 | } 876 | </style> 877 | <div id='scrollingContainer'> 878 | <div id="slow" class="parallax"></div> 879 | <div id="fast" class="parallax"></div> 880 | </div> 881 | 882 | <script> 883 | await CSS.animationWorklet.addModule('parallax-animator.js'); 884 | const scrollTimeline = new ScrollTimeline($scrollingContainer, {timeRange: 1000}); 885 | const scrollRange = $scrollingContainer.scrollHeight - $scrollingContainer.clientHeight; 886 | 887 | const slowParallax = new WorkletAnimation( 888 | 'parallax', 889 | new KeyframeEffect($parallax_slow, [{'transform': 'translateY(0)'}, {'transform': 'translateY(' + -scrollRange + 'px)'}], {duration: 1000}), 890 | scrollTimeline, 891 | {rate : 0.4} 892 | ); 893 | slowParallax.play(); 894 | 895 | const fastParallax = new WorkletAnimation( 896 | 'parallax', 897 | new KeyframeEffect($parallax_fast, [{'transform': 'translateY(0)'}, {'transform': 'translateY(' + -scrollRange + 'px)'}], {duration: 1000}), 898 | scrollTimeline, 899 | {rate : 0.8} 900 | ); 901 | fastParallax.play(); 902 | </script> 903 | 904 | 905 | 906 | 907 | // Inside AnimationWorkletGlobalScope. 908 | registerAnimator('parallax', class { 909 | constructor(options) { 910 | this.rate_ = options.rate; 911 | } 912 | 913 | animate(currentTime, effect) { 914 | effect.localTime = currentTime * this.rate_; 915 | } 916 | }); 917 | 918 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /w3c.json: -------------------------------------------------------------------------------- 1 | { 2 | "group": ["80485"] 3 | , "contacts": ["yoavweiss"] 4 | , "shortName": "animation-worklet" 5 | } --------------------------------------------------------------------------------