| <!DOCTYPE html> |
| <meta charset=utf-8> |
| <title>Verify applied effect output for all fill modes in all timeline states: before start, at start, in range, at end, after end while using various effect delay values</title> |
| <meta name="timeout" content="long"> |
| <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: hidden; |
| height: 200px; |
| width: 200px; |
| } |
| .contents { |
| /* Make scroll range 1000 to simplify the math and avoid rounding errors */ |
| height: 1200px; |
| width: 100%; |
| } |
| </style> |
| <div id="log"></div> |
| <script> |
| 'use strict'; |
| |
| test(t => { |
| const effect = new KeyframeEffect(createDiv(t), { opacity: [0.3, 0.7] }); |
| const animation = new Animation(effect, createScrollTimeline(t)); |
| |
| assert_equals(effect.getTiming().fill, "auto"); |
| assert_equals(effect.getComputedTiming().fill, "none"); |
| }, "Scroll based animation effect fill mode should return 'auto' for" + |
| " getTiming() and should return 'none' for getComputedTiming().") |
| |
| // Test cases are included where effect delay causes the effect iteration to |
| // overlap with the timeline start time and also the timeline end time. |
| // Timeline |
| // BEFORE +-----------------+ AFTER |
| // time: 0 timeline.duration |
| // 1) +-----------------+ |
| // 2) +------------+ |
| // 3) +---------------------+ |
| // 4) +---------------------+ |
| // 5) +-------------+ |
| // 6) +---------+ |
| // 7) +-----------------+ |
| // 8) +-----------------+ |
| // 9) +-------------------------+ |
| |
| // Note: effects are scaled to fill the timeline so that the start of the |
| // effect is after the start offset of the timeline, and that the end of the |
| // effect is at the beginning of the end offset of the timeline. |
| |
| /* All interesting transitions: |
| before timeline start |
| at timeline start |
| in timeline range |
| at timeline end |
| after timeline end |
| |
| test_case data structure: |
| fill_mode: { |
| scroll_percentage: ["state description", expected applied effect value] |
| } |
| */ |
| const test_cases = { |
| "none": { |
| 0.10: ["before timeline start", 1], |
| 0.25: ["at timeline start", 0.3], |
| 0.50: ["in timeline range", 0.5], |
| 0.75: ["at timeline end", 1], |
| 0.90: ["after timeline end", 1] |
| }, |
| "backwards": { |
| 0.10: ["before timeline start", 0.3], |
| 0.25: ["at timeline start", 0.3], |
| 0.50: ["in timeline range", 0.5], |
| 0.75: ["at timeline end", 1], |
| 0.90: ["after timeline end", 1] |
| }, |
| /* |
| There is an asymmetry between these 2 mirrored scenarios: |
| 1. fillmode: forwards + "at timeline end" |
| 2. fillmode: backwards + "at timeline start" |
| |
| In scenario 1, effect is not applied |
| In scenario 2, effect is applied |
| |
| The difference is accounted for by the equality at the end versus strict |
| inequality at the start when computing the timeline phase. |
| |
| This is currently in line with spec expectations but is an issue that |
| should be addressed. |
| */ |
| "forwards": { |
| 0.10: ["before timeline start", 1], |
| 0.25: ["at timeline start", 0.3], |
| 0.50: ["in timeline range", 0.5], |
| 0.75: ["at timeline end", 0.7], |
| 0.90: ["after timeline end", 0.7] |
| }, |
| "both": { |
| 0.10: ["before timeline start", 0.3], |
| 0.25: ["at timeline start", 0.3], |
| 0.50: ["in timeline range", 0.5], |
| 0.75: ["at timeline end", 0.7], |
| 0.90: ["after timeline end", 0.7] |
| }, |
| // "auto" behaves differently for different effect delay values. These |
| // cases are handled in scroll-animation-effect-phases.tentative.html |
| "auto": { |
| 0.10: ["before timeline start", 1], |
| 0.25: ["at timeline start", 0.3], |
| 0.50: ["in timeline range", 0.5], |
| 0.75: ["at timeline end", 1], |
| 0.90: ["after timeline end", 1] |
| } |
| } |
| |
| for (const fill_mode in test_cases) { |
| const scroll_percents = test_cases[fill_mode] |
| |
| for (const scroll_percentage in scroll_percents) { |
| const expectation = scroll_percents[scroll_percentage]; |
| |
| const [test_name, expected_value] = expectation; |
| |
| const description = |
| `Applied effect value ${test_name} with fill: ${fill_mode}` |
| promise_test( |
| create_scroll_timeline_fill_test( |
| fill_mode, scroll_percentage, expected_value), |
| description); |
| } |
| } |
| |
| function create_scroll_timeline_fill_test( |
| fill_mode, scroll_percentage, expected){ |
| return async t => { |
| const target = createDiv(t); |
| |
| const timeline = |
| createScrollTimelineWithOffsets(t, CSS.percent(25), CSS.percent(75)); |
| const effect = new KeyframeEffect( |
| target, |
| { |
| opacity: [0.3, 0.7] |
| }, |
| { |
| fill: fill_mode |
| } |
| ); |
| const animation = new Animation(effect, timeline); |
| const scroller = timeline.source; |
| const maxScroll = scroller.scrollHeight - scroller.clientHeight; |
| |
| animation.play(); |
| |
| await animation.ready; |
| |
| scroller.scrollTop = scroll_percentage * maxScroll; |
| |
| // Wait for new animation frame which allows the timeline to compute |
| // new current time. |
| await waitForNextFrame(); |
| |
| assert_equals( |
| parseFloat(window.getComputedStyle(target).opacity), |
| expected, |
| "animation effect applied property value"); |
| } |
| } |
| </script> |