reversing-an-animation.html (9519B)
1 <!DOCTYPE html> 2 <meta charset=utf-8> 3 <title>Reversing an animation</title> 4 <link rel="help" 5 href="https://drafts.csswg.org/web-animations/#reversing-an-animation-section"> 6 <script src="/resources/testharness.js"></script> 7 <script src="/resources/testharnessreport.js"></script> 8 <script src="../../testcommon.js"></script> 9 <body> 10 <div id="log"></div> 11 <script> 12 'use strict'; 13 14 promise_test(async t => { 15 const div = createDiv(t); 16 const animation = div.animate({}, { duration: 100 * MS_PER_SEC, 17 iterations: Infinity }); 18 19 await animation.ready; 20 // Wait a frame because if currentTime is still 0 when we call 21 // reverse(), it will throw (per spec). 22 await waitForAnimationFrames(1); 23 24 assert_greater_than_equal(animation.currentTime, 0, 25 'currentTime expected to be greater than 0, one frame after starting'); 26 animation.currentTime = 50 * MS_PER_SEC; 27 const previousPlaybackRate = animation.playbackRate; 28 animation.reverse(); 29 assert_equals(animation.playbackRate, previousPlaybackRate, 30 'Playback rate should not have changed'); 31 await animation.ready; 32 33 assert_equals(animation.playbackRate, -previousPlaybackRate, 34 'Playback rate should be inverted'); 35 }, 'Reversing an animation inverts the playback rate'); 36 37 promise_test(async t => { 38 const div = createDiv(t); 39 const animation = div.animate({}, { duration: 100 * MS_PER_SEC, 40 iterations: Infinity }); 41 animation.currentTime = 50 * MS_PER_SEC; 42 animation.pause(); 43 44 await animation.ready; 45 46 animation.reverse(); 47 await animation.ready; 48 49 assert_equals(animation.playState, 'running', 50 'Animation.playState should be "running" after reverse()'); 51 }, 'Reversing an animation plays a pausing animation'); 52 53 test(t => { 54 const div = createDiv(t); 55 const animation = div.animate({}, 100 * MS_PER_SEC); 56 animation.currentTime = 50 * MS_PER_SEC; 57 animation.reverse(); 58 59 assert_equals(animation.currentTime, 50 * MS_PER_SEC, 60 'The current time should not change it is in the middle of ' + 61 'the animation duration'); 62 }, 'Reversing an animation maintains the same current time'); 63 64 test(t => { 65 const div = createDiv(t); 66 const animation = div.animate({}, { duration: 200 * MS_PER_SEC, 67 delay: -100 * MS_PER_SEC }); 68 assert_true(animation.pending, 69 'The animation is pending before we call reverse'); 70 71 animation.reverse(); 72 73 assert_true(animation.pending, 74 'The animation is still pending after calling reverse'); 75 }, 'Reversing an animation does not cause it to leave the pending state'); 76 77 promise_test(async t => { 78 const div = createDiv(t); 79 const animation = div.animate({}, { duration: 200 * MS_PER_SEC, 80 delay: -100 * MS_PER_SEC }); 81 let readyResolved = false; 82 animation.ready.then(() => { readyResolved = true; }); 83 84 animation.reverse(); 85 86 await Promise.resolve(); 87 assert_false(readyResolved, 88 'ready promise should not have been resolved yet'); 89 }, 'Reversing an animation does not cause it to resolve the ready promise'); 90 91 test(t => { 92 const div = createDiv(t); 93 const animation = div.animate({}, 100 * MS_PER_SEC); 94 animation.currentTime = 200 * MS_PER_SEC; 95 animation.reverse(); 96 97 assert_equals(animation.currentTime, 100 * MS_PER_SEC, 98 'reverse() should start playing from the animation effect end ' + 99 'if the playbackRate > 0 and the currentTime > effect end'); 100 }, 'Reversing an animation when playbackRate > 0 and currentTime > ' + 101 'effect end should make it play from the end'); 102 103 test(t => { 104 const div = createDiv(t); 105 const animation = div.animate({}, 100 * MS_PER_SEC); 106 107 animation.currentTime = -200 * MS_PER_SEC; 108 animation.reverse(); 109 110 assert_equals(animation.currentTime, 100 * MS_PER_SEC, 111 'reverse() should start playing from the animation effect end ' + 112 'if the playbackRate > 0 and the currentTime < 0'); 113 }, 'Reversing an animation when playbackRate > 0 and currentTime < 0 ' + 114 'should make it play from the end'); 115 116 test(t => { 117 const div = createDiv(t); 118 const animation = div.animate({}, 100 * MS_PER_SEC); 119 animation.playbackRate = -1; 120 animation.currentTime = -200 * MS_PER_SEC; 121 animation.reverse(); 122 123 assert_equals(animation.currentTime, 0, 124 'reverse() should start playing from the start of animation time ' + 125 'if the playbackRate < 0 and the currentTime < 0'); 126 }, 'Reversing an animation when playbackRate < 0 and currentTime < 0 ' + 127 'should make it play from the start'); 128 129 test(t => { 130 const div = createDiv(t); 131 const animation = div.animate({}, 100 * MS_PER_SEC); 132 animation.playbackRate = -1; 133 animation.currentTime = 200 * MS_PER_SEC; 134 animation.reverse(); 135 136 assert_equals(animation.currentTime, 0, 137 'reverse() should start playing from the start of animation time ' + 138 'if the playbackRate < 0 and the currentTime > effect end'); 139 }, 'Reversing an animation when playbackRate < 0 and currentTime > effect ' + 140 'end should make it play from the start'); 141 142 test(t => { 143 const div = createDiv(t); 144 const animation = div.animate({}, { duration: 100 * MS_PER_SEC, 145 iterations: Infinity }); 146 animation.currentTime = -200 * MS_PER_SEC; 147 148 assert_throws_dom('InvalidStateError', 149 () => { animation.reverse(); }, 150 'reverse() should throw InvalidStateError ' + 151 'if the playbackRate > 0 and the currentTime < 0 ' + 152 'and the target effect is positive infinity'); 153 }, 'Reversing an animation when playbackRate > 0 and currentTime < 0 ' + 154 'and the target effect end is positive infinity should throw an exception'); 155 156 promise_test(async t => { 157 const animation = createDiv(t).animate({}, { duration: 100 * MS_PER_SEC, 158 iterations: Infinity }); 159 animation.currentTime = -200 * MS_PER_SEC; 160 161 try { animation.reverse(); } catch(e) { } 162 163 assert_equals(animation.playbackRate, 1, 'playbackRate is unchanged'); 164 165 await animation.ready; 166 assert_equals(animation.playbackRate, 1, 'playbackRate remains unchanged'); 167 }, 'When reversing throws an exception, the playback rate remains unchanged'); 168 169 test(t => { 170 const div = createDiv(t); 171 const animation = div.animate({}, { duration: 100 * MS_PER_SEC, 172 iterations: Infinity }); 173 animation.currentTime = -200 * MS_PER_SEC; 174 animation.playbackRate = 0; 175 176 try { 177 animation.reverse(); 178 } catch (e) { 179 assert_unreached(`Unexpected exception when calling reverse(): ${e}`); 180 } 181 }, 'Reversing animation when playbackRate = 0 and currentTime < 0 ' + 182 'and the target effect end is positive infinity should NOT throw an ' + 183 'exception'); 184 185 test(t => { 186 const div = createDiv(t); 187 const animation = div.animate({}, { duration: 100 * MS_PER_SEC, 188 iterations: Infinity }); 189 animation.playbackRate = -1; 190 animation.currentTime = -200 * MS_PER_SEC; 191 animation.reverse(); 192 193 assert_equals(animation.currentTime, 0, 194 'reverse() should start playing from the start of animation time ' + 195 'if the playbackRate < 0 and the currentTime < 0 ' + 196 'and the target effect is positive infinity'); 197 }, 'Reversing an animation when playbackRate < 0 and currentTime < 0 ' + 198 'and the target effect end is positive infinity should make it play ' + 199 'from the start'); 200 201 promise_test(async t => { 202 const div = createDiv(t); 203 const animation = div.animate({}, 100 * MS_PER_SEC); 204 animation.playbackRate = 0; 205 animation.currentTime = 50 * MS_PER_SEC; 206 animation.reverse(); 207 208 await animation.ready; 209 assert_equals(animation.playbackRate, 0, 210 'reverse() should preserve playbackRate if the playbackRate == 0'); 211 assert_equals(animation.currentTime, 50 * MS_PER_SEC, 212 'reverse() should not affect the currentTime if the playbackRate == 0'); 213 }, 'Reversing when when playbackRate == 0 should preserve the current ' + 214 'time and playback rate'); 215 216 test(t => { 217 const div = createDiv(t); 218 const animation = 219 new Animation(new KeyframeEffect(div, null, 100 * MS_PER_SEC)); 220 assert_equals(animation.currentTime, null); 221 222 animation.reverse(); 223 224 assert_equals(animation.currentTime, 100 * MS_PER_SEC, 225 'animation.currentTime should be at its effect end'); 226 }, 'Reversing an idle animation from starts playing the animation'); 227 228 test(t => { 229 const div = createDiv(t); 230 const animation = 231 new Animation(new KeyframeEffect(div, null, 100 * MS_PER_SEC), null); 232 233 assert_throws_dom('InvalidStateError', () => { animation.reverse(); }); 234 }, 'Reversing an animation without an active timeline throws an ' + 235 'InvalidStateError'); 236 237 promise_test(async t => { 238 const animation = createDiv(t).animate(null, 100 * MS_PER_SEC); 239 await animation.ready; 240 241 animation.updatePlaybackRate(2); 242 animation.reverse(); 243 244 await animation.ready; 245 assert_equals(animation.playbackRate, -2); 246 }, 'Reversing should use the negative pending playback rate'); 247 248 promise_test(async t => { 249 const animation = createDiv(t).animate(null, { 250 duration: 100 * MS_PER_SEC, 251 iterations: Infinity, 252 }); 253 animation.currentTime = -200 * MS_PER_SEC; 254 await animation.ready; 255 256 animation.updatePlaybackRate(2); 257 assert_throws_dom('InvalidStateError', () => { animation.reverse(); }); 258 assert_equals(animation.playbackRate, 1); 259 260 await animation.ready; 261 assert_equals(animation.playbackRate, 2); 262 }, 'When reversing fails, it should restore any previous pending playback' 263 + ' rate'); 264 265 </script> 266 </body>