blob: bdd27487ac8dfe8bc2286b689a4a6142854ef725 [file] [log] [blame]
<!DOCTYPE html>
<meta charset="utf-8">
<title>ScrollTimeline current time algorithm</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="./resources/scrolltimeline-utils.js"></script>
<body></body>
<script>
'use strict';
test(function() {
const scroller = setupScrollTimelineTest();
// Set the timeRange such that currentTime maps directly to the value
// scrolled. The contents and scroller are square, so it suffices to compute
// one edge and use it for all the timelines.
const scrollerSize = scroller.scrollHeight - scroller.clientHeight;
const blockScrollTimeline = new ScrollTimeline(
{scrollSource: scroller, timeRange: scrollerSize, orientation: 'block'});
const inlineScrollTimeline = new ScrollTimeline(
{scrollSource: scroller, timeRange: scrollerSize, orientation: 'inline'});
const horizontalScrollTimeline = new ScrollTimeline({
scrollSource: scroller,
timeRange: scrollerSize,
orientation: 'horizontal'
});
const verticalScrollTimeline = new ScrollTimeline({
scrollSource: scroller,
timeRange: scrollerSize,
orientation: 'vertical'
});
// Unscrolled, all timelines should read a currentTime of 0.
assert_equals(
blockScrollTimeline.currentTime, 0, 'Unscrolled block timeline');
assert_equals(
inlineScrollTimeline.currentTime, 0, 'Unscrolled inline timeline');
assert_equals(
horizontalScrollTimeline.currentTime, 0,
'Unscrolled horizontal timeline');
assert_equals(
verticalScrollTimeline.currentTime, 0, 'Unscrolled vertical timeline');
// Do some scrolling and make sure that the ScrollTimelines update.
scroller.scrollTop = 50;
scroller.scrollLeft = 75;
// As noted above timeRange is mapped such that currentTime should be the
// scroll offset.
assert_equals(blockScrollTimeline.currentTime, 50, 'Scrolled block timeline');
assert_equals(
inlineScrollTimeline.currentTime, 75, 'Scrolled inline timeline');
assert_equals(
horizontalScrollTimeline.currentTime, 75, 'Scrolled horizontal timeline');
assert_equals(
verticalScrollTimeline.currentTime, 50, 'Scrolled vertical timeline');
}, 'currentTime calculates the correct time based on scrolled amount');
test(function() {
// It is difficult to calculate the scroll offset which results in an exact
// currentTime. Scrolling is calculated in integers which allows for the
// possibility of rounding, and scrollbar widths differ between platforms
// which means it is not possible to ensure a divisible scroller size. Instead
// the scroller content is made large enough that rounding differences result
// in negligible deltas in the output value.
const contentOverrides = new Map([['width', '1000px'], ['height', '1000px']]);
const scroller = setupScrollTimelineTest(new Map(), contentOverrides);
const scrollTimeline = new ScrollTimeline(
{scrollSource: scroller, timeRange: 100, orientation: 'block'});
// Mapping timeRange to 100 means the output is 'percentage scrolled', so
// calculate where the 50% scroll mark would be.
const halfwayY = (scroller.scrollHeight - scroller.clientHeight) / 2;
scroller.scrollTop = halfwayY;
assert_approx_equals(scrollTimeline.currentTime, 50, 0.5);
}, 'currentTime adjusts correctly for the timeRange');
test(function() {
const scroller = setupScrollTimelineTest();
// Set the timeRange such that currentTime maps directly to the value
// scrolled. The contents and scroller are square, so it suffices to 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, as the
// current offset (0) will be less than the startScrollOffset.
assert_equals(
lengthScrollTimeline.currentTime, null,
'Unscrolled length-based timeline');
assert_equals(
percentageScrollTimeline.currentTime, null,
'Unscrolled percentage-based timeline');
assert_equals(
calcScrollTimeline.currentTime, null, 'Unscrolled calc-based timeline');
// Check the length-based ScrollTimeline.
scroller.scrollTop = 19;
assert_equals(
lengthScrollTimeline.currentTime, null,
'Length-based timeline before the startScrollOffset point');
scroller.scrollTop = 20;
assert_equals(
lengthScrollTimeline.currentTime, 0,
'Length-based timeline at the startScrollOffset point');
scroller.scrollTop = 50;
assert_equals(
lengthScrollTimeline.currentTime,
calculateCurrentTime(50, 20, scrollerSize, scrollerSize),
'Length-based timeline after the startScrollOffsetPoint');
// Check the percentage-based ScrollTimeline.
scroller.scrollTop = 0.19 * scrollerSize;
assert_equals(
percentageScrollTimeline.currentTime, null,
'Percentage-based scroller before the startScrollOffset point');
scroller.scrollTop = 0.20 * scrollerSize;
assert_equals(
percentageScrollTimeline.currentTime, 0,
'Percentage-based scroller at the startScrollOffset point');
scroller.scrollTop = 0.50 * scrollerSize;
assert_equals(
percentageScrollTimeline.currentTime,
calculateCurrentTime(
scroller.scrollTop, 0.2 * scrollerSize, scrollerSize, scrollerSize),
'Percentage-based scroller after the startScrollOffset point');
// Check the calc-based ScrollTimeline.
scroller.scrollTop = 0.2 * scrollerSize - 10;
assert_equals(
calcScrollTimeline.currentTime, null,
'Calc-based scroller before the startScrollOffset point');
scroller.scrollTop = 0.2 * scrollerSize - 5;
assert_equals(
calcScrollTimeline.currentTime, 0,
'Calc-based scroller at the startScrollOffset point');
scroller.scrollTop = 0.2 * scrollerSize;
assert_equals(
calcScrollTimeline.currentTime,
calculateCurrentTime(
scroller.scrollTop, 0.2 * scrollerSize - 5, scrollerSize,
scrollerSize),
'Calc-based scroller after the startScrollOffset point');
}, 'currentTime handles startScrollOffset correctly');
test(function() {
const scroller = setupScrollTimelineTest();
// Set the timeRange such that currentTime maps directly to the value
// scrolled. The contents and scroller are square, so it suffices to 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, null,
'Length-based timeline after the endScrollOffset point');
scroller.scrollTop = scrollerSize - 20;
assert_equals(
lengthScrollTimeline.currentTime, null,
'Length-based timeline at the endScrollOffset point');
scroller.scrollTop = scrollerSize - 50;
assert_equals(
lengthScrollTimeline.currentTime,
calculateCurrentTime(
scrollerSize - 50, 0, scrollerSize - 20, scrollerSize),
'Length-based timeline before the endScrollOffset point');
// Check the percentage-based ScrollTimeline.
scroller.scrollTop = 0.81 * scrollerSize;
assert_equals(
percentageScrollTimeline.currentTime, null,
'Percentage-based timeline after the endScrollOffset point');
scroller.scrollTop = 0.80 * scrollerSize;
assert_equals(
percentageScrollTimeline.currentTime, null,
'Percentage-based timeline at the endScrollOffset point');
scroller.scrollTop = 0.50 * scrollerSize;
assert_equals(
percentageScrollTimeline.currentTime,
calculateCurrentTime(
scroller.scrollTop, 0, 0.8 * scrollerSize, scrollerSize),
'Percentage-based timeline before the endScrollOffset point');
// Check the calc-based ScrollTimeline.
scroller.scrollTop = 0.8 * scrollerSize + 6;
assert_equals(
calcScrollTimeline.currentTime, null,
'Calc-based timeline after the endScrollOffset point');
scroller.scrollTop = 0.8 * scrollerSize + 5;
assert_equals(
calcScrollTimeline.currentTime, null,
'Calc-based timeline at the endScrollOffset point');
scroller.scrollTop = 0.5 * scrollerSize;
assert_equals(
calcScrollTimeline.currentTime,
calculateCurrentTime(
scroller.scrollTop, 0, 0.8 * scrollerSize + 5, scrollerSize),
'Calc-based timeline before the endScrollOffset point');
}, 'currentTime handles endScrollOffset correctly');
test(function() {
const scroller = setupScrollTimelineTest();
// Set the timeRange such that currentTime maps directly to the value
// scrolled. The contents and scroller are square, so it suffices to compute
// one edge and use it for all the timelines.
const scrollerSize = scroller.scrollHeight - scroller.clientHeight;
// When the endScrollOffset is equal to the maximum scroll offset (and there
// are no fill modes), the endScrollOffset is treated as inclusive.
const inclusiveAutoScrollTimeline = new ScrollTimeline({
scrollSource: scroller,
timeRange: scrollerSize,
orientation: 'block',
endScrollOffset: 'auto'
});
const inclusiveLengthScrollTimeline = new ScrollTimeline({
scrollSource: scroller,
timeRange: scrollerSize,
orientation: 'block',
endScrollOffset: scrollerSize + 'px'
});
const inclusivePercentageScrollTimeline = new ScrollTimeline({
scrollSource: scroller,
timeRange: scrollerSize,
orientation: 'block',
endScrollOffset: '100%'
});
const inclusiveCalcScrollTimeline = new ScrollTimeline({
scrollSource: scroller,
timeRange: scrollerSize,
orientation: 'block',
endScrollOffset: 'calc(80% + ' + (0.2 * scrollerSize) + 'px)'
});
scroller.scrollTop = scrollerSize;
let expectedCurrentTime = calculateCurrentTime(
scroller.scrollTop, 0, scrollerSize, scrollerSize);
assert_equals(
inclusiveAutoScrollTimeline.currentTime, expectedCurrentTime,
'Inclusive auto timeline at the endScrollOffset point');
assert_equals(
inclusiveLengthScrollTimeline.currentTime, expectedCurrentTime,
'Inclusive length-based timeline at the endScrollOffset point');
assert_equals(
inclusivePercentageScrollTimeline.currentTime, expectedCurrentTime,
'Inclusive percentage-based timeline at the endScrollOffset point');
assert_equals(
inclusiveCalcScrollTimeline.currentTime, expectedCurrentTime,
'Inclusive calc-based timeline at the endScrollOffset point');
}, 'currentTime handles endScrollOffset correctly (inclusive cases)');
test(function() {
const scroller = setupScrollTimelineTest();
// Set the timeRange such that currentTime maps directly to the value
// scrolled. The contents and scroller are square, so it suffices to 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();
// Set the timeRange such that currentTime maps directly to the value
// scrolled. The contents and scroller are square, so it suffices to 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, null);
}, 'currentTime handles startScrollOffset == endScrollOffset correctly');
test(function() {
const scroller = setupScrollTimelineTest();
// Set the timeRange such that currentTime maps directly to the value
// scrolled. The contents and scroller are square, so it suffices to 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, null);
}, 'currentTime handles startScrollOffset > endScrollOffset correctly');
test(function() {
const scroller = setupScrollTimelineTest();
const scrollerSize = scroller.scrollHeight - scroller.clientHeight;
const timeRange = 100;
const startScrollOffset = 20;
const endScrollOffset = scrollerSize - 20;
const forwardsFillingScrollTimeline = new ScrollTimeline({
scrollSource: scroller,
timeRange: timeRange,
orientation: 'block',
startScrollOffset: startScrollOffset + 'px',
endScrollOffset: endScrollOffset + 'px',
fill: 'forwards'
});
const backwardsFillingScrollTimeline = new ScrollTimeline({
scrollSource: scroller,
timeRange: timeRange,
orientation: 'block',
startScrollOffset: startScrollOffset + 'px',
endScrollOffset: endScrollOffset + 'px',
fill: 'backwards'
});
const bothFillingScrollTimeline = new ScrollTimeline({
scrollSource: scroller,
timeRange: timeRange,
orientation: 'block',
startScrollOffset: startScrollOffset + 'px',
endScrollOffset: endScrollOffset + 'px',
fill: 'both'
});
// 'auto' should be equivalent to 'both'.
const autoFillingScrollTimeline = new ScrollTimeline({
scrollSource: scroller,
timeRange: timeRange,
orientation: 'block',
startScrollOffset: startScrollOffset + 'px',
endScrollOffset: endScrollOffset + 'px',
fill: 'auto'
});
// Before the startScrollOffset the current time should be 0 for backwards or
// both, and unresolved otherwise.
scroller.scrollTop = startScrollOffset - 10;
assert_equals(
forwardsFillingScrollTimeline.currentTime, null,
'Before startScrollOffset forwards-filling timeline');
assert_equals(
backwardsFillingScrollTimeline.currentTime, 0,
'Before startScrollOffset backwards-filling timeline');
assert_equals(
bothFillingScrollTimeline.currentTime, 0,
'Before startScrollOffset both-filling timeline');
assert_equals(
autoFillingScrollTimeline.currentTime, 0,
'Before startScrollOffset auto-filling timeline');
// At the endScrollOffset the current time should be time-range for
// forwards or both, and unresolved otherwise.
scroller.scrollTop = endScrollOffset;
assert_equals(
forwardsFillingScrollTimeline.currentTime, timeRange,
'After endScrollOffset forwards-filling timeline');
assert_equals(
backwardsFillingScrollTimeline.currentTime, null,
'After endScrollOffset backwards-filling timeline');
assert_equals(
bothFillingScrollTimeline.currentTime, timeRange,
'After endScrollOffset both-filling timeline');
assert_equals(
autoFillingScrollTimeline.currentTime, timeRange,
'After endScrollOffset auto-filling timeline');
// After the endScrollOffset the current time should be time-range for
// forwards or both, and unresolved otherwise.
scroller.scrollTop = endScrollOffset + 10;
assert_equals(
forwardsFillingScrollTimeline.currentTime, timeRange,
'After endScrollOffset forwards-filling timeline');
assert_equals(
backwardsFillingScrollTimeline.currentTime, null,
'After endScrollOffset backwards-filling timeline');
assert_equals(
bothFillingScrollTimeline.currentTime, timeRange,
'After endScrollOffset both-filling timeline');
assert_equals(
autoFillingScrollTimeline.currentTime, timeRange,
'After endScrollOffset auto-filling timeline');
}, 'currentTime handles fill modes correctly');
</script>