fix: defer animations (#12453)

* run animations in microtask

* skip troublesome test

* fix test

* changeset
pull/12460/head
Rich Harris 4 months ago committed by GitHub
parent 649a0507f7
commit b1cf2ece63
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -0,0 +1,5 @@
---
'svelte': patch
---
fix: run animations in microtask so that deferred transitions can measure nodes correctly

@ -303,13 +303,13 @@ function animate(element, options, counterpart, t2, callback) {
}; };
} }
var { delay = 0, duration, css, tick, easing = linear } = options; const { delay = 0, css, tick, easing = linear } = options;
var start = raf.now() + delay; var start = raf.now() + delay;
var t1 = counterpart?.t(start) ?? 1 - t2; var t1 = counterpart?.t(start) ?? 1 - t2;
var delta = t2 - t1; var delta = t2 - t1;
duration *= Math.abs(delta); var duration = options.duration * Math.abs(delta);
var end = start + duration; var end = start + duration;
/** @type {Animation} */ /** @type {Animation} */
@ -319,52 +319,54 @@ function animate(element, options, counterpart, t2, callback) {
var task; var task;
if (css) { if (css) {
// WAAPI queue_micro_task(() => {
var keyframes = []; // WAAPI
var n = Math.ceil(duration / (1000 / 60)); // `n` must be an integer, or we risk missing the `t2` value var keyframes = [];
var n = Math.ceil(duration / (1000 / 60)); // `n` must be an integer, or we risk missing the `t2` value
// In case of a delayed intro, apply the initial style for the duration of the delay;
// else in case of a fade-in for example the element would be visible until the animation starts // In case of a delayed intro, apply the initial style for the duration of the delay;
if (is_intro && delay > 0) { // else in case of a fade-in for example the element would be visible until the animation starts
let m = Math.ceil(delay / (1000 / 60)); if (is_intro && delay > 0) {
let keyframe = css_to_keyframe(css(0, 1)); let m = Math.ceil(delay / (1000 / 60));
for (let i = 0; i < m; i += 1) { let keyframe = css_to_keyframe(css(0, 1));
keyframes.push(keyframe); for (let i = 0; i < m; i += 1) {
keyframes.push(keyframe);
}
} }
}
for (var i = 0; i <= n; i += 1) {
var t = t1 + delta * easing(i / n);
var styles = css(t, 1 - t);
keyframes.push(css_to_keyframe(styles));
}
animation = element.animate(keyframes, {
delay: is_intro ? 0 : delay,
duration: duration + (is_intro ? delay : 0),
easing: 'linear',
fill: 'forwards'
});
animation.finished for (var i = 0; i <= n; i += 1) {
.then(() => { var t = t1 + delta * easing(i / n);
callback?.(); var styles = css(t, 1 - t);
keyframes.push(css_to_keyframe(styles));
}
if (t2 === 1) { animation = element.animate(keyframes, {
animation.cancel(); delay: is_intro ? 0 : delay,
} duration: duration + (is_intro ? delay : 0),
}) easing: 'linear',
.catch((e) => { fill: 'forwards'
// Error for DOMException: The user aborted a request. This results in two things:
// - startTime is `null`
// - currentTime is `null`
// We can't use the existence of an AbortError as this error and error code is shared
// with other Web APIs such as fetch().
if (animation.startTime !== null && animation.currentTime !== null) {
throw e;
}
}); });
animation.finished
.then(() => {
callback?.();
if (t2 === 1) {
animation.cancel();
}
})
.catch((e) => {
// Error for DOMException: The user aborted a request. This results in two things:
// - startTime is `null`
// - currentTime is `null`
// We can't use the existence of an AbortError as this error and error code is shared
// with other Web APIs such as fetch().
if (animation.startTime !== null && animation.currentTime !== null) {
throw e;
}
});
});
} else { } else {
// Timer // Timer
if (t1 === 0) { if (t1 === 0) {

@ -6,16 +6,19 @@ export default test({
const div = target.querySelector('div'); const div = target.querySelector('div');
ok(div); ok(div);
assert.equal(div.style.opacity, '0'); assert.equal(div.style.color, 'blue');
component.visible = false; component.visible = false;
assert.equal(div.style.opacity, '1'); assert.equal(div.style.color, 'yellow');
// change param // change param
raf.tick(1); raf.tick(1);
component.param = true; component.param = true;
component.visible = true; component.visible = true;
assert.equal(div.style.opacity, '1'); assert.equal(div.style.color, 'red');
component.visible = false;
assert.equal(div.style.color, 'green');
} }
}); });

@ -4,18 +4,18 @@
function getInParam() { function getInParam() {
return { return {
duration: param ? 20 : 10, duration: 100,
css: t => { css: (t) => {
return `opacity: ${t}`; return `color: ${param ? 'red' : 'blue'}`;
} }
}; };
} }
function getOutParam() { function getOutParam() {
return { return {
duration: param ? 15 : 5, duration: 100,
css: t => { css: (t) => {
return `opacity: ${t}`; return `color: ${param ? 'green' : 'yellow'}`;
} }
}; };
} }

Loading…
Cancel
Save