Source code
Revision control
Copy as Markdown
Other Tools
Test Info:
- This WPT test may be referenced by the following Test IDs:
- /scroll-animations/animation-trigger/animation-trigger-multiple-triggers.tentative.html - WPT Dashboard Interop Dashboard
<!DOCTYPE html>
<html>
<head>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="/web-animations/testcommon.js"></script>
<script src="/dom/events/scrolling/scroll_support.js"></script>
<script src="support/support.js"></script>
</head>
<body>
<style>
.subject, .target {
height: 50px;
width: 50px;
background-color: red;
}
.scroller {
overflow-y: scroll;
height: 500px;
width: 500px;
border: solid 1px;
position: relative;
}
#space {
width: 50px;
height: 600px;
}
</style>
<div id="wrapper">
<div id="scroller" class="scroller">
<div id="space"></div>
<div id="subject1" class="subject"></div>
<div id="space"></div>
<div id="subject2" class="subject"></div>
<div id="space"></div>
</div>
<div id="target" class="target"></div>
</div>
<script>
// The trigger and exit ranges are the same for this test.
const TRIGGER_START_PX = 150;
const TRIGGER_END_PX = 200;
const scroller = document.getElementById("scroller");
const subject1 = document.getElementById("subject1");
const subject2 = document.getElementById("subject2");
const target = document.getElementById("target");
function getRangeBoundariesForSubject(subject, scroller) {
const cover_start_offset = subject.offsetTop - scroller.clientHeight;
return getRangeBoundariesForTest(
cover_start_offset + TRIGGER_START_PX,
cover_start_offset + TRIGGER_END_PX,
cover_start_offset + TRIGGER_START_PX,
cover_start_offset + TRIGGER_END_PX,
scroller);
}
const rangeBoundaries1 = getRangeBoundariesForSubject(subject1,
scroller);
const rangeBoundaries2 = getRangeBoundariesForSubject(subject2,
scroller);
const ANIMATION_DURATION_MS = 1;
const view_timeline1 = new ViewTimeline({ subject: subject1 });
const view_timeline2 = new ViewTimeline({ subject: subject2 });
function setupAnimation() {
const animation = new Animation(
new KeyframeEffect(
target,
[
{ transform: "scaleX(1)", backgroundColor: "pink" },
{ transform: "scaleX(5)", backgroundColor: "pink" }
],
{ duration: ANIMATION_DURATION_MS, fill: "both" }
));
return animation;
}
function setupAnimationTrigger(view_timeline) {
const trigger = new AnimationTrigger({
type: "repeat",
timeline: view_timeline,
rangeStart: `${TRIGGER_START_PX}px`,
rangeEnd: `${TRIGGER_END_PX}px`
});
return trigger;
}
promise_test(async (test) => {
const animation = setupAnimation();
const trigger1 = setupAnimationTrigger(view_timeline1);
const trigger2 = setupAnimationTrigger(view_timeline2);
// Test preconditions.
assert_equals(animation.playState, "idle", "animation is idle");
assert_equals(animation.currentTime, null, "currentTime is null");
assert_equals(scroller.scrollTop, 0, "scroller is not scrolled, i.e. " +
"not within the trigger range");
trigger1.addAnimation(animation);
trigger2.addAnimation(animation);
// Test preconditions.
assert_equals(animation.playState, "paused", "animation is idle");
assert_equals(animation.currentTime, 0, "currentTime is null");
await rangeBoundaries1.enterTriggerRange();
await animation.finished;
assert_equals(animation.playState, "finished",
"animation is played by trigger1");
assert_times_equal(animation.currentTime, ANIMATION_DURATION_MS,
`currentTime is ${ANIMATION_DURATION_MS}`);
await rangeBoundaries1.exitExitRangeBelow();
await waitForNextFrame();
assert_equals(animation.playState, "paused",
"animation should be reset by trigger1");
assert_times_equal(animation.currentTime, 0, "currentTime is 0");
// animation should be played by trigger2.
rangeBoundaries2.enterTriggerRange();
await animation.finished;
assert_equals(animation.playState, "finished",
"animation is paused, awaiting trigger event");
assert_times_equal(animation.currentTime, ANIMATION_DURATION_MS,
`currentTime is ${ANIMATION_DURATION_MS}`);
// animation should be reversed by trigger2.
rangeBoundaries2.exitExitRangeBelow();
await waitForNextFrame();
assert_equals(animation.playState, "paused",
"animation is paused, awaiting trigger event");
assert_times_equal(animation.currentTime, 0, `currentTime is 0`);
}, "Triggers on same animation; no conflict.");
promise_test(async (test) => {
await waitForScrollReset(test, scroller);
const animation = setupAnimation();
const trigger1 = setupAnimationTrigger(view_timeline1);
const trigger2 = setupAnimationTrigger(view_timeline2);
// Test preconditions.
assert_equals(animation.playState, "idle", "animation is idle");
assert_equals(animation.currentTime, null, "currentTime is null");
assert_equals(scroller.scrollTop, 0, "scroller is not scrolled, i.e. " +
"not within the trigger range");
trigger1.addAnimation(animation);
trigger2.addAnimation(animation);
// Test preconditions.
assert_equals(animation.playState, "paused", "animation is paused");
assert_equals(animation.currentTime, 0, "currentTime is null");
await rangeBoundaries1.enterTriggerRange();
await animation.finished;
assert_equals(animation.playState, "finished",
"animation is played by trigger1");
assert_times_equal(animation.currentTime, ANIMATION_DURATION_MS,
`currentTime is ${ANIMATION_DURATION_MS}`);
// Entering trigger2's trigger range exits trigger1's exit range.
// So trigger1 wants to do a reset and trigger2 wants to play the
// animation. As trigger2 was the most recently created, it wins and the
// animation is played.
rangeBoundaries2.enterTriggerRange();
await animation.finished;
assert_equals(animation.playState, "finished",
"animation is paused, awaiting trigger event");
assert_times_equal(animation.currentTime, ANIMATION_DURATION_MS,
`currentTime is ${ANIMATION_DURATION_MS}`);
}, "Triggers on same animation; conflict.");
</script>
</body>
</html>