Skip to content

Commit 5a49df2

Browse files
authored
[scroll-animations-1] Add Element-based offset (w3c#4337) (w3c#5124)
Add basic definition for Element-based offsets Major changes: - Introduce concept of "scroll timeline offset" that can be container-based (existing concept) and element-based (new concept). - Add IDL for the new offset type and use it. - Define the process for each offset type to be resolved into an effective scroll offset. - Update current time calculation to resolve offsets and use the effective values. - Add basic diagram to show the behavior visually for a simple example Minor changes: - Rewrap lines to fit in 80 chars - Trim end-of-line whitespace - Clarify some definitions TODO (as follow ups): - Define threshold for element-based offset. - Add css syntax for element-based offsets. - Add more examples.
1 parent 66cc51e commit 5a49df2

2 files changed

Lines changed: 348 additions & 59 deletions

File tree

scroll-animations-1/Overview.bs

Lines changed: 250 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ Group: CSSWG
99
URL: https://drafts.csswg.org/scroll-animations-1/
1010
ED: https://drafts.csswg.org/scroll-animations-1/
1111
Shortname: scroll-animations-1
12-
Abstract: Defines an API and markup for creating animations that are tied to
12+
Abstract: Defines an API and markup for creating animations that are tied to
1313
the scroll offset of a scroll container.
1414
Editor: Brian Birtles, Invited Expert, brian@birchill.co.jp, w3cid 43194
1515
Editor: Botond Ballo, Mozilla, botond@mozilla.com
@@ -32,6 +32,7 @@ urlPrefix: https://w3c.github.io/web-animations/; type: dfn; spec: web-animation
3232
text: timeline
3333
urlPrefix: https://drafts.csswg.org/cssom-view/; type: dfn; spec: cssom-view-1
3434
text: CSS layout box
35+
text: overflow direction; url: overflow-directions
3536
urlPrefix: https://html.spec.whatwg.org/multipage/browsers.html; type: dfn; spec: html
3637
text: document associated with a window; url: concept-document-window
3738
</pre>
@@ -216,7 +217,7 @@ Typically, the scroll bar provides this visual indication but
216217
applications may wish to hide the scroll bar for aesthetic or useability
217218
reasons.
218219

219-
Using the updated 'animation' shorthand that includes 'animation-timeline',
220+
Using the updated 'animation' shorthand that includes 'animation-timeline',
220221
this example could be written as follows:
221222

222223
<pre class='lang-css'>
@@ -344,8 +345,8 @@ enum ScrollTimelineAutoKeyword { "auto" };
344345
dictionary ScrollTimelineOptions {
345346
Element? source = null;
346347
ScrollDirection orientation = "block";
347-
DOMString start = "auto";
348-
DOMString end = "auto";
348+
(DOMString or ElementBasedOffset) start = "auto";
349+
(DOMString or ElementBasedOffset) end = "auto";
349350
(double or ScrollTimelineAutoKeyword) timeRange = "auto";
350351
};
351352

@@ -360,8 +361,9 @@ interface ScrollTimeline : AnimationTimeline {
360361
};
361362
</pre>
362363

363-
A <dfn>scroll timeline</dfn> is an {{AnimationTimeline}} whose time values are determined
364-
not by wall-clock time, but by the progress of scrolling in a [=scroll container=].
364+
A <dfn>scroll timeline</dfn> is an {{AnimationTimeline}} whose time values are
365+
determined not by wall-clock time, but by the progress of scrolling in a
366+
[=scroll container=].
365367

366368
<div link-for-hint="ScrollTimeline">
367369

@@ -391,69 +393,224 @@ not by wall-clock time, but by the progress of scrolling in a [=scroll container
391393
<div class="attributes">
392394

393395
: <dfn attribute for=ScrollTimeline>source</dfn>
394-
:: The scrollable element whose scrolling triggers the activation and drives the
395-
progress of the timeline.
396+
:: The scrollable element whose scrolling triggers the activation and drives
397+
the progress of the timeline.
396398

397399
: <dfn attribute for=ScrollTimeline>orientation</dfn>
398-
:: Determines the direction of scrolling which triggers the activation and drives
399-
the progress of the timeline.
400+
:: Determines the direction of scrolling which triggers the activation and
401+
drives the progress of the timeline.
400402

401403
: <dfn attribute for=ScrollTimeline>start</dfn>
402-
:: A scroll offset, in the direction specified by {{orientation}}, which constitutes
403-
the beginning of the range in which the timeline is active.
404-
405-
Recognized values are defined by the following grammar:
406-
407-
<blockquote>
408-
<pre class="prod">auto | <<length>> | <<percentage>></pre>
409-
</blockquote>
410-
411-
The meaning of each value is as follows:
412-
413-
: auto
414-
:: The beginning of {{source}}'s scroll range in {{orientation}}.
415-
: <<length>>
416-
:: An absolute distance along {{source}}'s scroll range in {{orientation}}.
417-
: <<percentage>>
418-
:: A percentage distance along {{source}}'s scroll range in {{orientation}}.
404+
:: A [=scroll timeline offset=] which determines the [=effective scroll
405+
offset=] in the direction specified by {{orientation}} that constitutes the
406+
beginning of the range in which the timeline is active.
419407

420408
: <dfn attribute for=ScrollTimeline>end</dfn>
421-
:: A scroll offset, in the direction specified by {{orientation}}, which constitutes
422-
the end of the range in which the timeline is activated.
423-
424-
Recognized values are defined by the following grammar:
425-
426-
<blockquote>
427-
<pre class="prod">auto | <<length>> | <<percentage>></pre>
428-
</blockquote>
429-
430-
The meaning of each value is as follows:
431-
432-
: auto
433-
:: The end of {{source}}'s scroll range in {{orientation}}.
434-
: <<length>>
435-
:: An absolute distance along {{source}}'s scroll range in {{orientation}}.
436-
: <<percentage>>
437-
:: A percentage distance along {{source}}'s scroll range in {{orientation}}.
409+
:: A [=scroll timeline offset=] which determines the [=effective scroll
410+
offset=] in the direction specified by {{orientation}} that constitutes the
411+
end of the range in which the timeline is active.
438412

439413
: <dfn attribute for=ScrollTimeline>timeRange</dfn>
440414
:: A time duration that allows mapping between a distance scrolled, and
441415
quantities specified in time units, such as an animation's [=duration=] and
442416
[=start delay=].
443417

444-
Conceptually, {{timeRange}} represents the number of milliseconds to map to the
445-
scroll range defined by {{start}} and {{end}}. As a
446-
result, this value does not have a correspondence to wall-clock time.
418+
Conceptually, {{timeRange}} represents the number of milliseconds to map to
419+
the scroll range defined by {{start}} and {{end}}. As a result, this value
420+
does not have a correspondence to wall-clock time.
447421

448422
This value is used to compute the timeline's [=effective time range=], and
449423
the mapping is then defined by mapping the scroll distance from
450424
{{start}} to {{end}}, to the [=effective time range=].
451425

452426
</div>
453427

454-
### The effective time range of a {{ScrollTimeline}} ### {#efffective-time-range-algorithm}
428+
### Scroll Timeline Offset ### {#scroll-timeline-offset-section}
429+
430+
An <dfn>effective scroll offset</dfn> is a scroll position for a given [=scroll
431+
container=] and on a given scroll direction.
432+
433+
A <dfn>scroll timeline offset</dfn> is provided by authors and determines a
434+
[=effective scroll offset=] for the {{source}} and in the direction specified by
435+
{{orientation}}.
436+
437+
There are two types of scroll timeline offset: [=container-based offset=], and
438+
[=element-based offset=]. To <dfn>resolve a scroll timeline offset</dfn> into an
439+
[=effective scroll offset=], run the procedure to [=resolve a container-based
440+
offset=] or to [=resolve a element-based offset=] depending on the offset type.
441+
It is possible for a [=scroll timeline offset=] to be resolved to null.
442+
443+
The <dfn>effective start offset</dfn> is the [=effective scroll offset=] that is
444+
resolved from {{start}}. The <dfn>effective end offset</dfn> is
445+
the [=effective scroll offset=] that is resolved from {{end}}.
446+
447+
#### Container-based Offset #### {#container-based-offset-section}
448+
A <dfn>container-based offset</dfn> is a scroll timeline offset that is declared
449+
only in relation with the <a>scroll container</a> as specified by {{source}}.
450+
451+
A [=container-based offset=] is provided in the {{DOMString}} form and can have
452+
one the following three values:
453+
454+
* auto
455+
* <<length>>
456+
* <<percentage>>
457+
458+
459+
The procedure to <dfn>resolve a container-based offset</dfn> given
460+
<var>offset</var> is as follows:
461+
462+
1. [=effective scroll offset=] is the scroll offset corresponding to the first
463+
matching condition from the following:
464+
<div class="switch">
465+
466+
: <var>offset</var> is <code>auto</code>
467+
:: Either the beginning or the ending of {{source}}'s scroll range
468+
in {{orientation}} depending on whether the offset is {{start}} or {{end}}.
469+
470+
: <var>offset</var> is a <<length>>
471+
:: The absolute distance indicated by the value along {{source}}'s scroll range
472+
in {{orientation}}.
473+
474+
: <var>offset</var> is a <<percentage>>
475+
:: The percentage distance along {{source}}'s scroll range in {{orientation}}.
476+
477+
</div>
478+
479+
Note: The scroll range of an element is the range defined by its minimum and
480+
maximum scroll offsets which are determined by it [=scrolling box=], [=padding
481+
box=], and [=overflow direction=].
482+
483+
Note: It is valid to provide a length or percentage based offset such that it is
484+
outside the source's scroll range and thus not reachable e.g., '120%'.
485+
486+
#### Element-based Offset #### {#element-based-offset-section}
487+
488+
An <dfn>element-based offset</dfn> is a scroll timeline offset that is declared
489+
in terms of the intersection of the <a>scroll container</a> as specified by
490+
{{source}} and one of its descendents as specified by {{target}}.
491+
492+
An [=element-based offset=] is provided in the {{ElementBasedOffset}} form.
493+
494+
<pre class="idl">
495+
enum Edge { "start", "end" };
496+
497+
dictionary ElementBasedOffset {
498+
Element target;
499+
Edge edge = "start";
500+
};
501+
</pre>
502+
503+
504+
<div class=members>
505+
506+
: <dfn dict-member for=ElementBasedOffset>target</dfn>
507+
:: The target whose intersection with {{source}}'s [=scrolling box=] determines
508+
the concrete scroll offset.
509+
510+
: <dfn dict-member for=ElementBasedOffset>edge</dfn>
511+
:: The edge of {{source}}'s [=scrolling box=] which the target should
512+
intersect with.
513+
514+
</div>
515+
516+
517+
Issue: TODO: Add threshold value that allows authors to control how much of the
518+
target should become visible within scrollport.
519+
520+
521+
The procedure to <dfn>resolve a element-based offset</dfn> given
522+
<var>offset</var> is as follows:
523+
524+
1. If {{source}} is null, does not currently have a [=CSS layout box=], or if
525+
its layout box is not a [=scroll container=], then the [=effective scroll
526+
offset=] is null and abort the following steps.
527+
528+
1. Let <var>target</var> be <var>offset</var>'s {{target}}.
529+
530+
1. If <var>target</var> is null, does not currently have a [=CSS layout box=],
531+
then the [=effective scroll offset=] is null and abort the following steps.
532+
533+
1. If <var>target</var> 's nearest [=scroll container=] ancestor is not the
534+
{{source}} then the [=effective scroll offset=] is null and abort the
535+
following steps.
536+
537+
1. Let <var>container box</var> be the {{source}}'s [=scrollport=].
538+
539+
1. Let <var>target box</var> be the result of finding the rectangular bounding
540+
box (axis-aligned in the {{source}}’s coordinate space) of
541+
<var>target</var>'s transformed border box.
542+
543+
1. If <var>offset</var>'s {{edge}} is 'start' then let <var>scroll offset</var>
544+
be the scroll offset at which <var>container box</var>'s start edge is flush
545+
with the <var>target box</var>'s end edge in the axis and direction
546+
determined by {{orientation}}.
547+
548+
1. If <var>offset</var>'s {{edge}} is 'end' then let <var>scroll offset</var>
549+
be the scroll offset at which <var>container box</var>'s end edge is flush
550+
with the <var>target box</var>'s start edge in the axis and direction
551+
determined by {{orientation}}.
552+
553+
1. Clamp the value of <var>scroll offset</var> to be within the {{source}}'s
554+
scroll range.
555+
556+
1. The [=effective scroll offset=] is <var>scroll offset</var>
557+
558+
559+
560+
Note: The current algorithm selects the effective scroll offset such that the
561+
target is adjacent to the scrollport but not yet visible. The upcoming threshold
562+
value will allow authors to control the amount of target that needs to be
563+
visible.
564+
565+
<div class="example">
566+
Here is a basic example showing how element-based offsets can be used to declare
567+
an scroll-linked animation that occurs when an element enters the scroller
568+
scrollport and ends once it exits the scrollport.
569+
570+
<figure>
571+
<img src="img/example-element-based.svg" width="600"
572+
alt="Example usage of element-based offset.">
573+
<figcaption>
574+
Usage of element-based offsets to create enter/exit triggers.<br>
575+
The left figure shows the scroller and target being aligned at 'end' edge.<br>
576+
The right figure shows them being aligned at 'start' edge.
577+
</figcaption>
578+
</figure>
579+
580+
581+
Note that here we are expecting a typical top to bottom scrolling and thus
582+
consider the entrance to coincide when target's start edge is flushed with
583+
scrollport's <strong>end edge</strong> and viceversa for exit.
584+
585+
<pre class='lang-javascript'>
586+
if (window.matchMedia('(prefers-reduced-motion: no-preference)').matches) {
587+
const scrollableElement = document.querySelector('#container');
588+
const image = document.querySelector('#image');
589+
590+
const timeline = new ScrollTimeline({
591+
source: scrollableElement,
592+
start: {target: image, edge: 'end'},
593+
end: {target: image, edge: 'start'},
594+
});
595+
596+
const slideIn = target.animate({
597+
transform: ['translateX(0)',q 'translateX(50vw)'],
598+
opacity: [0, 1]
599+
}, {
600+
timeline:timeline,
601+
duration: 1000
602+
}
603+
);
604+
}
605+
</pre>
606+
607+
608+
</div>
609+
610+
### The effective time range of a {{ScrollTimeline}} ### {#effective-time-range-algorithm}
455611

456-
The <dfn>effective time range</dfn> of a {{ScrollTimeline}} is calculated as follows:
612+
The <dfn>effective time range</dfn> of a {{ScrollTimeline}} is calculated as
613+
follows:
457614

458615
<div class="switch">
459616

@@ -472,29 +629,63 @@ The <dfn>effective time range</dfn> of a {{ScrollTimeline}} is calculated as fol
472629

473630
</div>
474631

632+
### The effective scroll range of a {{ScrollTimeline}} ### {#effective-scroll-range-algorithm}
633+
634+
The procedure to calculate <dfn>effective scroll range</dfn> of a
635+
{{ScrollTimeline}} is as follows:
636+
637+
1. Run the procedure to [=resolve a scroll timeline offset=] for both {{start}}
638+
and {{end}}.
639+
640+
1. Calculate [=effective scroll range=] as follow:
641+
<div class="switch">
642+
: If [=effective start offset=] or [=effective end offset=] is null.
643+
:: The [=effective scroll range=] is null.
644+
645+
: Otherwise
646+
:: The [=effective scroll range=] is the result of evaluating the following
647+
expression:
648+
<blockquote>
649+
<code>[=effective end offset=] - [=effective start offset=]</code>
650+
</blockquote>
651+
652+
</div>
653+
475654
### The current time of a {{ScrollTimeline}} ### {#current-time-algorithm}
476655

477-
The [=current time=] of a {{ScrollTimeline}} is calculated
478-
as follows:
656+
The [=current time=] of a {{ScrollTimeline}} is calculated as follows:
657+
658+
1. If {{source}} is null, does not currently have a [=CSS layout box=], or if
659+
its layout box is not a [=scroll container=], return an unresolved time
660+
value.
479661

480-
1. If {{source}} is null, does not currently have a [=CSS layout box=], or
481-
if its layout box is not a [=scroll container=], return an unresolved time value.
662+
1. Otherwise, let <var>current scroll offset</var> be the current scroll offset
663+
of {{source}} in the direction specified by {{orientation}}.
482664

483-
1. Otherwise, let <var>current scroll offset</var> be the current scroll offset of {{source}}
484-
in the direction specified by {{orientation}}.
665+
1. If [=effective scroll range=] is null, return an unresolved time value.
485666

486-
1. If <var>current scroll offset</var> is less than {{start}}, return 0.
667+
1. If <var>current scroll offset</var> is less than [=effective start offset=],
668+
return 0.
487669

488-
Note: TODO(<a href="https://github.com/w3c/csswg-drafts/issues/4325#issuecomment-585295758">Issue 4325</a>): Define what the correct timeline state is based on scroll offset.
670+
Issue(4325): Define what the correct timeline state is based on scroll
671+
offset.
489672

490-
1. If <var>current scroll offset</var> is greater than or equal to {{end}}, return [=effective time range=].
673+
1. If <var>current scroll offset</var> is greater than or equal to [=effective
674+
end offset=], return [=effective time range=].
491675

492676
1. Return the result of evaluating the following expression:
493677

494678
<blockquote>
495-
<code>(<var>current scroll offset</var> - {{start}}) / ({{end}} - {{start}}) &times; [=effective time range=]</code>
679+
<code>(<var>current scroll offset</var> - [=effective start offset=]) / [=effective scroll range=] &times; [=effective time range=]</code>
496680
</blockquote>
497681

682+
683+
684+
Note: To be considered active a scroll timeline requires its [=effective start
685+
offset=] and its [=effective end offset=] to be non-null. This means that for
686+
example if one uses an element-based offset whose {{target}} is not a descendant
687+
of the scroll timeline {{source}}, the timeline remains inactive.
688+
498689
## The 'animation-timeline' property ## {#animation-timeline}
499690

500691
A {{ScrollTimeline}} may be applied to a CSS Animation [[CSS3-ANIMATIONS]] using

0 commit comments

Comments
 (0)