Commit e2f934fe authored by Stephen McGruer's avatar Stephen McGruer Committed by Commit Bot

Add main-thread support for {start,end}ScrollOffset on ScrollTimelines

This CL changes the currentTime algorithm to respect the start and end
scroll offset specified for the ScrollTimeline. It does not change the
compositor logic - that is left for a followup.

https://wicg.github.io/scroll-animations/#dom-scrolltimeline-startscrolloffset

Bug: 885196
Change-Id: I732a39659f1f354a8dec982bf75e14f11c0f2134
Reviewed-on: https://chromium-review.googlesource.com/1238376
Commit-Queue: Stephen McGruer <smcgruer@chromium.org>
Reviewed-by: default avatarMajid Valipour <majidvp@chromium.org>
Reviewed-by: default avatarAnders Ruud <andruud@chromium.org>
Cr-Commit-Position: refs/heads/master@{#595487}
parent cd315c05
<!DOCTYPE html> <!DOCTYPE html>
<style>
.scroller {
height: 100px;
width: 100px;
overflow: scroll;
}
.content {
height: 500px;
width: 500px;
}
</style>
<script src='resources/scroll-timeline-util.js'></script>
<script src='../../../resources/testharness.js'></script> <script src='../../../resources/testharness.js'></script>
<script src='../../../resources/testharnessreport.js'></script> <script src='../../../resources/testharnessreport.js'></script>
<div id='scroller1' class='scroller'> <body></body>
<div class='content'></div>
</div>
<script> <script>
// Builds a generic structure that looks like:
//
// <div class="scroller"> // 100x100 viewport
// <div class="contents"></div> // 500x500
// </div>
//
// The |scrollerOverrides| and |contentOverrides| parameters are maps which
// are applied to the scroller and contents style after basic setup.
//
// Returns the outer 'scroller' element.
function setupScrollTimelineTest(
scrollerOverrides = new Map(), contentOverrides = new Map()) {
let scroller = document.createElement('div');
scroller.style.width = '100px';
scroller.style.height = '100px';
scroller.style.overflow = 'scroll';
for (const [key, value] of scrollerOverrides) {
scroller.style[key] = value;
}
let contents = document.createElement('div');
contents.style.width = '500px';
contents.style.height = '500px';
for (const [key, value] of contentOverrides) {
contents.style[key] = value;
}
scroller.appendChild(contents);
document.body.appendChild(scroller);
return scroller;
}
// Helper method to calculate the current time, implementing only step 5 of
// https://wicg.github.io/scroll-animations/#current-time-algorithm
function calculateCurrentTime(
currentScrollOffset, startScrollOffset, endScrollOffset,
effectiveTimeRange) {
return ((currentScrollOffset - startScrollOffset) /
(endScrollOffset - startScrollOffset)) *
effectiveTimeRange;
}
test(function() { test(function() {
const scroller = document.querySelector('#scroller1'); const scroller = setupScrollTimelineTest();
// For simplicity, we set the timeRange such that currentTime maps directly to // For simplicity, we set the timeRange such that currentTime maps directly to
// the value scrolled. We have a square scroller/contents, so can just compute // the value scrolled. We have a square scroller/contents, so can just compute
// one edge and use it for all the timelines; // one edge and use it for all the timelines;
const scrollerSize = scroller.scrollHeight - scroller.clientHeight; const scrollerSize = scroller.scrollHeight - scroller.clientHeight;
const blockScrollTimeline = new ScrollTimeline( const blockScrollTimeline = new ScrollTimeline(
{ scrollSource: scroller, timeRange: scrollerSize, orientation: 'block' }); {scrollSource: scroller, timeRange: scrollerSize, orientation: 'block'});
const inlineScrollTimeline = new ScrollTimeline( const inlineScrollTimeline = new ScrollTimeline(
{ scrollSource: scroller, timeRange: scrollerSize, orientation: 'inline' }); {scrollSource: scroller, timeRange: scrollerSize, orientation: 'inline'});
const horizontalScrollTimeline = new ScrollTimeline( const horizontalScrollTimeline = new ScrollTimeline({
{ scrollSource: scroller, timeRange: scrollerSize, orientation: 'horizontal' }); scrollSource: scroller,
const verticalScrollTimeline = new ScrollTimeline( timeRange: scrollerSize,
{ scrollSource: scroller, timeRange: scrollerSize, orientation: 'vertical' }); orientation: 'horizontal'
});
const verticalScrollTimeline = new ScrollTimeline({
scrollSource: scroller,
timeRange: scrollerSize,
orientation: 'vertical'
});
// Unscrolled, all timelines should read a currentTime of 0. // Unscrolled, all timelines should read a currentTime of 0.
assert_equals(blockScrollTimeline.currentTime, 0); assert_equals(blockScrollTimeline.currentTime, 0);
...@@ -52,13 +88,7 @@ test(function() { ...@@ -52,13 +88,7 @@ test(function() {
assert_equals(horizontalScrollTimeline.currentTime, 75); assert_equals(horizontalScrollTimeline.currentTime, 75);
assert_equals(verticalScrollTimeline.currentTime, 50); assert_equals(verticalScrollTimeline.currentTime, 50);
}, 'currentTime calculates the correct time based on scrolled amount'); }, 'currentTime calculates the correct time based on scrolled amount');
</script>
<div id='scroller2' class='scroller'>
<div class='content' style='height: 1000px; width: 1000px;'></div>
</div>
<script>
test(function() { test(function() {
// It is unfortunately difficult to calculate what scroll offset results in an // It is unfortunately difficult to calculate what scroll offset results in an
// exact currentTime. Scrolling is calculated in integers which allows for the // exact currentTime. Scrolling is calculated in integers which allows for the
...@@ -67,10 +97,10 @@ test(function() { ...@@ -67,10 +97,10 @@ test(function() {
// //
// Instead we make the scroller content big enough that a 1-pixel rounding // Instead we make the scroller content big enough that a 1-pixel rounding
// difference results in a negligible difference in the output value. // difference results in a negligible difference in the output value.
const contentOverrides = new Map([['width', '1000px'], ['height', '1000px']]);
const scroller = document.querySelector('#scroller2'); const scroller = setupScrollTimelineTest(new Map(), contentOverrides);
const scrollTimeline = new ScrollTimeline( const scrollTimeline = new ScrollTimeline(
{ scrollSource: scroller, timeRange: 100, orientation: 'block' }); {scrollSource: scroller, timeRange: 100, orientation: 'block'});
// Mapping timeRange to 100 gives a form of 'percentage scrolled', so // Mapping timeRange to 100 gives a form of 'percentage scrolled', so
// calculate where the 50% scroll mark would be. // calculate where the 50% scroll mark would be.
...@@ -79,14 +109,10 @@ test(function() { ...@@ -79,14 +109,10 @@ test(function() {
assert_approx_equals(scrollTimeline.currentTime, 50, 0.5); assert_approx_equals(scrollTimeline.currentTime, 50, 0.5);
}, 'currentTime adjusts correctly for the timeRange'); }, 'currentTime adjusts correctly for the timeRange');
</script>
<div id='scroller3' class='scroller' style='direction: rtl;'>
<div class='content'></div>
</div>
<script>
test(function() { test(function() {
const scroller = document.querySelector('#scroller3'); const scrollerOverrides = new Map([['direction', 'rtl']]);
const scroller = setupScrollTimelineTest(scrollerOverrides);
// For simplicity, we set the timeRange such that currentTime maps directly to // For simplicity, we set the timeRange such that currentTime maps directly to
// the value scrolled. We have a square scroller/contents, so can just compute // the value scrolled. We have a square scroller/contents, so can just compute
...@@ -94,13 +120,19 @@ test(function() { ...@@ -94,13 +120,19 @@ test(function() {
const scrollerSize = scroller.scrollHeight - scroller.clientHeight; const scrollerSize = scroller.scrollHeight - scroller.clientHeight;
const blockScrollTimeline = new ScrollTimeline( const blockScrollTimeline = new ScrollTimeline(
{ scrollSource: scroller, timeRange: scrollerSize, orientation: 'block' }); {scrollSource: scroller, timeRange: scrollerSize, orientation: 'block'});
const inlineScrollTimeline = new ScrollTimeline( const inlineScrollTimeline = new ScrollTimeline(
{ scrollSource: scroller, timeRange: scrollerSize, orientation: 'inline' }); {scrollSource: scroller, timeRange: scrollerSize, orientation: 'inline'});
const horizontalScrollTimeline = new ScrollTimeline( const horizontalScrollTimeline = new ScrollTimeline({
{ scrollSource: scroller, timeRange: scrollerSize, orientation: 'horizontal' }); scrollSource: scroller,
const verticalScrollTimeline = new ScrollTimeline( timeRange: scrollerSize,
{ scrollSource: scroller, timeRange: scrollerSize, orientation: 'vertical' }); orientation: 'horizontal'
});
const verticalScrollTimeline = new ScrollTimeline({
scrollSource: scroller,
timeRange: scrollerSize,
orientation: 'vertical'
});
// Unscrolled, all timelines should read a current time of 0 even though the // Unscrolled, all timelines should read a current time of 0 even though the
// X-axis will have started at the right hand side for rtl. // X-axis will have started at the right hand side for rtl.
...@@ -119,14 +151,10 @@ test(function() { ...@@ -119,14 +151,10 @@ test(function() {
assert_equals(horizontalScrollTimeline.currentTime, scrollerSize - 75); assert_equals(horizontalScrollTimeline.currentTime, scrollerSize - 75);
assert_equals(verticalScrollTimeline.currentTime, 50); assert_equals(verticalScrollTimeline.currentTime, 50);
}, 'currentTime handles direction: rtl correctly'); }, 'currentTime handles direction: rtl correctly');
</script>
<div id='scroller4' class='scroller' style='writing-mode: vertical-rl;'>
<div class='content'></div>
</div>
<script>
test(function() { test(function() {
const scroller = document.querySelector('#scroller4'); const scrollerOverrides = new Map([['writing-mode', 'vertical-rl']]);
const scroller = setupScrollTimelineTest(scrollerOverrides);
// For simplicity, we set the timeRange such that currentTime maps directly to // For simplicity, we set the timeRange such that currentTime maps directly to
// the value scrolled. We have a square scroller/contents, so can just compute // the value scrolled. We have a square scroller/contents, so can just compute
...@@ -134,13 +162,19 @@ test(function() { ...@@ -134,13 +162,19 @@ test(function() {
const scrollerSize = scroller.scrollHeight - scroller.clientHeight; const scrollerSize = scroller.scrollHeight - scroller.clientHeight;
const blockScrollTimeline = new ScrollTimeline( const blockScrollTimeline = new ScrollTimeline(
{ scrollSource: scroller, timeRange: scrollerSize, orientation: 'block' }); {scrollSource: scroller, timeRange: scrollerSize, orientation: 'block'});
const inlineScrollTimeline = new ScrollTimeline( const inlineScrollTimeline = new ScrollTimeline(
{ scrollSource: scroller, timeRange: scrollerSize, orientation: 'inline' }); {scrollSource: scroller, timeRange: scrollerSize, orientation: 'inline'});
const horizontalScrollTimeline = new ScrollTimeline( const horizontalScrollTimeline = new ScrollTimeline({
{ scrollSource: scroller, timeRange: scrollerSize, orientation: 'horizontal' }); scrollSource: scroller,
const verticalScrollTimeline = new ScrollTimeline( timeRange: scrollerSize,
{ scrollSource: scroller, timeRange: scrollerSize, orientation: 'vertical' }); orientation: 'horizontal'
});
const verticalScrollTimeline = new ScrollTimeline({
scrollSource: scroller,
timeRange: scrollerSize,
orientation: 'vertical'
});
// Unscrolled, all timelines should read a current time of 0 even though the // Unscrolled, all timelines should read a current time of 0 even though the
// X-axis will have started at the right hand side for vertical-rl. // X-axis will have started at the right hand side for vertical-rl.
...@@ -150,8 +184,9 @@ test(function() { ...@@ -150,8 +184,9 @@ test(function() {
assert_equals(verticalScrollTimeline.currentTime, 0); assert_equals(verticalScrollTimeline.currentTime, 0);
// For vertical-rl, the X-axis starts on the right-hand-side and is the block // For vertical-rl, the X-axis starts on the right-hand-side and is the block
// axis. The Y-axis is normal but is the inline axis. For the horizontal/vertical // axis. The Y-axis is normal but is the inline axis. For the
// cases, horizontal starts on the right-hand-side and vertical is normal. // horizontal/vertical cases, horizontal starts on the right-hand-side and
// vertical is normal.
scroller.scrollTop = 50; scroller.scrollTop = 50;
scroller.scrollLeft = 75; scroller.scrollLeft = 75;
...@@ -160,14 +195,10 @@ test(function() { ...@@ -160,14 +195,10 @@ test(function() {
assert_equals(horizontalScrollTimeline.currentTime, scrollerSize - 75); assert_equals(horizontalScrollTimeline.currentTime, scrollerSize - 75);
assert_equals(verticalScrollTimeline.currentTime, 50); assert_equals(verticalScrollTimeline.currentTime, 50);
}, 'currentTime handles writing-mode: vertical-rl correctly'); }, 'currentTime handles writing-mode: vertical-rl correctly');
</script>
<div id='scroller5' class='scroller' style='writing-mode: vertical-lr;'>
<div class='content'></div>
</div>
<script>
test(function() { test(function() {
const scroller = document.querySelector('#scroller5'); const scrollerOverrides = new Map([['writing-mode', 'vertical-lr']]);
const scroller = setupScrollTimelineTest(scrollerOverrides);
// For simplicity, we set the timeRange such that currentTime maps directly to // For simplicity, we set the timeRange such that currentTime maps directly to
// the value scrolled. We have a square scroller/contents, so can just compute // the value scrolled. We have a square scroller/contents, so can just compute
...@@ -175,13 +206,19 @@ test(function() { ...@@ -175,13 +206,19 @@ test(function() {
const scrollerSize = scroller.scrollHeight - scroller.clientHeight; const scrollerSize = scroller.scrollHeight - scroller.clientHeight;
const blockScrollTimeline = new ScrollTimeline( const blockScrollTimeline = new ScrollTimeline(
{ scrollSource: scroller, timeRange: scrollerSize, orientation: 'block' }); {scrollSource: scroller, timeRange: scrollerSize, orientation: 'block'});
const inlineScrollTimeline = new ScrollTimeline( const inlineScrollTimeline = new ScrollTimeline(
{ scrollSource: scroller, timeRange: scrollerSize, orientation: 'inline' }); {scrollSource: scroller, timeRange: scrollerSize, orientation: 'inline'});
const horizontalScrollTimeline = new ScrollTimeline( const horizontalScrollTimeline = new ScrollTimeline({
{ scrollSource: scroller, timeRange: scrollerSize, orientation: 'horizontal' }); scrollSource: scroller,
const verticalScrollTimeline = new ScrollTimeline( timeRange: scrollerSize,
{ scrollSource: scroller, timeRange: scrollerSize, orientation: 'vertical' }); orientation: 'horizontal'
});
const verticalScrollTimeline = new ScrollTimeline({
scrollSource: scroller,
timeRange: scrollerSize,
orientation: 'vertical'
});
// Unscrolled, all timelines should read a current time of 0. // Unscrolled, all timelines should read a current time of 0.
assert_equals(blockScrollTimeline.currentTime, 0); assert_equals(blockScrollTimeline.currentTime, 0);
...@@ -190,8 +227,8 @@ test(function() { ...@@ -190,8 +227,8 @@ test(function() {
assert_equals(verticalScrollTimeline.currentTime, 0); assert_equals(verticalScrollTimeline.currentTime, 0);
// For vertical-lr, both axes start at their 'normal' positions but the X-axis // For vertical-lr, both axes start at their 'normal' positions but the X-axis
// is the block direction and the Y-axis is the inline direction. This does not // is the block direction and the Y-axis is the inline direction. This does
// affect horizontal/vertical. // not affect horizontal/vertical.
scroller.scrollTop = 50; scroller.scrollTop = 50;
scroller.scrollLeft = 75; scroller.scrollLeft = 75;
...@@ -200,4 +237,380 @@ test(function() { ...@@ -200,4 +237,380 @@ test(function() {
assert_equals(horizontalScrollTimeline.currentTime, 75); assert_equals(horizontalScrollTimeline.currentTime, 75);
assert_equals(verticalScrollTimeline.currentTime, 50); assert_equals(verticalScrollTimeline.currentTime, 50);
}, 'currentTime handles writing-mode: vertical-lr correctly'); }, 'currentTime handles writing-mode: vertical-lr correctly');
test(function() {
const scroller = setupScrollTimelineTest();
// For simplicity, we set the timeRange such that currentTime maps directly to
// the value scrolled. We have a square scroller/contents, so can just compute
// one edge and use it for all the timelines;
const scrollerSize = scroller.scrollHeight - scroller.clientHeight;
const lengthScrollTimeline = new ScrollTimeline({
scrollSource: scroller,
timeRange: scrollerSize,
orientation: 'block',
startScrollOffset: '20px'
});
const percentageScrollTimeline = new ScrollTimeline({
scrollSource: scroller,
timeRange: scrollerSize,
orientation: 'block',
startScrollOffset: '20%'
});
const calcScrollTimeline = new ScrollTimeline({
scrollSource: scroller,
timeRange: scrollerSize,
orientation: 'block',
startScrollOffset: 'calc(20% - 5px)'
});
// Unscrolled, all timelines should read a current time of unresolved, since
// the current offset (0) will be less than the startScrollOffset.
assert_equals(lengthScrollTimeline.currentTime, NaN);
assert_equals(percentageScrollTimeline.currentTime, NaN);
assert_equals(calcScrollTimeline.currentTime, NaN);
// Check the length-based ScrollTimeline.
scroller.scrollTop = 19;
assert_equals(lengthScrollTimeline.currentTime, NaN);
scroller.scrollTop = 20;
assert_equals(lengthScrollTimeline.currentTime, 0);
scroller.scrollTop = 50;
assert_equals(
lengthScrollTimeline.currentTime,
calculateCurrentTime(50, 20, scrollerSize, scrollerSize));
scroller.scrollTop = 200;
assert_equals(
lengthScrollTimeline.currentTime,
calculateCurrentTime(200, 20, scrollerSize, scrollerSize));
// Check the percentage-based ScrollTimeline.
scroller.scrollTop = 0.19 * scrollerSize;
assert_equals(percentageScrollTimeline.currentTime, NaN);
scroller.scrollTop = 0.20 * scrollerSize;
assert_equals(percentageScrollTimeline.currentTime, 0);
scroller.scrollTop = 0.50 * scrollerSize;
assert_equals(
percentageScrollTimeline.currentTime,
calculateCurrentTime(
scroller.scrollTop, 0.2 * scrollerSize, scrollerSize, scrollerSize));
// Check the calc-based ScrollTimeline.
scroller.scrollTop = 0.2 * scrollerSize - 10;
assert_equals(calcScrollTimeline.currentTime, NaN);
scroller.scrollTop = 0.2 * scrollerSize - 5;
assert_equals(calcScrollTimeline.currentTime, 0);
scroller.scrollTop = 0.2 * scrollerSize;
assert_equals(
calcScrollTimeline.currentTime,
calculateCurrentTime(
scroller.scrollTop, 0.2 * scrollerSize - 5, scrollerSize,
scrollerSize));
scroller.scrollTop = 0.5 * scrollerSize;
assert_equals(
calcScrollTimeline.currentTime,
calculateCurrentTime(
scroller.scrollTop, 0.2 * scrollerSize - 5, scrollerSize,
scrollerSize));
}, 'currentTime handles startScrollOffset correctly');
test(function() {
const scrollerOverrides = new Map([['direction', 'rtl']]);
const scroller = setupScrollTimelineTest(scrollerOverrides);
// For simplicity, we set the timeRange such that currentTime maps directly to
// the value scrolled. We have a square scroller/contents, so can just compute
// one edge and use it for all the timelines;
const scrollerSize = scroller.scrollHeight - scroller.clientHeight;
const lengthScrollTimeline = new ScrollTimeline({
scrollSource: scroller,
timeRange: scrollerSize,
orientation: 'horizontal',
startScrollOffset: '20px'
});
const percentageScrollTimeline = new ScrollTimeline({
scrollSource: scroller,
timeRange: scrollerSize,
orientation: 'horizontal',
startScrollOffset: '20%'
});
const calcScrollTimeline = new ScrollTimeline({
scrollSource: scroller,
timeRange: scrollerSize,
orientation: 'horizontal',
startScrollOffset: 'calc(20% - 5px)'
});
// Unscrolled, all timelines should read a current time of unresolved, since
// the current offset (0) will be less than the startScrollOffset.
assert_equals(lengthScrollTimeline.currentTime, NaN);
assert_equals(percentageScrollTimeline.currentTime, NaN);
assert_equals(calcScrollTimeline.currentTime, NaN);
// With direction rtl offsets are inverted, such that scrollLeft ==
// scrollerSize is the 'zero' point for currentTime. However the
// startScrollOffset is an absolute distance along the offset, so doesn't
// need adjusting.
// Check the length-based ScrollTimeline.
scroller.scrollLeft = scrollerSize;
assert_equals(lengthScrollTimeline.currentTime, NaN);
scroller.scrollLeft = scrollerSize - 20;
assert_equals(lengthScrollTimeline.currentTime, 0);
scroller.scrollLeft = scrollerSize - 50;
assert_equals(
lengthScrollTimeline.currentTime,
calculateCurrentTime(50, 20, scrollerSize, scrollerSize));
scroller.scrollLeft = scrollerSize - 200;
assert_equals(
lengthScrollTimeline.currentTime,
calculateCurrentTime(200, 20, scrollerSize, scrollerSize));
// Check the percentage-based ScrollTimeline.
scroller.scrollLeft = scrollerSize - (0.19 * scrollerSize);
assert_equals(percentageScrollTimeline.currentTime, NaN);
scroller.scrollLeft = scrollerSize - (0.20 * scrollerSize);
assert_equals(percentageScrollTimeline.currentTime, 0);
scroller.scrollLeft = scrollerSize - (0.4 * scrollerSize);
assert_equals(
percentageScrollTimeline.currentTime,
calculateCurrentTime(
0.4 * scrollerSize, 0.2 * scrollerSize, scrollerSize, scrollerSize));
// Check the calc-based ScrollTimeline.
scroller.scrollLeft = scrollerSize - (0.2 * scrollerSize - 10);
assert_equals(calcScrollTimeline.currentTime, NaN);
scroller.scrollLeft = scrollerSize - (0.2 * scrollerSize - 5);
assert_equals(calcScrollTimeline.currentTime, 0);
scroller.scrollLeft = scrollerSize - (0.2 * scrollerSize);
assert_equals(
calcScrollTimeline.currentTime,
calculateCurrentTime(
0.2 * scrollerSize, 0.2 * scrollerSize - 5, scrollerSize,
scrollerSize));
scroller.scrollLeft = scrollerSize - (0.4 * scrollerSize);
assert_equals(
calcScrollTimeline.currentTime,
calculateCurrentTime(
0.4 * scrollerSize, 0.2 * scrollerSize - 5, scrollerSize,
scrollerSize));
}, 'currentTime handles startScrollOffset with direction: rtl correctly');
test(function() {
const scroller = setupScrollTimelineTest();
// For simplicity, we set the timeRange such that currentTime maps directly to
// the value scrolled. We have a square scroller/contents, so can just compute
// one edge and use it for all the timelines;
const scrollerSize = scroller.scrollHeight - scroller.clientHeight;
const lengthScrollTimeline = new ScrollTimeline({
scrollSource: scroller,
timeRange: scrollerSize,
orientation: 'block',
endScrollOffset: (scrollerSize - 20) + 'px'
});
const percentageScrollTimeline = new ScrollTimeline({
scrollSource: scroller,
timeRange: scrollerSize,
orientation: 'block',
endScrollOffset: '80%'
});
const calcScrollTimeline = new ScrollTimeline({
scrollSource: scroller,
timeRange: scrollerSize,
orientation: 'block',
endScrollOffset: 'calc(80% + 5px)'
});
// Check the length-based ScrollTimeline.
scroller.scrollTop = scrollerSize;
assert_equals(lengthScrollTimeline.currentTime, NaN);
scroller.scrollTop = scrollerSize - 20;
assert_equals(
lengthScrollTimeline.currentTime,
calculateCurrentTime(
scrollerSize - 20, 0, scrollerSize - 20, scrollerSize));
scroller.scrollTop = scrollerSize - 50;
assert_equals(
lengthScrollTimeline.currentTime,
calculateCurrentTime(
scrollerSize - 50, 0, scrollerSize - 20, scrollerSize));
scroller.scrollTop = scrollerSize - 200;
assert_equals(
lengthScrollTimeline.currentTime,
calculateCurrentTime(
scrollerSize - 200, 0, scrollerSize - 20, scrollerSize));
// Check the percentage-based ScrollTimeline.
scroller.scrollTop = 0.81 * scrollerSize;
assert_equals(percentageScrollTimeline.currentTime, NaN);
scroller.scrollTop = 0.80 * scrollerSize;
assert_equals(
percentageScrollTimeline.currentTime,
calculateCurrentTime(
scroller.scrollTop, 0, 0.8 * scrollerSize, scrollerSize));
scroller.scrollTop = 0.50 * scrollerSize;
assert_equals(
percentageScrollTimeline.currentTime,
calculateCurrentTime(
scroller.scrollTop, 0, 0.8 * scrollerSize, scrollerSize));
// Check the calc-based ScrollTimeline.
scroller.scrollTop = 0.8 * scrollerSize + 6;
assert_equals(calcScrollTimeline.currentTime, NaN);
scroller.scrollTop = 0.8 * scrollerSize + 5;
assert_equals(
calcScrollTimeline.currentTime,
calculateCurrentTime(
scroller.scrollTop, 0, 0.8 * scrollerSize + 5, scrollerSize));
scroller.scrollTop = 0.5 * scrollerSize;
assert_equals(
calcScrollTimeline.currentTime,
calculateCurrentTime(
scroller.scrollTop, 0, 0.8 * scrollerSize + 5, scrollerSize));
}, 'currentTime handles endScrollOffset correctly');
test(function() {
const scrollerOverrides = new Map([['direction', 'rtl']]);
const scroller = setupScrollTimelineTest(scrollerOverrides);
// For simplicity, we set the timeRange such that currentTime maps directly to
// the value scrolled. We have a square scroller/contents, so can just compute
// one edge and use it for all the timelines;
const scrollerSize = scroller.scrollHeight - scroller.clientHeight;
const lengthScrollTimeline = new ScrollTimeline({
scrollSource: scroller,
timeRange: scrollerSize,
orientation: 'horizontal',
endScrollOffset: (scrollerSize - 20) + 'px'
});
const percentageScrollTimeline = new ScrollTimeline({
scrollSource: scroller,
timeRange: scrollerSize,
orientation: 'horizontal',
endScrollOffset: '80%'
});
const calcScrollTimeline = new ScrollTimeline({
scrollSource: scroller,
timeRange: scrollerSize,
orientation: 'horizontal',
endScrollOffset: 'calc(80% + 5px)'
});
// With direction rtl offsets are inverted, such that scrollLeft ==
// scrollerSize is the 'zero' point for currentTime. However the
// endScrollOffset is an absolute distance along the offset, so doesn't need
// adjusting.
// Check the length-based ScrollTimeline.
scroller.scrollLeft = 0;
assert_equals(lengthScrollTimeline.currentTime, NaN);
scroller.scrollLeft = 20;
assert_equals(
lengthScrollTimeline.currentTime,
calculateCurrentTime(
scrollerSize - 20, 0, scrollerSize - 20, scrollerSize));
scroller.scrollLeft = 50;
assert_equals(
lengthScrollTimeline.currentTime,
calculateCurrentTime(
scrollerSize - 50, 0, scrollerSize - 20, scrollerSize));
scroller.scrollLeft = 200;
assert_equals(
lengthScrollTimeline.currentTime,
calculateCurrentTime(
scrollerSize - 200, 0, scrollerSize - 20, scrollerSize));
// Check the percentage-based ScrollTimeline.
scroller.scrollLeft = 0.19 * scrollerSize;
assert_equals(percentageScrollTimeline.currentTime, NaN);
scroller.scrollLeft = 0.20 * scrollerSize;
assert_equals(
percentageScrollTimeline.currentTime,
calculateCurrentTime(
0.8 * scrollerSize, 0, 0.8 * scrollerSize, scrollerSize));
scroller.scrollLeft = 0.4 * scrollerSize;
assert_equals(
percentageScrollTimeline.currentTime,
calculateCurrentTime(
0.6 * scrollerSize, 0, 0.8 * scrollerSize, scrollerSize));
// Check the calc-based ScrollTimeline. 80% + 5px
scroller.scrollLeft = 0.2 * scrollerSize - 10;
assert_equals(calcScrollTimeline.currentTime, NaN);
scroller.scrollLeft = 0.2 * scrollerSize - 5;
assert_equals(
calcScrollTimeline.currentTime,
calculateCurrentTime(
0.8 * scrollerSize + 5, 0, 0.8 * scrollerSize + 5, scrollerSize));
scroller.scrollLeft = 0.2 * scrollerSize;
assert_equals(
calcScrollTimeline.currentTime,
calculateCurrentTime(
0.8 * scrollerSize, 0, 0.8 * scrollerSize + 5, scrollerSize));
scroller.scrollLeft = 0.6 * scrollerSize;
assert_equals(
calcScrollTimeline.currentTime,
calculateCurrentTime(
0.4 * scrollerSize, 0, 0.8 * scrollerSize + 5, scrollerSize));
}, 'currentTime handles endScrollOffset with direction: rtl correctly');
test(function() {
const scroller = setupScrollTimelineTest();
// For simplicity, we set the timeRange such that currentTime maps directly to
// the value scrolled. We have a square scroller/contents, so can just compute
// one edge and use it for all the timelines;
const scrollerSize = scroller.scrollHeight - scroller.clientHeight;
const scrollTimeline = new ScrollTimeline({
scrollSource: scroller,
timeRange: scrollerSize,
orientation: 'block',
startScrollOffset: '20px',
endScrollOffset: (scrollerSize - 50) + 'px'
});
scroller.scrollTop = 150;
assert_equals(
scrollTimeline.currentTime,
calculateCurrentTime(150, 20, scrollerSize - 50, scrollerSize));
}, 'currentTime handles startScrollOffset and endScrollOffset together correctly');
test(function() {
const scroller = setupScrollTimelineTest();
// For simplicity, we set the timeRange such that currentTime maps directly to
// the value scrolled. We have a square scroller/contents, so can just compute
// one edge and use it for all the timelines;
const scrollerSize = scroller.scrollHeight - scroller.clientHeight;
const scrollTimeline = new ScrollTimeline({
scrollSource: scroller,
timeRange: scrollerSize,
orientation: 'block',
startScrollOffset: '20px',
endScrollOffset: '20px',
});
scroller.scrollTop = 150;
assert_equals(scrollTimeline.currentTime, NaN);
}, 'currentTime handles startScrollOffset == endScrollOffset correctly');
test(function() {
const scroller = setupScrollTimelineTest();
// For simplicity, we set the timeRange such that currentTime maps directly to
// the value scrolled. We have a square scroller/contents, so can just compute
// one edge and use it for all the timelines;
const scrollerSize = scroller.scrollHeight - scroller.clientHeight;
const scrollTimeline = new ScrollTimeline({
scrollSource: scroller,
timeRange: scrollerSize,
orientation: 'block',
startScrollOffset: '50px',
endScrollOffset: '10px',
});
scroller.scrollTop = 150;
assert_equals(scrollTimeline.currentTime, NaN);
}, 'currentTime handles startScrollOffset > endScrollOffset correctly');
</script> </script>
...@@ -4,13 +4,17 @@ ...@@ -4,13 +4,17 @@
#include "third_party/blink/renderer/core/animation/scroll_timeline.h" #include "third_party/blink/renderer/core/animation/scroll_timeline.h"
#include "third_party/blink/renderer/core/css/css_calculation_value.h"
#include "third_party/blink/renderer/core/css/css_to_length_conversion_data.h"
#include "third_party/blink/renderer/core/css/parser/css_tokenizer.h" #include "third_party/blink/renderer/core/css/parser/css_tokenizer.h"
#include "third_party/blink/renderer/core/css/properties/css_parsing_utils.h" #include "third_party/blink/renderer/core/css/properties/css_parsing_utils.h"
#include "third_party/blink/renderer/core/dom/node_computed_style.h"
#include "third_party/blink/renderer/core/layout/layout_box.h" #include "third_party/blink/renderer/core/layout/layout_box.h"
#include "third_party/blink/renderer/core/layout/layout_view.h" #include "third_party/blink/renderer/core/layout/layout_view.h"
#include "third_party/blink/renderer/core/paint/compositing/paint_layer_compositor.h" #include "third_party/blink/renderer/core/paint/compositing/paint_layer_compositor.h"
#include "third_party/blink/renderer/core/paint/paint_layer.h" #include "third_party/blink/renderer/core/paint/paint_layer.h"
#include "third_party/blink/renderer/core/paint/paint_layer_scrollable_area.h" #include "third_party/blink/renderer/core/paint/paint_layer_scrollable_area.h"
#include "third_party/blink/renderer/platform/length_functions.h"
namespace blink { namespace blink {
...@@ -159,22 +163,76 @@ double ScrollTimeline::currentTime(bool& is_null) { ...@@ -159,22 +163,76 @@ double ScrollTimeline::currentTime(bool& is_null) {
current_offset = scroll_offset.Height(); current_offset = scroll_offset.Height();
max_offset = scroll_dimensions.Height(); max_offset = scroll_dimensions.Height();
} }
// When using a rtl direction, current_offset grows correctly from 0 to
// max_offset, but is negative. Since our offsets are all just deltas along
// the orientation direction, we can just take the absolute current_offset and
// use that everywhere.
current_offset = std::abs(current_offset);
double resolved_start_scroll_offset = 0;
double resolved_end_scroll_offset = max_offset;
ResolveScrollStartAndEnd(layout_box, max_offset, resolved_start_scroll_offset,
resolved_end_scroll_offset);
// 3. If current scroll offset is less than startScrollOffset, return an // 3. If current scroll offset is less than startScrollOffset, return an
// unresolved time value if fill is none or forwards, or 0 otherwise. // unresolved time value if fill is none or forwards, or 0 otherwise.
// TODO(smcgruer): Implement |startScrollOffset| and |fill|. // TODO(smcgruer): Implement |fill|.
if (current_offset < resolved_start_scroll_offset) {
return std::numeric_limits<double>::quiet_NaN();
}
// 4. If current scroll offset is greater than or equal to endScrollOffset, // 4. If current scroll offset is greater than or equal to endScrollOffset,
// return an unresolved time value if fill is none or backwards, or the // return an unresolved time value if fill is none or backwards, or the
// effective time range otherwise. // effective time range otherwise.
// TODO(smcgruer): Implement |endScrollOffset| and |fill|. //
// TODO(smcgruer): Implement |fill|.
//
// Note we deliberately break the spec here by only returning if the current
// offset is strictly greater, as that is more in line with the web animation
// spec. See https://github.com/WICG/scroll-animations/issues/19
if (current_offset > resolved_end_scroll_offset) {
return std::numeric_limits<double>::quiet_NaN();
}
// This is not by the spec, but avoids both negative current time and a
// divsion by zero issue. See
// https://github.com/WICG/scroll-animations/issues/20 and
// https://github.com/WICG/scroll-animations/issues/21
if (resolved_start_scroll_offset >= resolved_end_scroll_offset) {
return std::numeric_limits<double>::quiet_NaN();
}
// 5. Return the result of evaluating the following expression: // 5. Return the result of evaluating the following expression:
// ((current scroll offset - startScrollOffset) / // ((current scroll offset - startScrollOffset) /
// (endScrollOffset - startScrollOffset)) * effective time range // (endScrollOffset - startScrollOffset)) * effective time range
is_null = false; is_null = false;
return (std::abs(current_offset) / max_offset) * time_range_; return ((current_offset - resolved_start_scroll_offset) /
(resolved_end_scroll_offset - resolved_start_scroll_offset)) *
time_range_;
}
void ScrollTimeline::ResolveScrollStartAndEnd(
const LayoutBox* layout_box,
double max_offset,
double& resolved_start_scroll_offset,
double& resolved_end_scroll_offset) {
const ComputedStyle& computed_style = layout_box->StyleRef();
Document& document = layout_box->GetDocument();
const ComputedStyle* root_style =
document.documentElement()
? document.documentElement()->GetComputedStyle()
: document.GetComputedStyle();
CSSToLengthConversionData conversion_data = CSSToLengthConversionData(
&computed_style, root_style, document.GetLayoutView(),
computed_style.EffectiveZoom());
if (start_scroll_offset_) {
resolved_start_scroll_offset = FloatValueForLength(
start_scroll_offset_->ConvertToLength(conversion_data), max_offset);
}
if (end_scroll_offset_) {
resolved_end_scroll_offset = FloatValueForLength(
end_scroll_offset_->ConvertToLength(conversion_data), max_offset);
}
} }
Element* ScrollTimeline::scrollSource() { Element* ScrollTimeline::scrollSource() {
......
...@@ -78,6 +78,11 @@ class CORE_EXPORT ScrollTimeline final : public AnimationTimeline { ...@@ -78,6 +78,11 @@ class CORE_EXPORT ScrollTimeline final : public AnimationTimeline {
CSSPrimitiveValue*, CSSPrimitiveValue*,
double); double);
void ResolveScrollStartAndEnd(const LayoutBox*,
double max_offset,
double& resolved_start_scroll_offset,
double& resolved_end_scroll_offset);
Member<Element> scroll_source_; Member<Element> scroll_source_;
ScrollDirection orientation_; ScrollDirection orientation_;
Member<CSSPrimitiveValue> start_scroll_offset_; Member<CSSPrimitiveValue> start_scroll_offset_;
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment