forked from WICG/animation-worklet
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathindex.bs
854 lines (610 loc) · 32 KB
/
index.bs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
<pre class='metadata'>
Title: CSS Animation Worklet API
Status: CG-DRAFT
Group: WICG
URL: https://wicg.github.io/animation-worklet/
Shortname: css-animation-api
Level: 1
Abstract:
Editor: Majid Valipour, [email protected]
Editor: Robert Flack, [email protected]
Editor: Stephen McGruer, [email protected]
</pre>
<pre class="link-defaults">
spec:css-break-3; type:dfn; text:fragment
</pre>
<pre class="anchors">
urlPrefix: https://heycam.github.io/webidl/; type: dfn;
text: NotSupportedError
urlPrefix: #dfn-;
text: callback this value
text: exception
text: throw
url: throw; text: thrown
url: es-invoking-callback-functions; text: Invoke
urlPrefix: https://www.w3.org/TR/css3-transitions/#; type: dfn;
text: animatable properties
urlPrefix: https://www.w3.org/TR/web-animations/#; type: dfn;
url: the-documents-default-timeline; text: default document timeline
urlPrefix: https://tc39.github.io/ecma262/#sec-; type: dfn;
text: constructor
text: Construct
text: IsCallable
text: IsConstructor
text: HasProperty
url: ecmascript-data-types-and-values; text: Type
url: map-objects; text:map object
url: get-o-p; text: Get
url: set-o-p-v-throw; text: Set
url: terms-and-definitions-function; text: function
urlPrefix: native-error-types-used-in-this-standard-
text: TypeError
urlPrefix: https://www.w3.org/TR/hr-time-2/#dom-; type: dfn
text: DOMHighResTimeStamp
urlPrefix: https://wicg.github.io/scroll-animations/#; type: interface
url: scrolltimeline; text: ScrollTimeline
url: dictdef-scrolltimelineoptions; text: ScrollTimelineOptions
url: dom-scrolltimeline-scrollsource; text: scrollSource
urlPrefix: https://wicg.github.io/scroll-animations/#; type: dfn
url: current-time-algorithm; text: current time of the ScrollTimeline;
</pre>
<pre class=biblio>
{
"explainer": {
"href": "https://github.com/WICG/animation-worklet/blob/gh-pages/README.md",
"title": "Animation Worklet Explainer",
"status": "CR",
"publisher": "WICG",
"deliveredBy": [
"https://github.com/WICG/animation-worklet//"
]
}
}
</pre>
Deprecated {#deprecated}
========================
<strong>The contents of this document are out of date and in need of large re-write to cover
changes to the AnimationWorklet API</strong>.
For now, please refer to our <a href="https://github.com/WICG/animation-worklet/blob/gh-pages/WIP.md">WIP explainer</a>
for more up-to-date content.
Introduction {#intro}
=====================
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 <a>Animation Worklet</a> provides a method to create scripted animations that can mutate a set
of user visible animatable attributes. The API is designed to make it possible for such animations to
run in performance-critical parts of the rendering pipeline. Although the specification does not
require certain performance guarantees (e.g., running in sync with every frame produced or in
isolation from main thread) but the API is designed to facilitate and encourage this.
<strong>Relationship to Web Animation API</strong>: Animations running on <a>Animation
Worklet</a> do not necessarily run on the main thread and thus are not synchronized with <a>default
document timeline</a>. At the moment we do not expose any API to start, stop, compose or otherwise
control these animations from outside the worklet, however our plan is to leverage existing web
animation APIs such as Element::getAnimations().
Note: <strong>Access to input</strong>: We are interested on exposing additional user input (e.g.,
scrolling input) to these animations so that authors can create jank-free input driven animations
which are not really possible today.
Note: <strong>Threading Model</strong>: The API is designed to allow animation worklets to run on
threads other than the main thread. In particular, it is recommended that if it is possible to run
them in a dedicated thread and provide a best-effort attempt to run in sync with visual frame
production. This ensures the animations will not be impacted by jank on main thread.
This means that when querying those animations running on the worklet in main thread, the data may
be staled until such time that the animation effects are synced back to main thread (Not dissimilar
to how async scrolling can lead to stale scroll offsets).
It is still possible for such animation to slip in relation with visual frame production if
*animate* callback cannot be completed in time. We believe such slippage is going to be rare in
particular if animators are running on their dedicated thread and its effects avoid layout inducing
attributes.
Animation Worklet {#animation-worklet-desc}
==============================
<dfn>Animation Worklet</dfn> is a {{Worklet}} responsible for all classes related to custom
animations. The worklet can be accessed via {{animationWorklet}} attribute.
The {{animationWorklet}}'s <a>worklet global scope type</a> is {{AnimationWorkletGlobalScope}}.
<pre class='idl'>
interface AnimationWorklet {
[NewObject] Promise<void> import(USVString moduleURL);
};
partial interface Window {
[SameObject] readonly attribute AnimationWorklet animationWorklet;
};
</pre>
<pre class='idl'>
callback VoidFunction = void ();
[Exposed=AnimationWorklet]
interface AnimationWorkletGlobalScope : WorkletGlobalScope {
void registerAnimator(DOMString name, VoidFunction animatorCtor);
};
</pre>
<div class='note'>
Note: This is how the class should look.
<pre class='lang-javascript'>
class FooAnimator {
static get elements() {
return [
{name: 'foo', inputProperties: ['--foo'], outputProperties: []},
{name: 'bar', inputProperties: [], outputProperties: ['transform']},
];
};
static timelines = [
'document',
{ type: 'scroll', { orientation: 'vertical' } },
];
animate(elementMap, timelines) {
// Animation frame logic goes here
}
}
</pre>
</div>
Concepts {#concepts}
====================
A <dfn>animator definition</dfn> describes an author defined animation which can be referenced by
an <a>animator instance</a>. It consists of:
- A <dfn>class constructor</dfn>.
- An <dfn>animate function</dfn>.
- An <dfn>animator slot list</dfn> containing a dictionary providing each <dfn>slot name</dfn>,
its <dfn>input property list</dfn>, and its <dfn>output property list</dfn>.
- An <dfn>animator timeline options list</dfn>.
Each entry in the <a>animator slot list</a> consists of:
- An <dfn>animator slot</dfn> <<ident>>#.
- An <dfn>animator input property list</dfn>.
- An <dfn>animator output property list</dfn>.
An <dfn>animator instance</dfn> describes a fully realized custom animation instance in the worklet
context and links an <a>animator definition</a> with the instance specific state such as its element
proxies. It consists of:
- An <dfn>animator name</dfn> <<ident>>#.
- An <a>animation requested flag</a>.
- A <dfn>root element</dfn>.
- An <dfn>element proxy map</dfn> mapping <a>animator slot</a> to a list of <a>element proxies</a>.
An <dfn>element proxy</dfn> defines a handle to an element which can be used to read or
write its explicitly exposed animatable attributes. It consists of:
- An <a>animator instance</a>.
- A <dfn>proxied element</dfn>.
- A read only map of proxied input properties to their values.
- A map of proxied output properties to their values.
Registering an Animator Definition {#registering-animator-definition}
============================================================
The {{AnimationWorkletGlobalScope}} has a <dfn>animator name to animator definition map</dfn>.
The map gets populated when {{registerAnimator(name, animatorCtor)}} is called.
When the <dfn method for=AnimationWorkletGlobalScope>registerAnimator(|name|,
|animatorCtor|)</dfn> method is called, the user agent <em>must</em> run the following steps:
1. If the |name| is not a valid <<ident>>, <a>throw</a> a <a>TypeError</a> and abort all these
steps.
2. If the |name| exists as a key in the <a>animator name to animator definition map</a>,
<a>throw</a> a <a>NotSupportedError</a> and abort all these steps.
3. If the result of <a>IsConstructor</a>(|animatorCtor|) is false, <a>throw</a> a
<a>TypeError</a> and abort all these steps.
4. Let |prototype| be the result of <a>Get</a>(|animatorCtor|, "prototype").
5. If the result of <a>Type</a>(|prototype|) is not Object, <a>throw</a> a <a>TypeError</a>
and abort all these steps.
6. Let |animate| be the result of <a>Get</a>(|prototype|, "animate").
7. If the result of <a>IsCallable</a>(|animate|) is false, <a>throw</a> a <a>TypeError</a> and
abort all these steps.
8. Let |elements| be the result of <a>parsing an animator slot list</a> for |animatorCtor|.
If an exception is thrown, rethrow the exception and abort all these steps.
9. Let |timelineOptions| be the result of <a>parsing an animator timeline options list</a> for
|animatorCtor|. If an exception is thrown, rethrow the exception and abort all these steps.
10. Let |definition| be a new <a>animator definition</a> with:
- <a>animator name</a> being |name|
- <a>class constructor</a> being |animatorCtor|
- <a>animate function</a> being |animate|
- <a>element proxy map</a> being |elements|
- <a>animator timeline options list</a> being |timelineOptions|
11. Add the key-value pair (|name| - |definition|) to the <a>animator name to animator
definition map</a> of the associated <a>document</a>.
When <dfn>parsing an animator slot list</dfn> for {{animatorCtor}}, the user agent <em>must</em>
run the following steps:
1. Let |elements| be an empty <code>sequence<Dictionary></code>.
1. Let |elementsIterable| be the result of <a>Get</a>(|animatorCtor|, |elements|).
2. If |elementsIterable| is not <em>undefined</em>, then for each |slot| in |elementsIterable| run the
following steps:
- Let |slotDictionary| be an empty dictionary.
- Let the |name| key of |slotDictionary| be the |name| value of |element|.
- Let the |inputProperties| key of |slotDictionary| be the result of
<a>parsing an animator property list</a> with name <em>inputProperties</em> for |slot|.
- Let the |outputProperties| key of |slotDictionary| be the result of
<a>parsing an animator property list</a> with name <em>outputProperties</em> for |slot|.
- Push |slotDictionary| onto |elements|.
When <dfn>parsing an animator property list</dfn> with name <em>name</em> for <em>slot</em>,
the user agent <em>must</em> run the following steps:
1. Let |properties| be an empty <code>sequence<DOMString></code>.
2. Let |propertiesIterable| be the result of <a>Get</a>(|slot|, |name|).
3. If |propertiesIterable| is not <em>undefined</em>, then set |properties| to the result of
converting |propertiesIterable| to a <code>sequence<DOMString></code>. If an
exception is thrown, rethrow the exception and abort all these steps.
When <dfn>parsing an animator timeline options list</dfn> for {{animatorCtor}},
the user agent <em>must</em> run the following steps:
1. Let |timelineOptions| be an empty <code>sequence<Object></code>.
2. Let |timelinesIterable| be the result of <a>Get</a>(|animatorCtor|, |timelines|).
3. For each |timeline| in |timelinesIterable|, run the following steps:
1. If <a>Type</a>(|timeline|) is {{DOMString}} let |timelineObject| be <code>{ type:
|timeline|, options: {} }</code>, otherwise let |timelineObject| be |timeline|.
2. Let |type| be the result of <a>Get</a>(|timelineObject|, "type").
3. Let |options| be the result of <a>Get</a>(|timelineObject|, "options").
4. Process |type| and |options| according to the first matching condition in the following:
<div class="switch">
: If |type| or |options| is <b>undefined</b>,
:: throw a <a>NotSupportedError</a> and abort all remaining steps.
: If |type| is "document",
:: Push a {{DocumentTimelineOptions}} created from |options| onto |timelineOptions|.
: If |type| is "scroll",
:: Push a {{ScrollTimelineOptions}} created from |options| onto |timelineOptions|.
: Otherwise,
:: throw a <a>NotSupportedError</a> and abort all remaining steps.
</div>
Creating an Animator {#creating-animator}
====================================================
Each <a>animator instance</a> lives in an {{AnimationWorkletGlobalScope}}. The
<a>animator instance</a> cannot be disposed arbitrarily (e.g., in the middle of running animation
as it may contain the scripted animation state.
The {{AnimationWorkletGlobalScope}} has an <dfn>(animator name, root element) to instance map</dfn>.
The map is populated when the user agent constructs a new <a>animator instance</a> in that scope.
To <dfn>create a new animator instance</dfn> given a |name|, |root element|, and |workletGlobalScope|,
the user agent <em>must</em> run the following steps:
1. Let the |definition| be the result of looking up |name| on the |workletGlobalScope|'s
<a>animator name to animator definition map</a>.
If |definition| does not exist abort the following steps.
2. Let |animatorInstanceMap| be |workletGlobalScope|'s <a>(animator name, root element) to
instance map</a>.
3. If an entry exists for |name| and |root element| within |animatorInstanceMap| then abort the
following steps.
4. Let |animatorCtor| be the <a>class constructor</a> of |definition|.
5. Let |animatorInstance| be the result of <a>Construct</a>(|animatorCtor|).
If <a>Construct</a> throws an exception, set the result of looking up |name| and
|root element| in |animatorInstanceMap| to <b>null</b>, and abort the following steps.
6. Set the following on |animatorInstance| with:
- <a>animator name</a> being |name|
- <a>animation requested flag</a> being <a>frame-current</a>
7. Set the result of looking up |name| and |root element| in |animatorInstanceMap| to
|animatorInstance|.
Creating an Element Proxy {#creating-element-proxy}
====================================================
<a>Element Proxy</a> objects are created by the user agent for elements which have either 'animator'
or 'animator-root' CSS properties. They cannot be constructed from user script. A single element may
have multiple <a>Element Proxy</a> objects associated with it; one per <a>animator instance</a>.
The {{ElementProxy}} interface exposes two maps: a read only map of proxied input properties to their
values, and a map of proxied output properties to their values. The input and output properties in
the maps are those specified in registerAnimator for the given <a>animator instance</a>.
<pre class='idl'>
[
Exposed=(AnimationWorklet),
] interface ElementProxy {
attribute StylePropertyMapReadOnly inputStyleMap;
attribute StylePropertyMapReadOnly outputStyleMap;
};
</pre>
Issue: Todo: Should we have NoInterfaceObject/Exposed here? How do we properly represent that this
cannot be constructed via javascript?
When user agent wants to <dfn>create an element proxy</dfn> given |element| with a given |animatorSlot|, it
<em>must</em> run the following steps:
1. Let |animatorInstance| be the <a>animator instance</a> associated with |element|
2. Let |workletGlobalScope| be the scope associated with |animatorInstance|.
3. Let |definition| be the result of looking up |name| on the |workletGlobalScope|'s
<a>animator name to animator definition map</a>.
4. Let |slot| be the result of finding the item with name |animatorSlot| in the
<a>animator slot list</a> of |definition|.
5. Let |inputProperties| be <a>animator input property list</a> of |slot|.
6. Let |outputProperties| be <a>animator output property list</a> of |slot|.
6. Let |proxy| be a new {{ElementProxy}} Object with:
- <a>proxied element</a> being |element|.
- {{outputStyleMap}} being a new {{StylePropertyMap}} for |outputProperties|.
7. <a>update proxy input style map</a> with |proxy| and |inputProperties|.
8. Associate |proxy| with |animatorInstance|.
9. Return proxy.
Note: Writing to the outputStyle map will update the effective value of the used style value of the
<a>proxied element</a>. This <em>may</em> happen asynchronously.
When the user agent wants to <dfn>update proxy input style map</dfn> given |proxy|, and
|properties|, it <em>must</em> run the following steps:
1. Let |styleMap| be a new {{StylePropertyMapReadOnly}} populated with <em>only</em> the
<a>computed value</a> of <a>proxied element</a> for properties listed in |properties|.
2. Set |proxy|'s inputStyleMap to |styleMap|.
When an element is removed, all proxies of that element are considered to be disconnected.
A disconnected proxy continues to have the last value from the <a>computed value</a>
of the <a>proxied element</a> properties, but setting values on its outputStyleMap will have no
effect.
Requesting Animation Frames {#requesting-animation-frames}
====================
Each <a>animator instance</a> has an associated <dfn>animation requested flag</dfn>. It must be
either <dfn>frame-requested</dfn> or <dfn>frame-current</dfn>. It is initially set to
<a>frame-current</a>. Various events can cause the <a>animation requested flag</a> to be set to
<a>frame-requested</a>.
[[#running-animators]] resets the <a>animation requested flag</a> on animators to
<a>frame-current</a>.
Element proxy creation/removal {#requesting-animation-frames-element-proxy}
------------------------------
When a new element proxy is assigned to or removed from an <a>animator instance</a>, that
<a>animator instance</a>'s <a>animation requested flag</a> should be set to <a>frame-requested</a>.
Change in an element's computed style {#requesting-animation-frames-computed-style}
-------------------------------------
When the computed style for an element changes, the user agent <em>must</em> run the following steps:
For each <a>element proxy</a> for that element, perform the following steps:
1. Let |proxy| be the current <a>element proxy</a> of the element.
2. Let |animator| be the |proxy|'s assigned animator.
3. Let |name| be the |animator|'s name.
4. Let |definition| be the result of looking up |name| on the |workletGlobalScope|'s
<a>animator name to animator definition map</a>.
5. Let |slot| be the result of finding the item with name |animatorSlot| in the
<a>animator slot list</a> of |definition|.
6. Let |inputProperties| be <a>animator input property list</a> of |slot|.
7. For each property in |inputProperties|, if the property’s computed value has changed,
set the <a>animation requested flag</a> on the |animator| to <a>frame-requested</a>.
Animator specifies a DocumentTimeline {#requesting-animation-frames-document-timeline}
-------------------------------------
If a {{DocumentTimelineOptions}} instance is present in an animator's <a>animator timeline options
list</a> then that animator's <a>animation requested flag</a> <em>must</em> be set to
<a>frame-requested</a> at the start of each animation frame.
Animator specifies a ScrollTimeline {#requesting-animation-frames-scroll-timeline}
-----------------------------------
If a {{ScrollTimelineOptions}} |instance| is present in an animator's <a>animator timeline options
list</a> then the user agent <em>must</em> run the following steps when the scroll position of the
nearest non-visible overflow ancestor to the animator’s <a>root element</a> changes:
1. For each {{ScrollTimelineOptions}} instance |options| in the animator's <a>animator timeline
options list</a> do the following steps:
1. Let |timeline| be a {{ScrollTimeline}} created from |options|, adding the nearest non-visible
overflow ancestor of the <a>root element</a> as the {{scrollSource}}.
2. If the <a>current time of the ScrollTimeline</a> |timeline| would have changed due to the
change in scroll position, set the animator's <a>animation requested flag</a> to
<a>frame-requested</a>.
Issue: Give each animator instance a re-used set of AnimationTimelines instead of creating them
specifically here and when running animations.
Issue: Need to add a definition for 'nearest non-visible overflow ancestor' to make sure all mentions
stay in sync.
Running Animators {#running-animators}
======================================================
When a user agent wants to produce a new animation frame, if for any <a>animator instance</a> the
associated <a>animation requested flag</a> is <a>frame-requested</a> then the the user agent
<em>must</em> <a>run animators</a> for the current frame.
Note: The user agent is not required to run animations on every 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 <dfn>run animators</dfn> in a given |workletGlobalScope|, it
<em>must</em> iterate over all pairs of animator name and root element as (|animatorName|,
|rootElement|). For each such pair:
1. Let the |definition| be the result of looking up |animatorName| on the |workletGlobalScope|'s
<a>animator name to animator definition map</a>.
If |definition| does not exist then abort the following steps.
2. Let |instance| be the result of looking up (|animatorName|, |rootElement|) in the
|workletGlobalScope|'s <a>(animator name, root element) to instance map</a>.
If |instance| does not exist then <a>create a new animator instance</a> for |animatorName|,
|rootElement|, and |workletGlobalScope|, and let |instance| be the result of that.
3. If |instance| still does not exist or |instance| is <b>null</b> then abort the following steps.
4. If the <a>animation requested flag</a> for |instance| is <a>frame-current</a> or the proxies
belonging to |instance| will not be visible within the visual viewport of the current frame
the user agent <em>may</em> abort all the following steps.
Issue: Consider giving user agents permission to skip running animator instances to throttle
slow animators.
5. Let |animateFunction| be |definition|'s <a>animate function</a>.
6. Let |timelines| be an empty <code>sequence<AnimationTimeline></code>.
7. For each |timelineOptions| in the <a>animator timeline options list</a> of |definition|, do the
following:
- If <a>Type</a>(|timelineOptions|) is {{DocumentTimelineOptions}}, push a new
{{DocumentTimeline}} created using |timelineOptions| to the |timelines| list.
- If <a>Type</a>(|timelineOptions|) is {{ScrollTimelineOptions}}, push a new {{ScrollTimeline}}
created using |timelineOptions| to the |timelines| list, adding the nearest non-visible
overflow ancestor of |rootElement| as the {{scrollSource}}.
8. Let |elementMap| be an <a>element proxy map</a> of |instance|.
9. <a>Invoke</a> |animateFunction| with arguments «|elementMap|, |timelines|»,
and with |instance| as the <a>callback this value</a>.
Note: Although inefficient, it is legal for the user agent to <a>run animators</a> multiple times
in the same frame.
Closing an Animator {#closing-animator}
====================================================
Issue: Todo: Define when we may get rid of the animator.
CSS Animator Notation {#css-animator-notation}
==============================================
Two CSS properties 'animator-root' and 'animator' may be used to assign an HTML elements to an
<a>animator instance</a> either as a root element or a child element.
<pre class='propdef'>
Name: animator-root
Value: none | [<a>animator name</a> <a>animator slot</a>, ]* <a>animator name</a> <a>animator slot</a>
Initial: none
Applies to: all elements
Inherited: no
Computed value: as specified
Percentages: N/A
Media: interactive
Animatable: no
</pre>
<dl dfn-type=value dfn-for=animator-root>
<dt><dfn>none</dfn>
<dd>
There will be no <a>animator instance</a> that has this element as its
<a>root element</a>.
<dt><a>animator name</a> <a>animator slot</a>
<dd>
For each <a>animator name</a> specified in the list, the following applies for each animation frame:
* If there is no <a>animator definition</a> registered with the given <a>animator name</a>, the inclusion
of that <a>animator name</a> as a value has no effect.
* Otherwise, an <a>animator instance</a> with this element as its <a>root element</a>
will exist and will have its <a>animate function</a> called (with respect to the rules
laid out in [[#running-animators]]) with an <a>element proxy</a> for this element passed as
one of the <a>element proxies</a> for the given <a>animator slot</a>.
If the same <a>animator name</a> occurs multiple times in the list, only the first occurrence of that
<a>animator name</a> is processed.
</dl>
<pre class='propdef'>
Name: animator
Value: none | [<a>animator name</a> <a>animator slot</a>, ]* <a>animator name</a> <a>animator slot</a>
Initial: none
Applies to: all elements
Inherited: no
Computed value: as specified
Percentages: N/A
Media: interactive
Animatable: no
</pre>
<dl dfn-type=value dfn-for=animator>
<dt><dfn>none</dfn>
<dd>
This element will not be passed into any <a>animate function</a> as one of the
<a>element proxies</a>.
<dt><a>animator name</a> <a>animator slot</a>
<dd>
For each <a>animator name</a> specified in the list, the following applies for each animation frame:
* If there is no <a>animator definition</a> registered with the given <a>animator name</a>, the inclusion
of that <a>animator name</a> as a value has no effect.
* Otherwise, an <a>element proxy</a> for this element will be passed into the
<a>animate function</a> of the closest ancestor <a>animator instance</a> with the given
<a>animator name</a> as one of the <a>element proxies</a> for the given <a>animator slot</a>. If no
such <a>animator instance</a> exists, one is created with the document root element as its
<a>root element</a>.
If the same <a>animator name</a> occurs multiple times in the list, only the first occurrence of that
<a>animator name</a> is processed.
</dl>
Effect Stack {#effect-stack}
============================
Issue: Todo: the animators output style values have the highest order in animation stack effect and
their composite operation is "replace" which is going to allow them to run independent on other
animations and in a different thread. Supporting other composite operation is not in scope at this
point.
Security Considerations {#security-considerations}
==================================================
Issue: Need to decide what security considerations there are for this spec.
Privacy Considerations {#privacy-considerations}
================================================
Issue: Need to decide what privacy considerations there are for this spec.
Examples {#examples}
====================
Example 1: A fade-in animation with spring timing. {#example-1}
-----------------------------------------
<pre class='lang-markup'>
<style>
.myFadein {
animator: 'spring' 'fadein';
}
</style>
<section class='myFadein'></section>
<section class='myFadein' style="--spring-k: 25;"></section>
<script>
document.animationWorklet.import('spring-timing.js');
</script>
</pre>
<pre class='lang-javascript'>
// Inside AnimationWorkletGlobalScope
registerAnimator('spring', class {
static elements = [
{name: 'fadein', inputProperties: ['--spring-k'], outputProperties = ['opacity']}];
static timelines = ['document'];
animate(elementMap, timelines) {
this.startTime_ = this.startTime_ || timelines[0].currentTime;
const deltaT = timelines[0].currentTime - this.startTime_;
elementMap.get('fadein').forEach(elem => {
// read a custom css property.
const k = elem.inputStyleMap.get('--spring-k') || 1;
// compute progress using a fancy spring timing function.
const effectiveValue = 1 * springTiming(deltaT, k);
// update opacity accordingly.
elem.ouputStyleMap.opacity = effectiveValue;
});
}
springTiming(deltaT, k) {
// simulate the spring effect and return a progress value between [-1, 1];
}
});
</pre>
Example 2: Twitter header. {#example-2}
--------------------------
<pre class='lang-markup'>
<style>
#scroller {
animator-root: twitter-header;
}
.avatar {
animator: twitter-header avatar;
}
.bar {
animator: twitter-header bar;
}
</style>
<div id="scroller">
<section class="bar"></section>
<header>
<picture class="avatar">
<img src="avatar.jpg">
</picture>
<section>
<button>Friends</button>
<button>Edit Profile</button>
</section>
</header>
</div>
<script>
document.animationWorklet.import('tweeter-header.js');
</script>
</pre>
<pre class='lang-javascript'>
// Inside AnimationWorkletGlobalScope.
registerAnimator('twitter-header', class {
static elements = [
{name: 'avatar', inputProperties: [], outputProperties: ['transform']},
{name: 'bar', inputProperties: [], outputProperties: ['opacity']}
];
// maps scroll offset [0, 140px] -> time values [0, 1]
static timelines = [ { type: 'scroll', options: {
orientation: 'vertical',
endScrollOffset: '140px',
timeRange: 1
}},
];
animate(elementMap, timelines) {
var progress = timelines[0].currentTime;
elementMap.get('avatar').forEach(elem => {
elem.outputStyleMap.set('transform', new CSSTransformValue([
new CSSTranslation(new CSSSimpleLength(0, 'px'),
new CSSSimpleLength(tween(140, 45)(progress), 'px')),
new CSSScale(tween(1, 0.5)(progress), tween(1, 0.5)(progress))
]));
});
elementMap.get('bar').forEach(elem => {
elem.outputStyleMap.set('opacity', progress);
});
}
tween(begin, end) {
return function lerp(progress) {
return begin + (end - begin) * progress;
}
}
});
</pre>
Example 3: Parallax backgrounds. {#example-3}
-----------------------------------------
<pre class='lang-markup'>
<style>
#scroller {
animator-root: 'parallax';
}
.background {
animator: 'parallax' 'background';
}
</style>
<div id="scroller">
<div class="background" style="--parallax-rate: 0.6" >
</div>
<div class="background" style="--parallax-rate: 0.3" >
</div>
overflowing content!
</div>
<script>
document.animationWorklet.import('parallax.js');
</script>
</pre>
<pre class='lang-javascript'>
// Inside AnimationWorkletGlobalScope
registerAnimation('parallax', class {
static elements = [
{ name: 'background', inputProperties: ['--parallax-rate'], outputProperties: ['transform']}
];
static timelines = [{ type: 'scroll', options: { orientation: 'vertical'} }];
// This can be passed in as an input but we should consider exposing this in ScrollTimeline
static SCROLL_SIZE = 1000;
animate(elementMap, timelines) {
// read root scroller vertical scroll offset.
const scrollTop = timelines[0].currentTime * SCROLL_SIZE;
// update parallax transform
elementMap.get('background').forEach(elem => {
const rate = elem.inputStyleMap.get('--parallax-rate') || 0.2;
elem.outputStyleMap.set('transform', new CSSTransformValue([
new CSSTranslation(new CSSSimpleLength(0, 'px'), new CSSSimpleLength(rate * scrollTop, 'px'))]));
});
}
});
</pre>