diff --git a/scroll-animations-1/Overview.bs b/scroll-animations-1/Overview.bs index f9cbf0a94df..676e451c39c 100644 --- a/scroll-animations-1/Overview.bs +++ b/scroll-animations-1/Overview.bs @@ -239,6 +239,99 @@ spec:selectors-4; type:dfn; text:selector References to the [=root element=] propagate to the document viewport (which functions as its [=scroll container=]). +
@@ -399,6 +568,23 @@ spec:selectors-4; type:dfn; text:selector 'scroll-timeline-name' and 'scroll-timeline-axis' in a single declaration. ++ The following two rules are equivalent: + + ```css + .scroller { + scroll-timeline-name: --my-scroller; + scroll-timeline-axis: inline; + } + ``` + + ```css + .scroller { + scroll-timeline: --my-scroller inline; + } + ``` ++ # View Progress Timelines # {#view-timelines} Often animations are desired to start and end @@ -561,6 +747,38 @@ spec:selectors-4; type:dfn; text:selector of the [=view progress visibility range=], as defined for 'view-timeline-inset'. ++ In the following example, each direct child of the `.scroller` element + will reveal itself as it crosses the scrollport. + + ```css + @keyframes reveal { + from { opacity: 0; } + } + + .scroller > * { + animation: reveal linear both; + animation-timeline: view(); + } + ``` + + For every element matched by the selector, + a [=view progress timeline=] gets created to drive the animation. + Because no arguments are passed into ''view()'' it uses the default values + for <+ Each use of ''view()'' corresponds to its own instance of {{ViewTimeline}} in the Web Animations API, even if multiple elements use ''view()'' to reference @@ -671,6 +889,33 @@ spec:selectors-4; type:dfn; text:selector The values of {{ViewTimeline/subject}}, {{ScrollTimeline/source}}, and {{AnimationTimeline/currentTime}} are all computed when any of them is requested or updated. +> and <<'view-timeline-inset'>>, + thus tracking its scroll position in the [=block axis=]. + + With the keyframes driven by the [=view progress timeline=], + the element will be at `opacity: 0` when it is about to enter the scrollport, + and at `opacity: 1` when it has just left the scrollport. + Any scroll position in between results in an interpolated value. + + Note: Because matched elements + can be positioned at different offsets within the scroller + or can differ in size, + every matched element gets its own unique [=view progress timeline=]. + + In the following example, each child of the `.scroller` element + will reveal itself as it crosses the scrollport. + + ```js + document.querySelectorAll('.scroller > *').forEach(childElement => { + const timeline = new ViewTimeline({ + subject: childElement, + axis: 'block', + }); + + childElement.animate({ + opacity: [ 0, 1 ], + }, { + fill: 'both', + timeline, + }); + }); + ``` + + In a vertical scroller, this results in an element going fully transparent (`opacity: 0`) + when its about to enter the scroller from the bottom edge of the scroller + to being fully opaque (`opacity: 1`) when it has completely entered the scroller. + + Scroll positions in between result in an opacity `0` and `1`. ++ ## Named View Progress Timelines ## {#view-timelines-named} [=View progress timelines=] can also be defined declaratively @@ -685,6 +930,32 @@ spec:selectors-4; type:dfn; text:selector with 'view-timeline-name' as the [=coordinating list base property=]. See [[css-values-4#linked-properties]]. ++ This example behaves exactly the same + as the previous one: + each child of the `.scroller` element will reveal itself + as it crosses the scrollport. + + The difference is that + instead of using an anonymous [=View progress timeline=] + to drive the animation, it now uses a [=named view progress timeline=] that tracks the `.scroller`. + + ```css + @keyframes reveal { + from { opacity: 0; } + } + + .scroller { + view-timeline: --my-scroller block; + } + + .scroller > * { + animation: reveal linear both; + animation-timeline: --my-scroller; + } + ``` ++ ### Naming a View Progress Timeline: the 'view-timeline-name' property ### {#view-timeline-name}@@ -851,19 +1122,19 @@ spec:selectors-4; type:dfn; text:selector } .root { - /* declares the scope of a 'scroller' timeline to reach all descendants */ - timeline-scope: scroller; + /* declares the scope of a '--scroller' timeline to reach all descendants */ + timeline-scope: --scroller; } .root .animation { animation: anim; - /* references the 'scroller' timeline for driving the progress of 'anim' */ - animation-timeline: scroller; + /* references the '--scroller' timeline for driving the progress of 'anim' */ + animation-timeline: --scroller; } .root .animation + .scroller { - /* attaches a scroll progress timeline to the timeline name 'scroller' */ - scroll-timeline: scroller; + /* attaches a scroll progress timeline to the timeline named '--scroller' */ + scroll-timeline: --scroller; } … @@ -1022,6 +1293,55 @@ spec:selectors-4; type:dfn; text:selector are only generated for properties that don't have keyframes at or earlier than 0% or at or after 100% (respectively). ++ In this example the range information is included directly in the ''@keyframes'' rule: + + ```css + @keyframes animate-in-and-out { + entry 0% { + opacity: 0; transform: translateY(100%); + } + entry 100% { + opacity: 1; transform: translateY(0); + } + + exit 0% { + opacity: 1; transform: translateY(0); + } + exit 100% { + opacity: 0; transform: translateY(-100%); + } + } + + .scroller > * { + animation: linear animate-in-and-out; + animation-timeline: view(); + } + ``` + + It has the same outcome as the following snippet which uses + two distinct sets of keyframes combined with 'animation-range' + + ```css + @keyframes animate-in { + 0% { opacity: 0; transform: translateY(100%); } + 100% { opacity: 1; transform: translateY(0); } + } + + @keyframes animate-out { + 0% { opacity: 1; transform: translateY(0); } + 100% { opacity: 0; transform: translateY(-100%); } + } + + .scroller > * { + animation: animate-in linear forwards, + animate-out linear forwards; + animation-timeline: view(); + animation-range: entry, exit; + } + ``` ++ ## Attaching Animations to Timeline Ranges ## {#named-range-animation-declaration} A set of animation keyframes can be attached @@ -1055,6 +1375,117 @@ spec:selectors-4; type:dfn; text:selector ISSUE: Define application to time-driven animations. +### Examples + ++ In the following example, each direct child of the `.scroller` element + reveals itself as it enters the scrollport instead of when entirely crossing it. + + This is achieved by setting 'animation-range' to limit the [=active interval=] + to the ''entry'' range instead of the default ''cover'' range: + + ```css + @keyframes reveal { + from { opacity: 0; } + } + + .scroller > * { + animation: reveal linear both; + animation-timeline: view(); + animation-range: entry; + } + ``` + + In a vertical scroller, this results in an element going fully transparent (`opacity: 0`) + when its about to enter the scroller from the bottom edge of the scroller + to being fully opaque (`opacity: 1`) when it has completely entered the scroller. + + Scroll positions in between result in an opacity `0` and `1`. ++ ++ A variation of the previous example is to add both entry and exit effects, + each linked to their own 'animation-range': + + ```css + @keyframes reveal { + from { opacity: 0; } + to { opacity: 1; } + } + @keyframes hide { + from { opacity: 1; } + to { opacity: 0; } + } + + .scroller > * { + animation: reveal linear both, + hide linear forwards; + animation-timeline: view(); + animation-range: entry, exit; + } + ``` + + The `reveal` effect is linked to the ''entry'' range + while the `hide` effect is linked to the ''exit'' range. + + In a vertical scroller, when scrolling down, + this results in the element going from `opacity: 0` to `opacity: 1` + as it enters the scrollport from the bottom edge of the scroller. + + When continuing to scroll down, + the element will eventually go from `opacity: 1` to `opacity: 0` + as the subject exits the scrollport at the top edge of the scroller. ++ ++ The Web Animations API equivalent of the previous example is the following: + + ```js + document.querySelectorAll('.scroller > *').forEach(childElement => { + const timeline = new ViewTimeline({ + subject: childElement, + axis: 'block', + }); + + // Reveal effect on entry + childElement.animate({ + opacity: [ 0, 1 ], + }, { + fill: 'forwards', + timeline, + rangeStart: 'entry 0%', + rangeEnd: 'entry 100%', + }); + + // Hide effect on exit + childElement.animate({ + opacity: [ 1, 0 ], + }, { + fill: 'forwards', + timeline, + rangeStart: 'exit 100%', + rangeEnd: 'exit 100%', + }); + }); + ``` + + For every matched element, a single timeline tracking that element is created. + The timeline tracks the element inside its scroller in the block direction. + + The timeline is used to drive two animations added to the element + but the effects are only attached to a part of the range, + thanks to the `rangeStart` and `rangeEnd` options. + + The first animation is attached to the ''entry'' range, + animating the element from `opacity: 0` to `opacity: 1` + as it enters the scrollport. + + The second animation is attached to the ''exit'' range, + animating the element from `opacity: 1` to `opacity: 0` + as it leaves the scrollport. + ++ ### Specifying an Animation’s Timeline Range: the 'animation-range' shorthand ### {#animation-range}@@ -1110,6 +1541,19 @@ spec:selectors-4; type:dfn; text:selector++ Because <<'animation-range-start'>> and <<'animation-range-end'>> accept <+ ISSUE(8438): What's the best way to handle defaulting of omitted values here? ### Specifying an Animation’s Timeline Range Start: the 'animation-range-start' property ### {#animation-range-start}>s, + it’s perfectly fine to do calculations using ''calc()'' for the ranges: + + ```css + #subject { + animation: anim linear both; + animation-timeline: view(); + animation-range: entry calc(100% - 100px) exit calc(0% + 100px); + } + ``` +