-
Notifications
You must be signed in to change notification settings - Fork 636
/
Overview.bs
executable file
·1214 lines (929 loc) · 40.1 KB
/
Overview.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
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
<link href='web-animations.css' rel='stylesheet' type='text/css'>
<pre class='metadata'>
Title: Scroll-linked Animations
Group: CSSWG
Status: ED
Work Status: exploring
Level: 1
Group: CSSWG
URL: https://drafts.csswg.org/scroll-animations-1/
ED: https://drafts.csswg.org/scroll-animations-1/
Shortname: scroll-animations-1
Abstract: Defines an API and markup for creating animations that are tied to
the scroll offset of a scroll container.
Editor: Brian Birtles, Invited Expert, brian@birchill.co.jp, w3cid 43194
Editor: Botond Ballo, Mozilla, botond@mozilla.com
Editor: Antoine Quint, Apple, graouts@apple.com, w3cid 51377
Editor: Majid Valipour, Google, majidvp@google.com, w3cid 81464
Editor: Olga Gerchikov, Microsoft, gerchiko@microsoft.com
Former editor: Mantaroh Yoshinaga
Former editor: Stephen McGruer, Google, smcgruer@google.com
</pre>
<pre class=anchors>
urlPrefix: https://w3c.github.io/web-animations/; type: dfn; spec: web-animations
text: animation; url: concept-animation
text: current time
text: default document timeline
text: duration
text: inactive timeline
text: start delay
text: target effect end
text: timeline
text: phase; url: timeline-phase
text: inactive phase; url: timeline-inactive-phase
text: before phase; url: timeline-before-phase
text: active phase; url: timeline-active-phase
text: after phase; url: timeline-after-phase
urlPrefix: https://drafts.csswg.org/cssom-view/; type: dfn; spec: cssom-view-1
text: CSS layout box
text: overflow direction; url: overflow-directions
urlPrefix: https://html.spec.whatwg.org/multipage/browsers.html; type: dfn; spec: html
text: document associated with a window; url: concept-document-window
</pre>
<pre class=link-defaults>
spec:html; type:dfn; for:/; text:browsing context
spec:html; type:method; text:requestAnimationFrame()
</pre>
# Introduction # {#intro}
This specification defines mechanisms for
[[#scroll-driven-animations|driving the progress of an animation]] based
on the scroll progress of a scroll container.
## Relationship to other specifications ## {#other-specs}
Web Animations [[WEB-ANIMATIONS-1]] defines an abstract conceptual model for
animations on the Web platform, with elements of the model including
[=animations=] and their [=timelines=],
and associated programming interfaces.
This specification extends this model by defining a new type of animation [=timeline=]:
a [=scroll timeline=].
This specification defines both programming interfaces for interacting with these
concepts, as well as CSS markup which applies these concepts to CSS Animations
[[CSS3-ANIMATIONS]].
The behavior of the CSS markup is described in terms of the programming interfaces.
User agents that do not support script may still implement the CSS markup
provided it behaves as if the underlying programming interfaces were in place.
## Relationship to asynchronous scrolling ## {#async-scrolling}
Some user agents support scrolling that is asynchronous with respect to layout
or script. This specification is intended to be compatible with such an
architecture.
Specifically, this specification allows expressing scroll-linked effects in a
way that does not require script to run each time the effect is sampled. User
agents that support asynchronous scrolling are allowed (but not required) to
sample such effects asynchronously as well.
## Value Definitions ## {#values}
This specification follows the <a href="https://www.w3.org/TR/CSS2/about.html#property-defs">CSS property definition conventions</a> from [[!CSS2]]
using the <a href="https://www.w3.org/TR/css-values-3/#value-defs">value definition syntax</a> from [[!CSS-VALUES-3]].
Value types not defined in this specification are defined in CSS Values & Units [[!CSS-VALUES-3]].
Combination with other CSS modules may expand the definitions of these value types.
In addition to the property-specific values listed in their definitions,
all properties defined in this specification
also accept the <a>CSS-wide keywords</a> keywords as their property value.
For readability they have not been repeated explicitly.
# Use cases # {#use-cases}
<em>This section is non-normative</em>
Note: Based on this <a
href="https://github.com/w3c/csswg-drafts/blob/master/scroll-animations-1/Use%20cases.md">curated
list of use cases</a>.
Issue(4354): These use cases need updating.
## Scrollable picture-story show ## {#scrollable-animation-usecase}
It is sometimes desired to use an animation to tell a story where the user
controls the progress of the animation by scrolling or some other
gesture. This may be because the animation contains a lot of textual
information which the user may wish to peruse more slowly, it may be for
accessibility considerations to accommodate users who are uncomfortable
with rapid animation, or it may simply be to allow the user to easily
return to previous parts of the story such as a story that introduces
a product where the user wishes to review previous information.
The following (simplified) example shows two balls colliding. The
animation is controlled by scroll position allowing the user to easily
rewind and replay the interaction.
<figure>
<img src="img/usecase3-1.svg" width="600"
alt="Use case: The picture-story show.">
<figcaption>
A scrollable movie.<br>
The left figure shows the initial position of the balls<br>
The right figure shows them after they have collided.
</figcaption>
</figure>
Using the CSS markup:
<pre class='lang-css'>
@media (prefers-reduced-motion: no-preference) {
div.circle {
animation-duration: 1s;
animation-timing-function: linear;
animation-timeline: collision-timeline;
}
#left-circle {
animation-name: left-circle;
}
#right-circle {
animation-name: right-circle;
}
#union-circle {
animation-name: union-circle;
animation-fill-mode: forwards;
animation-timeline: union-timeline;
}
@scroll-timeline collision-timeline {
source: selector(#container);
orientation: block;
start: 200px;
end: 300px;
}
@scroll-timeline union-timeline {
source: selector(#container);
orientation: block;
start: 250px;
end: 300px;
}
@keyframes left-circle {
to { transform: translate(300px) }
}
@keyframes right-circle {
to { transform: translate(350px) }
}
@keyframes union-circle {
to { opacity: 1 }
}
}
</pre>
Using the programming interface, we might write this as:
<pre class='lang-javascript'>
if (window.matchMedia('(prefers-reduced-motion: no-preference)').matches) {
const scrollableElement = document.querySelector('#container');
const collisionTimeline = new ScrollTimeline({
source: scrollableElement,
start: '200px',
end: '300px'
});
const left = leftCircle.animate({ transform: 'translate(300px)' }, 1000);
left.timeline = collisionTimeline;
const right = leftCircle.animate({ transform: 'translate(350px)' }, 1000);
right.timeline = collisionTimeline;
const union = unionCircle.animate({ opacity: 1 }, { duration: 1000, fill: "forwards" });
union.timeline = new ScrollTimeline({
source: scrollableElement,
start: '250px',
end: '300px'
});
}
</pre>
## The content progress bar ## {#content-progress-bar-usecase}
Another common example of an animation that tracks scroll position is a
progress bar that is used to indicate the reader's position in a long
article.
<figure>
<img src="img/usecase3-2.svg" width="600"
alt="Use case: Scroll based styling">
<figcaption>
Content progress bar.<br>
The left figure shows the initial state before scrolling.<br>
The right figure shows the progress bar is half-filled in since the
user has scrolled half way through the article.
</figcaption>
</figure>
Typically, the scroll bar provides this visual indication but
applications may wish to hide the scroll bar for aesthetic or useability
reasons.
Using the updated 'animation' shorthand that includes 'animation-timeline',
this example could be written as follows:
<pre class='lang-css'>
@media (prefers-reduced-motion: no-preference) {
@scroll-timeline progress-timeline {
source: selector(#body);
}
@keyframes progress {
to { width: 100%; }
}
#progress {
width: 0px;
height: 30px;
background: red;
animation: 1s linear forwards progress progress-timeline;
}
}
</pre>
If we use this API for this case, the example code will be as follow:
<pre class='lang-javascript'>
if (window.matchMedia('(prefers-reduced-motion: no-preference)').matches) {
var animation = div.animate({ width: '100%' }, { duration: 1000, fill: "forwards" });
animation.timeline = new ScrollTimeline(
{ start: '0px' }
);
}
</pre>
## Combination scroll and time-base animations ## {#combination-scroll-and-time-base-animations-usecase}
### Photo viewer ### {#movie-show-case-usecase}
Advisement: We are currently reworking this use case
<!--
Maybe the developer will want to use the scroll based timeline and the time-based timeline.
Here's an example content which showing the photos.
If scroll position is out of specified range, the animation of the slideshow will start. The progress of this slideshow is related to scroll volume. And if scroll position is within the specified range, the animation of the slideshow will continue automatically.
<figure>
<img src="img/usecase4.svg" width="600"
alt="Use case 4: Scrollable slide show.">
<figcaption>
Use case 4: Scrollable slide show.<br>
The left figure is before scroll, the slide show will start as scroll-linked animation.<br>
The right figure is after scroll, the slide show will start as related to the time animation.
</figcaption>
</figure>
This content can't build the CSS only.
<pre class='lang-javascript'>
if (window.matchMedia('(prefers-reduced-motion: no-preference)').matches) {
var animation = slideTarget.getAnimation()[0];
var scrollTimeline = new ScrollTimeline({
source: scrollableElement,
orientation: "vertical",
scrollOffset: '0px',
end: '200px'
});
animation.timeline = scrollTimeline;
// We use scroll event in order to change the timeline.
scrollableElement.addEventListener("scroll", function(evt) {
if ((scrollableElement.scrollTop > 200) && animation.timeline != document.timeline) {
animation.timeline = document.timeline;
} else if ((scrollableElement.scrollTop < 200) && animation.timeline == document.timeline) {
animation.timeline = scrollTimeline;
}
});
}
</pre>
-->
</div>
# Scroll-driven animations # {#scroll-driven-animations}
## Scroll timelines ## {#scroll-timelines}
### The {{ScrollDirection}} enumeration ### {#scrolldirection-enumeration}
<pre class="idl">
enum ScrollDirection {
"block",
"inline",
"horizontal",
"vertical"
};
</pre>
The {{ScrollDirection}} enumeration specifies a direction of scroll of a
scrollable element.
: <code>block</code>
:: Selects the direction along the [=block axis=], conforming to writing mode
and directionality.
: <code>inline</code>
:: Selects the direction along the [=inline axis=], confirming to writing mode
and directionality.
: <code>horizontal</code>
:: Selects the physical horizontal direction (ignoring writing mode and
directionality).
: <code>vertical</code>
:: Selects the physical vertical direction (ignoring writing mode and
directionality).
Note: Having both logical (block/inline) and physical (vertical/horizontal)
directions allows web developers to animate both logical (e.g.
margin-inline-start) and physical (e.g. transform) properties with good
behavior under different directionalities and writing modes.
### The {{ScrollTimeline}} interface ### {#scrolltimeline-interface}
<pre class="idl">
enum ScrollTimelineAutoKeyword { "auto" };
dictionary ScrollTimelineOptions {
Element? source = null;
ScrollDirection orientation = "block";
(DOMString or ElementBasedOffset) start = "auto";
(DOMString or ElementBasedOffset) end = "auto";
(double or ScrollTimelineAutoKeyword) timeRange = "auto";
};
[Exposed=Window]
interface ScrollTimeline : AnimationTimeline {
constructor(optional ScrollTimelineOptions options = {});
readonly attribute Element? source;
readonly attribute ScrollDirection orientation;
readonly attribute (DOMString or ElementBasedOffset) start;
readonly attribute (DOMString or ElementBasedOffset) end;
readonly attribute (double or ScrollTimelineAutoKeyword) timeRange;
};
</pre>
A <dfn>scroll timeline</dfn> is an {{AnimationTimeline}} whose time values are
determined not by wall-clock time, but by the progress of scrolling in a
[=scroll container=].
<div link-for-hint="ScrollTimeline">
<div class="constructors">
: <dfn constructor for=ScrollTimeline lt="ScrollTimeline(options)">ScrollTimeline(options)</dfn>
:: Creates a new {{ScrollTimeline}} object using the following procedure:
1. Let |timeline| be a new {{ScrollTimeline}} object.
1. Let |source| be the result corresponding to the first matching condition
from the following:
<div class="switch">
: If the <code>source</code> member of |options| is missing,
:: The {{scrollingElement}} of the {{Document}} <a lt="document
associated with a window">associated</a> with the {{Window}} that is
the <a>current global object</a>.
: Otherwise,
:: The <code>source</code> member of |options|.
</div>
1. Set the {{ScrollTimeline/source}} of |timeline| to |source|.
1. Assign the {{ScrollTimeline/orientation}}, {{ScrollTimeline/start}},
{{ScrollTimeline/end}}, and {{ScrollTimeline/timeRange}} properties of
|timeline| to the corresponding value from |options|.
</div>
<div class="attributes">
: <dfn attribute for=ScrollTimeline>source</dfn>
:: The scrollable element whose scrolling triggers the activation and drives
the progress of the timeline.
: <dfn attribute for=ScrollTimeline>orientation</dfn>
:: Determines the direction of scrolling which triggers the activation and
drives the progress of the timeline.
: <dfn attribute for=ScrollTimeline>start</dfn>
:: A [=scroll timeline offset=] which determines the [=effective scroll
offset=] in the direction specified by {{orientation}} that constitutes the
beginning of the range in which the timeline is active.
: <dfn attribute for=ScrollTimeline>end</dfn>
:: A [=scroll timeline offset=] which determines the [=effective scroll
offset=] in the direction specified by {{orientation}} that constitutes the
end of the range in which the timeline is active.
: <dfn attribute for=ScrollTimeline>timeRange</dfn>
:: A time duration that allows mapping between a distance scrolled, and
quantities specified in time units, such as an animation's [=duration=] and
[=start delay=].
Conceptually, {{timeRange}} represents the number of milliseconds to map to
the scroll range defined by {{start}} and {{end}}. As a result, this value
does not have a correspondence to wall-clock time.
This value is used to compute the timeline's [=effective time range=], and
the mapping is then defined by mapping the scroll distance from
{{start}} to {{end}}, to the [=effective time range=].
Issue(4862): We are working to remove the need for {{timeRange}} to be declared.
The most recent work on this involved introduction of the concept of
"progress-based animations" to web animations.
</div>
### Scroll Timeline Offset ### {#scroll-timeline-offset-section}
An <dfn>effective scroll offset</dfn> is a scroll position for a given [=scroll
container=] and on a given scroll direction.
A <dfn>scroll timeline offset</dfn> is provided by authors and determines a
[=effective scroll offset=] for the {{source}} and in the direction specified by
{{orientation}}.
There are two types of scroll timeline offset: [=container-based offset=], and
[=element-based offset=]. To <dfn>resolve a scroll timeline offset</dfn> into an
[=effective scroll offset=], run the procedure to [=resolve a container-based
offset=] or to [=resolve an element-based offset=] depending on the offset type.
It is possible for a [=scroll timeline offset=] to be resolved to null.
The <dfn>effective start offset</dfn> is the [=effective scroll offset=] that is
resolved from {{start}}. The <dfn>effective end offset</dfn> is
the [=effective scroll offset=] that is resolved from {{end}}.
#### Container-based Offset #### {#container-based-offset-section}
A <dfn>container-based offset</dfn> is a scroll timeline offset that is declared
only in relation with the <a>scroll container</a> as specified by {{source}}.
A [=container-based offset=] is provided in the {{DOMString}} form and can have
one the following values:
* auto
* <<length-percentage>>
The procedure to <dfn>resolve a container-based offset</dfn> given
<var>offset</var> is as follows:
1. If <em>any</em> of the following are true:
* {{source}} is null, or
* {{source}} does not currently have a [=CSS layout box=], or
* {{source}}'s layout box is not a [=scroll container=].
The [=effective scroll offset=] is null and abort remaining steps.
1. The [=effective scroll offset=] is the scroll offset corresponding to the
first matching condition from the following:
<div class="switch">
: <var>offset</var> is <code>auto</code>
:: Either the beginning or the ending of {{source}}'s scroll range in
{{orientation}} depending on whether the offset is {{start}} or {{end}}.
: <var>offset</var> is a <<length-percentage>>
:: The distance indicated by the value along {{source}}'s scroll range in
{{orientation}} as expressed by absolute length, a percentage, or a
''calc()'' expression that resolves to a <<length>>.
</div>
Note: The scroll range of an element is the range defined by its minimum and
maximum scroll offsets which are determined by it [=scrolling box=], [=padding
box=], and [=overflow direction=].
Note: It is valid to provide a length or percentage based offset such that it is
outside the source's scroll range and thus not reachable e.g., '120%'.
#### Element-based Offset #### {#element-based-offset-section}
An <dfn>element-based offset</dfn> is a scroll timeline offset that is declared
in terms of the intersection of the <a>scroll container</a> as specified by
{{source}} and one of its descendents as specified by {{target}}.
An [=element-based offset=] is provided in the {{ElementBasedOffset}} form.
<pre class="idl">
enum Edge { "start", "end" };
dictionary ElementBasedOffset {
Element target;
Edge edge = "start";
double threshold = 0.0;
};
</pre>
<div class=members>
: <dfn dict-member for=ElementBasedOffset>target</dfn>
:: The target whose intersection with {{source}}'s [=scrolling box=] determines
the concrete scroll offset.
: <dfn dict-member for=ElementBasedOffset>edge</dfn>
:: The edge of {{source}}'s [=scrolling box=] in the direction specified by
the {{orientation}} which the target should intersect with.
: <dfn dict-member for=ElementBasedOffset>threshold</dfn>
:: A double in the range of [0.0, 1.0] that represent the percentage
of the target that is expected to be visible in {{source}}'s [=scrollport=]
at the intersection offset.
Issue(5203): The range of the <code>threshold</code> member is not currently
checked anywhere.
</div>
The procedure to <dfn>resolve an element-based offset</dfn> given
<var>offset</var> is as follows:
1. If <em>any</em> of the following are true:
* {{source}} is null, or
* {{source}} does not currently have a [=CSS layout box=], or
* {{source}}'s layout box is not a [=scroll container=].
The [=effective scroll offset=] is null and abort remaining steps.
1. Let <var>target</var> be <var>offset</var>'s {{target}}.
1. If <em>any</em> of the following are true:
* <var>target</var> is null, or
* <var>target</var> does not currently have a [=CSS layout box=].
The [=effective scroll offset=] is null and abort remaining steps.
1. If <var>target</var> 's nearest [=scroll container=] ancestor
is not {{source}}
abort remaining steps
since the [=effective scroll offset=] is null.
1. Let <var>container box</var> be the {{source}}'s [=scrollport=].
1. Let <var>target box</var> be the result of finding
the rectangular bounding box
(axis-aligned in {{source}}’s coordinate space)
of <var>target</var>'s transformed border box.
1. If <var>offset</var>'s {{edge}} is 'start' then
let <var>scroll offset</var>
be the scroll offset at which <var>container box</var>'s start edge is flush
with the <var>target box</var>'s end edge
in the axis and direction determined by {{orientation}}.
1. If <var>offset</var>'s {{edge}} is 'end' then
let <var>scroll offset</var>
be the scroll offset at which <var>container box</var>'s end edge is flush
with the <var>target box</var>'s start edge
in the axis and direction determined by {{orientation}}.
1. Let <var>threshold amount</var> be the result of evaluating the following
expression where <var>target dimension</var> is <var>target box</var>'s dimension in the axis
determined by {{orientation}}.
<blockquote>
<code><var>threshold amount</var> = {{threshold}} × <var>target dimension</var></code>
</blockquote>
1. Adjust <var>scroll offset</var> by <var>threshold amount</var> as follow:
<div class="switch">
: If <var>offset</var>'s {{edge}} is 'start',
:: <var>scroll offset</var> = <var>scroll offset</var> - <var>threshold amount</var>.
: Otherwise (<var>offset</var>'s {{edge}} is 'end'),
:: <var>scroll offset</var> = <var>scroll offset</var> + <var>threshold amount</var>.
</div>
1. Clamp the value of <var>scroll offset</var> to be within the {{source}}'s
scroll range.
1. The [=effective scroll offset=] is <var>scroll offset</var>
<div class="note">
Note: With threshold 0.0, the algorithm selects the effective scroll offset such
that the target is adjacent to the [=scrollport=] at the given edge but not yet
visible.
The threshold value allows authors
to control the amount of target that needs to be visible in [=scrollport=].
In particular threshold value 1.0 ensure that target is fully visible
(as long as [=scrollport=] is large enough).
<figure>
<img src="img/example-element-based-threshold.svg" width="600"
alt="Example usage of element-based offset.">
<figcaption>
Threshold controls how much of target should be visible in [=scrollport=].
</figcaption>
</figure>
</div>
<div class="example">
Here is a basic example showing how element-based offsets can be used to declare
an scroll-linked animation that occurs when an element enters the scroller
scrollport and ends once it exits the scrollport.
<figure>
<img src="img/example-element-based.svg" width="600"
alt="Example usage of element-based offset.">
<figcaption>
Usage of element-based offsets to create enter/exit triggers.<br>
The left figure shows the scroller and target being aligned at 'end' edge.<br>
The right figure shows them being aligned at 'start' edge.
</figcaption>
</figure>
Note that here we are expecting a typical top to bottom scrolling and thus
consider the entrance to coincide when target's start edge is flushed with
scrollport's <strong>end edge</strong> and viceversa for exit.
<pre class='lang-javascript'>
if (window.matchMedia('(prefers-reduced-motion: no-preference)').matches) {
const scrollableElement = document.querySelector('#container');
const image = document.querySelector('#image');
const timeline = new ScrollTimeline({
source: scrollableElement,
start: {target: image, edge: 'end'},
end: {target: image, edge: 'start'},
});
const slideIn = target.animate({
transform: ['translateX(0)', 'translateX(50vw)'],
opacity: [0, 1]
}, {
timeline:timeline,
duration: 1000
}
);
}
</pre>
The same logic can be done in CSS markup:
<pre class="lang-css">
@media (prefers-reduced-motion: no-preference) {
@keyframes slide-in {
from {
transform: translateX(0);
opacity: 0;
}
to {
transform: translateX(50vw);
opacity: 1;
}
}
@scroll-timeline image-in-scrollport {
source: selector(#container);
start: selector(#image) end;
end: selector(#image) start;
}
#target {
animation-name: slide-in;
animation-duration: 1s;
animation-timeline: image-in-scrollport;
}
}
</pre>
</div>
### The effective time range of a {{ScrollTimeline}} ### {#effective-time-range-algorithm}
The <dfn>effective time range</dfn> of a {{ScrollTimeline}} is calculated as
follows:
<div class="switch">
: If the {{timeRange}} has the value <code>"auto"</code>,
:: The [=effective time range=] is the maximum value of the
[=target effect end=] of all animations
directly associated with this timeline.
If any animation directly associated with the timeline has a
[=target effect end=] of infinity, the [=effective time range=]
is zero.
: Otherwise,
:: The [=effective time range=] is the {{ScrollTimeline}}'s
{{timeRange}}.
</div>
### The effective scroll range of a {{ScrollTimeline}} ### {#effective-scroll-range-algorithm}
The procedure to calculate <dfn>effective scroll range</dfn> of a
{{ScrollTimeline}} is as follows:
1. Run the procedure to [=resolve a scroll timeline offset=] for both {{start}}
and {{end}}.
1. Calculate [=effective scroll range=] as follow:
<div class="switch">
: If [=effective start offset=] or [=effective end offset=] is null.
:: The [=effective scroll range=] is null.
: Otherwise
:: The [=effective scroll range=] is the result of evaluating the following
expression:
<blockquote>
<code>[=effective end offset=] - [=effective start offset=]</code>
</blockquote>
</div>
### The phase of a {{ScrollTimeline}} ### {#phase-algorithm}
The [=phase=] of a {{ScrollTimeline}} is calculated as follows:
1. If <em>any</em> of the following are true:
* {{source}} is null, or
* {{source}} does not currently have a [=CSS layout box=], or
* {{source}}'s layout box is not a [=scroll container=], or
* [=effective scroll range=] is null.
The [=phase=] is [=inactive phase|inactive=] and abort remaining steps.
1. Let <var>current scroll offset</var> be the current scroll offset of
{{source}} in the direction specified by {{orientation}}.
1. The [=phase=] is the result corresponding to the first matching condition
from below:
<div class="switch">
: If <var>current scroll offset</var> is less than [=effective start offset=]:
:: The [=phase=] is [=before phase|before=]
: If <var>current scroll offset</var> is greater than or equal to [=effective
end offset=] <em>and</em> [=effective end offset=] is less than the maximum
scroll offset of {{source}} in {{orientation}}:
:: The [=phase=] is [=after phase|after=]
Note: In web animations, in general ranges are normally exclusive of their end
point. But there is an exception here for the scroll timeline active range as it
may in some cases be inclusive of its end. In particular if the timeline end
offset is the maximum scroll offset we include it in active range because it is
not possible for user to scroll passed this point and not including this value
in the active range would leave to animations that would not be active at the
very last scroll position.
: Otherwise,
:: The [=phase=] is [=active phase|active=].
</div>
### The current time of a {{ScrollTimeline}} ### {#current-time-algorithm}
The [=current time=] of a {{ScrollTimeline}} is calculated as follows:
1. If <em>any</em> of the following are true:
* {{source}} is null, or
* {{source}} does not currently have a [=CSS layout box=], or
* {{source}}'s layout box is not a [=scroll container=], or
* [=effective scroll range=] is null.
The [=current time=] is an unresolved time value and abort remaining steps.
1. Let <var>current scroll offset</var> be the current scroll offset of
{{source}} in the direction specified by {{orientation}}.
1. The [=current time=] is the result corresponding to the first matching
condition from below:
<div class="switch">
: If <var>current scroll offset</var> is less than [=effective start offset=]:
:: The [=current time=] is 0.
: If <var>current scroll offset</var> is greater than or equal to [=effective
end offset=]:
:: The [=current time=] is the [=effective time range=].
: Otherwise,
:: The [=current time=] is the result of evaluating the following expression:
<blockquote>
<code>(<var>current scroll offset</var> - [=effective start offset=]) / [=effective scroll range=] × [=effective time range=]</code>
</blockquote>
</div>
Note: To be considered active a scroll timeline requires its [=effective start
offset=] and its [=effective end offset=] to be non-null. This means that for
example if one uses an element-based offset whose {{target}} is not a descendant
of the scroll timeline {{source}}, the timeline remains inactive.
## The '@scroll-timeline' at-rule ## {#scroll-timeline-at-rule}
[=Scroll Timelines=] are specified in CSS using the <dfn>@scroll-timeline</dfn>
at-rule, defined as follows:
<pre>
@scroll-timeline = @scroll-timeline <<timeline-name>> { <<declaration-list>> }
</pre>
An ''@scroll-timeline'' rule has a name given by the <<custom-ident>> or <<string>> in
its prelude. The two syntaxes are equivalent in functionality; the name is the
value of the ident or string. As normal for <<custom-ident>>s and <<string>>s,
the names are fully case-sensitive; two names are equal only if they are
codepoint-by-codepoint equal. The <<custom-ident>> additionally excludes the
none keyword.
Once specified, a scroll timeline may be associated with a CSS Animation
[[CSS3-ANIMATIONS]] by using the 'animation-timeline' property.
The <<declaration-list>> inside of ''@scroll-timeline'' rule can only contain the
descriptors defined in this section.
An ''@scroll-timeline'' rule is invalid if it occurs in a stylesheet inside of a
[=shadow tree=], and must be ignored.
Issue(5167): This will likely change in the future.
### Scroll Timeline descriptors ### {#scroll-timeline-descriptors}
<pre class='descdef'>
Name: source
For: @scroll-timeline
Value: selector( <<id-selector>> ) | auto | none
Initial: auto
</pre>
'source' descriptor determines the scroll timeline's {{source}}.
The value of {{source}} is the result corresponding to the first matching
condition from the following:
<div class="switch">
: If 'source' is a 'selector()'
:: The [=scroll container=] identified by the <<id-selector>>.
: If 'source' is <code>auto</code>
:: The {{scrollingElement}} of the {{Document}} <a lt="document associated with
a window">associated</a> with the {{Window}} that is the
<a>current global object</a>.
: Otherwise ('source' is <code>none</code>)
:: null.
</div>
Issue(4338): Consider choosing animation target's nearest scrollable ancestor
instead of document's scrolling Element for <code>auto</code>.
<pre class='descdef'>
Name: orientation
For: @scroll-timeline
Value: auto | block | inline | horizontal | vertical
Initial: auto
</pre>
'orientation' descriptor determines the scroll timeline's {{orientation}}.
<pre class='descdef'>
Name: start
For: @scroll-timeline
Value: <<scroll-timeline-offset>>
Initial: auto
</pre>
'start' descriptor determines the scroll timeline's {{start}}.
[=Scroll timeline offsets=] in CSS are represented by the
<<scroll-timeline-offset>> type:
<pre>
<dfn><scroll-timeline-offset></dfn> = auto | <<length-percentage>> | <<element-offset>>
<dfn><element-offset></dfn> = selector( <<id-selector>> ) [<<element-offset-edge>> || <<number>>]?
<dfn><element-offset-edge></dfn> = start | end
</pre>
The offset type depends on the value of <<scroll-timeline-offset>> per
following:
<div class="switch">
: If value is "auto" or of type <<length-percentage>>
:: The [=scroll timeline offset=] is a [=container-based offset=] with the same
value.
: If value is of type <<element-offset>>
:: The [=scroll timeline offset=] is an [=element-based offset=] with the
following member values:
* {{ElementBasedOffset/target}} is the element identified by
<<id-selector>>.
* {{ElementBasedOffset/edge}} is the optional value of
<<element-offset-edge>>. If not provided it defaults to "start".
* {{ElementBasedOffset/threshold}} is the optional value <<number>>. If
not provided it defaults to 0.
</div>
<pre class='descdef'>
Name: end
For: @scroll-timeline
Value: <<scroll-timeline-offset>>
Initial: auto
</pre>
'end' descriptor determines the scroll timeline's {{end}}.
<pre class='descdef'>
Name: time-range
For: @scroll-timeline
Value: auto | <<time>>
Initial: auto
</pre>
'time-range' descriptor determines the scroll timeline's {{timeRange}}.
</div> <!-- link-for-hint="ScrollTimeline" -->
### The <dfn interface>CSSScrollTimelineRule</dfn> Interface ### {#the-css-scroll-timeline-rule-interface}
<pre class='idl' export>
[Exposed=Window]
interface CSSScrollTimelineRule : CSSRule {
readonly attribute CSSOMString name;
readonly attribute CSSOMString source;
readonly attribute CSSOMString orientation;
readonly attribute CSSOMString start;
readonly attribute CSSOMString end;
readonly attribute CSSOMString timeRange;
};
</pre>
<dl dfn-for=CSSScrollTimelineRule dfn-type=attribute>
<dt><dfn>name</dfn></dt>
<dd>
The name associated with the ''@scroll-timeline'' rule.
</dd>
<dt><dfn>source</dfn></dt>
<dd>
The 'source' descriptor associated with the ''@scroll-timeline'', or "auto"
if not specified.
</dd>
<dt><dfn>orientation</dfn></dt>
<dd>
The 'orientation' descriptor associated with the ''@scroll-timeline'', or "auto" if not specified.