Skip to content

Commit

Permalink
Support AnimationTimeline::getCurrentTime(String range)
Browse files Browse the repository at this point in the history
Bug: 1385482
Change-Id: I0e8c526187f2cc34edc497bffee5d5e298e52f50
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/4035412
Reviewed-by: Anders Hartvoll Ruud <andruud@chromium.org>
Commit-Queue: Kevin Ellis <kevers@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1075167}
  • Loading branch information
kevers-google authored and Chromium LUCI CQ committed Nov 23, 2022
1 parent 6a20f2a commit 74a29f2
Show file tree
Hide file tree
Showing 9 changed files with 214 additions and 49 deletions.
Expand Up @@ -40,6 +40,10 @@ class CORE_EXPORT AnimationTimeline : public ScriptWrappable {
~AnimationTimeline() override = default;

virtual V8CSSNumberish* currentTime();
virtual CSSNumericValue* getCurrentTime(const String& rangeName) {
return nullptr;
}

absl::optional<AnimationTimeDelta> CurrentTime();
absl::optional<double> CurrentTimeMilliseconds();
absl::optional<double> CurrentTimeSeconds();
Expand Down
Expand Up @@ -11,4 +11,5 @@ enum TimelinePhase { "inactive", "before", "active", "after" };
] interface AnimationTimeline {
readonly attribute CSSNumberish? currentTime;
[RuntimeEnabled=ScrollTimeline] readonly attribute CSSNumberish? duration;
[RuntimeEnabled=ScrollTimeline] CSSNumericValue? getCurrentTime(optional DOMString rangeName = "cover");
};
15 changes: 9 additions & 6 deletions third_party/blink/renderer/core/animation/scroll_timeline.cc
Expand Up @@ -189,12 +189,15 @@ ScrollTimeline::TimelineState ScrollTimeline::ComputeTimelineState() {
CalculateOffsets(scrollable_area, physical_orientation);
DCHECK(scroll_offsets);

// TODO(crbug.com/1338167): Update once
// github.com/w3c/csswg-drafts/issues/7401 is resolved.
double progress = (scroll_offsets->start == scroll_offsets->end)
? 1
: (current_offset - scroll_offsets->start) /
(scroll_offsets->end - scroll_offsets->start);
// Make the timeline inactive when the scroll offset range is zero.
// github.com/w3c/csswg-drafts/issues/7401
if (std::abs(scroll_offsets->end - scroll_offsets->start) < 1) {
return {TimelinePhase::kInactive, /*current_time*/ absl::nullopt,
scroll_offsets};
}

double progress = (current_offset - scroll_offsets->start) /
(scroll_offsets->end - scroll_offsets->start);

base::TimeDelta duration = base::Seconds(GetDuration()->InSecondsF());
absl::optional<base::TimeDelta> calculated_current_time =
Expand Down
48 changes: 48 additions & 0 deletions third_party/blink/renderer/core/animation/view_timeline.cc
Expand Up @@ -289,6 +289,54 @@ absl::optional<ScrollTimeline::ScrollOffsets> ViewTimeline::CalculateOffsets(
return absl::make_optional<ScrollOffsets>(start_offset, end_offset);
}

// https://www.w3.org/TR/scroll-animations-1/#named-range-getTime
CSSNumericValue* ViewTimeline::getCurrentTime(const String& rangeName) {
if (!IsActive())
return nullptr;

Timing::Delay range_start;
Timing::Delay range_end;
if (rangeName == "cover") {
range_start.phase = Timing::TimelineNamedPhase::kCover;
} else if (rangeName == "contain") {
range_start.phase = Timing::TimelineNamedPhase::kContain;
} else if (rangeName == "enter") {
range_start.phase = Timing::TimelineNamedPhase::kEnter;
} else if (rangeName == "exit") {
range_start.phase = Timing::TimelineNamedPhase::kExit;
} else {
return nullptr;
}

range_start.relative_offset = 0;
range_end.phase = range_start.phase;
range_end.relative_offset = 1;

double relative_start_offset = ToFractionalOffset(range_start).value();
double relative_end_offset = ToFractionalOffset(range_end).value();
double range = relative_end_offset - relative_start_offset;

// TODO(https://github.com/w3c/csswg-drafts/issues/8114): Update and add tests
// once ratified in the spec.
if (range == 0)
return nullptr;

absl::optional<base::TimeDelta> current_time = CurrentPhaseAndTime().time;
// If current time is null then the timeline must be inactive, which is
// handled above.
DCHECK(current_time);
DCHECK(GetDuration());

double timeline_progress =
CurrentPhaseAndTime().time.value().InMillisecondsF() /
GetDuration().value().InMillisecondsF();

double named_range_progress =
(timeline_progress - relative_start_offset) / range;

return CSSUnitValues::percent(named_range_progress * 100);
}

absl::optional<double> ViewTimeline::ToFractionalOffset(
const Timing::Delay& delay) const {
absl::optional<double> result;
Expand Down
2 changes: 2 additions & 0 deletions third_party/blink/renderer/core/animation/view_timeline.h
Expand Up @@ -50,6 +50,8 @@ class CORE_EXPORT ViewTimeline : public ScrollTimeline {

bool IsViewTimeline() const override { return true; }

CSSNumericValue* getCurrentTime(const String& rangeName) override;

AnimationTimeDelta CalculateIntrinsicIterationDuration(
const Timing&) override;

Expand Down
Expand Up @@ -16,8 +16,12 @@
#element {
animation: anim forwards scroll(root);
}
#spacer {
height: 200vh;
}
</style>
<div id=element></div>
<div id=spacer></div>

<script>
'use strict';
Expand Down

This file was deleted.

@@ -0,0 +1,145 @@
<!DOCTYPE html>
<html id="top">
<meta charset="utf-8">
<title>View timeline delay</title>
<link rel="help" href="https://drafts.csswg.org/scroll-animations-1/#viewtimeline-interface">
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="/web-animations/testcommon.js"></script>
<script src="/scroll-animations/scroll-timelines/testcommon.js"></script>
<script src="/scroll-animations/view-timelines/testcommon.js"></script>
<style>
#container {
border: 10px solid lightgray;
overflow-x: scroll;
height: 200px;
width: 200px;
}
#content {
display: flex;
flex-flow: row nowrap;
justify-content: flex-start;
width: 1800px;
margin: 0;
}
.spacer {
width: 800px;
display: inline-block;
}
#target {
background-color: green;
height: 100px;
width: 100px;
display: inline-block;
}
</style>
<body>
<div id="container">
<div id="content">
<div class="spacer"></div>
<div id="target"></div>
<div class="spacer"></div>
</div>
</div>
</body>
<script type="text/javascript">
const MAX_SCROLL = 1600;

