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
|
<!DOCTYPE html>
<meta charset="utf-8">
<title>ScrollTimeline invalidation</title>
<link rel="help" href="https://wicg.github.io/scroll-animations/#current-time-algorithm">
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="/web-animations/testcommon.js"></script>
<script src="testcommon.js"></script>
<style>
.scroller {
overflow: auto;
height: 100px;
width: 100px;
will-change: transform;
}
.contents {
height: 1000px;
width: 100%;
}
</style>
<div id="log"></div>
<script>
'use strict';
promise_test(async t => {
const animation = createScrollLinkedAnimation(t);
const effect_duration = 350;
animation.effect.updateTiming({ duration: effect_duration });
const scroller = animation.timeline.source;
let maxScroll = scroller.scrollHeight - scroller.clientHeight;
scroller.scrollTop = 0.2 * maxScroll;
const initial_progress = (scroller.scrollTop / maxScroll) * 100;
// Wait for new animation frame which allows the timeline to compute new
// current time.
await waitForNextFrame();
animation.play();
await animation.ready;
// Animation current time is at 20% because scroller was scrolled to 20%
// assert_equals(animation.currentTime.value, 20);
assert_equals(animation.currentTime.value, 20);
assert_equals(scroller.scrollTop, 180);
assert_equals(maxScroll, 900);
// Shrink scroller content size (from 1000 to 500).
// scroller.scrollTop maintains the same offset, which means shrinking the
// content has the effect of skipping the animation forward.
scroller.firstChild.style.height = "500px";
maxScroll = scroller.scrollHeight - scroller.clientHeight;
assert_equals(scroller.scrollTop, 180);
assert_equals(maxScroll, 400);
await waitForNextFrame();
const expected_progress = (scroller.scrollTop / maxScroll) * 100;
assert_true(expected_progress > initial_progress)
assert_equals(animation.currentTime.value, expected_progress); // 45%
assert_equals(animation.timeline.currentTime.value, expected_progress); // 45%
assert_equals(animation.effect.getComputedTiming().localTime.value, expected_progress); // 45%
}, 'Animation current time and effect local time are updated after scroller ' +
'content size changes.');
promise_test(async t => {
const animation = createScrollLinkedAnimation(t);
animation.effect.updateTiming({ duration: 350 });
const scroller = animation.timeline.source;
let maxScroll = scroller.scrollHeight - scroller.clientHeight;
const scrollOffset = 0.2 * maxScroll
scroller.scrollTop = scrollOffset;
const initial_progress = (scroller.scrollTop / maxScroll) * 100;
// Wait for new animation frame which allows the timeline to compute new
// current time.
await waitForNextFrame();
animation.play();
await animation.ready;
// Animation current time is at 20% because scroller was scrolled to 20%
// assert_equals(animation.currentTime.value, 20);
assert_equals(animation.currentTime.value, 20);
assert_equals(scroller.scrollTop, scrollOffset);
assert_equals(maxScroll, 900);
// Change scroller size.
scroller.style.height = "500px";
maxScroll = scroller.scrollHeight - scroller.clientHeight;
assert_equals(scroller.scrollTop, scrollOffset);
assert_equals(maxScroll, 500);
await waitForNextFrame();
const expected_progress = (scroller.scrollTop / maxScroll) * 100;
assert_true(expected_progress > initial_progress);
assert_equals(animation.currentTime.value, expected_progress); // 45%
assert_equals(animation.timeline.currentTime.value, expected_progress); // 45%
assert_equals(animation.effect.getComputedTiming().localTime.value, expected_progress); // 45%
}, 'Animation current time and effect local time are updated after scroller ' +
'size changes.');
promise_test(async t => {
await waitForNextFrame();
const timeline = createScrollTimeline(t);
const scroller = timeline.source;
const maxScroll = scroller.scrollHeight - scroller.clientHeight;
// Instantiate scroll animation that resizes its scroll timeline scroller.
const animation = new Animation(
new KeyframeEffect(
timeline.source.firstChild,
[{ height: '1000px' }, { height: '2000px' }],
{ duration: 1000, }
), timeline);
animation.play();
await animation.ready;
await waitForNextFrame();
scroller.scrollTop = 0.2 * maxScroll;
// Wait for new animation frame which allows the timeline to compute new
// current time.
await waitForNextFrame();
// Let s = scroll position
// p = fractional progress
// v = viewport height
// c = scroll height
// c[i] = c[i-1] * (1 + p[i-1])
// p[i] = s / (c[i-1] - v)
// c[0] = 1000
// p[0] = 0.2 ==> s = 180
// c[1] = 1000 * 1.2 = 1200
assert_percents_equal(timeline.currentTime, 20,
'Timeline current time is updated after animation frame.');
assert_equals(scroller.scrollHeight, 1200);
await waitForNextFrame();
// Applying the animation effect alters the height of the scroll content and
// makes the scroll timeline stale.
// https://github.com/w3c/csswg-drafts/issues/8694
// p[1] = 180 / (1200 - 100) = 0.16363636363
// c[2] = 1000 * 1.16363636363 = 1163.6363 => 1164
// p[2] = 180 / (1164 - 100) = 0.1692
// c[3] = 1169
assert_percents_equal(timeline.currentTime, 16.92,
'Timeline current time is updated after two animation frames and ' +
'reflects single layout run.');
assert_approx_equals(scroller.scrollHeight, 1169, 1);
}, 'If scroll animation resizes its scroll timeline scroller, ' +
'layout reruns once per frame.');
</script>
|