promise_test(async t => {
// Points of interest along view timeline:
// 600 px cover start, enter start
// 700 px contain start, enter end
// 800 px contain end, exit start
// 900 px cover end, exit end
const anim =
CreateViewTimelineOpacityAnimation(t, target,
{ axis: 'inline', fill: 'both' });
let timeline = anim.timeline;

container.scrollLeft = 600;
await waitForNextFrame();

assert_percents_approx_equal(timeline.getCurrentTime('cover'), 0,
MAX_SCROLL, 'Scroll aligned with cover start');
assert_percents_approx_equal(timeline.getCurrentTime('enter'), 0,
MAX_SCROLL, 'Scroll aligned with enter start');
assert_percents_approx_equal(timeline.getCurrentTime(), 0,
MAX_SCROLL,
'Scroll aligned with timeline start offset');

container.scrollLeft = 650;
await waitForNextFrame();

assert_percents_approx_equal(timeline.getCurrentTime('enter'), 50,
MAX_SCROLL, 'Scroll at enter midpoint');

container.scrollLeft = 700;
await waitForNextFrame();

assert_percents_approx_equal(timeline.getCurrentTime('enter'), 100,
MAX_SCROLL, 'Scroll at enter end');
assert_percents_approx_equal(timeline.getCurrentTime('contain'), 0,
MAX_SCROLL, 'Scroll at contain start');

container.scrollLeft = 750;
await waitForNextFrame();

assert_percents_approx_equal(timeline.getCurrentTime('contain'), 50,
MAX_SCROLL, 'Scroll at contain midpoint');
assert_percents_approx_equal(timeline.getCurrentTime(), 50,
MAX_SCROLL, 'Scroll at timeline midpoint');

container.scrollLeft = 800;
await waitForNextFrame();

assert_percents_approx_equal(timeline.getCurrentTime('exit'), 0,
MAX_SCROLL, 'Scroll at exit start');
assert_percents_approx_equal(timeline.getCurrentTime('contain'), 100,
MAX_SCROLL, 'Scroll at contain end');

container.scrollLeft = 850;
await waitForNextFrame();

assert_percents_approx_equal(timeline.getCurrentTime('exit'), 50,
MAX_SCROLL, 'Scroll at exit midpoint');

container.scrollLeft = 900;
await waitForNextFrame();

assert_percents_approx_equal(timeline.getCurrentTime('exit'), 100,
MAX_SCROLL, 'Scroll at exit end');
assert_percents_approx_equal(timeline.getCurrentTime('cover'), 100,
MAX_SCROLL, 'Scroll at cover end');
assert_percents_approx_equal(timeline.getCurrentTime(), 100,
MAX_SCROLL, 'Scroll at end of timeline');

assert_equals(timeline.getCurrentTime('gibberish'), null,
'No current time for unknown named range');

// Add insets to force the start and end offsets to align. This forces
// the timeline to become inactive.
// start_offset = target_offset - viewport_size + end_side_inset
// = 600 + end_side_inset
// end_offset = target_offset + target_size - start_side_inset
// = 900 - start_side_inset
// Equating start_offset and end_offset:
// end_side_inset = 300 - start_side_inset;
timeline =
new ViewTimeline ({
subject: target,
axis: 'inline',
inset: [ CSS.px(150), CSS.px(150) ]
});
anim.timeline = timeline;
await waitForNextFrame();

assert_equals(timeline.currentTime, null,
'Current time is null when scroll-range is zero');
assert_equals(timeline.getCurrentTime(), null,
'getCurrentTime with an inactive timeline.');
assert_equals(timeline.getCurrentTime('contain'), null,
'getCurrentTime on a ranged name with an inactive timeline.');

}, 'View timeline current time for named range');

</script>
Expand Up @@ -232,6 +232,7 @@ interface AnimationTimeline
getter currentTime
getter duration
method constructor
method getCurrentTime
interface Attr : Node
attribute @@toStringTag
getter localName
Expand Down

0 comments on commit 74a29f2

Please sign in to comment.