Skip to content

Commit b1cf2ec

Browse files
authored
fix: defer animations (#12453)
* run animations in microtask * skip troublesome test * fix test * changeset
1 parent 649a050 commit b1cf2ec

File tree

4 files changed

+66
-56
lines changed

4 files changed

+66
-56
lines changed

.changeset/yellow-bananas-rhyme.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'svelte': patch
3+
---
4+
5+
fix: run animations in microtask so that deferred transitions can measure nodes correctly

packages/svelte/src/internal/client/dom/elements/transitions.js

Lines changed: 46 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -303,13 +303,13 @@ function animate(element, options, counterpart, t2, callback) {
303303
};
304304
}
305305

306-
var { delay = 0, duration, css, tick, easing = linear } = options;
306+
const { delay = 0, css, tick, easing = linear } = options;
307307

308308
var start = raf.now() + delay;
309309
var t1 = counterpart?.t(start) ?? 1 - t2;
310310
var delta = t2 - t1;
311311

312-
duration *= Math.abs(delta);
312+
var duration = options.duration * Math.abs(delta);
313313
var end = start + duration;
314314

315315
/** @type {Animation} */
@@ -319,52 +319,54 @@ function animate(element, options, counterpart, t2, callback) {
319319
var task;
320320

321321
if (css) {
322-
// WAAPI
323-
var keyframes = [];
324-
var n = Math.ceil(duration / (1000 / 60)); // `n` must be an integer, or we risk missing the `t2` value
325-
326-
// In case of a delayed intro, apply the initial style for the duration of the delay;
327-
// else in case of a fade-in for example the element would be visible until the animation starts
328-
if (is_intro && delay > 0) {
329-
let m = Math.ceil(delay / (1000 / 60));
330-
let keyframe = css_to_keyframe(css(0, 1));
331-
for (let i = 0; i < m; i += 1) {
332-
keyframes.push(keyframe);
322+
queue_micro_task(() => {
323+
// WAAPI
324+
var keyframes = [];
325+
var n = Math.ceil(duration / (1000 / 60)); // `n` must be an integer, or we risk missing the `t2` value
326+
327+
// In case of a delayed intro, apply the initial style for the duration of the delay;
328+
// else in case of a fade-in for example the element would be visible until the animation starts
329+
if (is_intro && delay > 0) {
330+
let m = Math.ceil(delay / (1000 / 60));
331+
let keyframe = css_to_keyframe(css(0, 1));
332+
for (let i = 0; i < m; i += 1) {
333+
keyframes.push(keyframe);
334+
}
333335
}
334-
}
335-
336-
for (var i = 0; i <= n; i += 1) {
337-
var t = t1 + delta * easing(i / n);
338-
var styles = css(t, 1 - t);
339-
keyframes.push(css_to_keyframe(styles));
340-
}
341-
342-
animation = element.animate(keyframes, {
343-
delay: is_intro ? 0 : delay,
344-
duration: duration + (is_intro ? delay : 0),
345-
easing: 'linear',
346-
fill: 'forwards'
347-
});
348336

349-
animation.finished
350-
.then(() => {
351-
callback?.();
337+
for (var i = 0; i <= n; i += 1) {
338+
var t = t1 + delta * easing(i / n);
339+
var styles = css(t, 1 - t);
340+
keyframes.push(css_to_keyframe(styles));
341+
}
352342

353-
if (t2 === 1) {
354-
animation.cancel();
355-
}
356-
})
357-
.catch((e) => {
358-
// Error for DOMException: The user aborted a request. This results in two things:
359-
// - startTime is `null`
360-
// - currentTime is `null`
361-
// We can't use the existence of an AbortError as this error and error code is shared
362-
// with other Web APIs such as fetch().
363-
364-
if (animation.startTime !== null && animation.currentTime !== null) {
365-
throw e;
366-
}
343+
animation = element.animate(keyframes, {
344+
delay: is_intro ? 0 : delay,
345+
duration: duration + (is_intro ? delay : 0),
346+
easing: 'linear',
347+
fill: 'forwards'
367348
});
349+
350+
animation.finished
351+
.then(() => {
352+
callback?.();
353+
354+
if (t2 === 1) {
355+
animation.cancel();
356+
}
357+
})
358+
.catch((e) => {
359+
// Error for DOMException: The user aborted a request. This results in two things:
360+
// - startTime is `null`
361+
// - currentTime is `null`
362+
// We can't use the existence of an AbortError as this error and error code is shared
363+
// with other Web APIs such as fetch().
364+
365+
if (animation.startTime !== null && animation.currentTime !== null) {
366+
throw e;
367+
}
368+
});
369+
});
368370
} else {
369371
// Timer
370372
if (t1 === 0) {

packages/svelte/tests/runtime-legacy/samples/transition-css-in-out-in-with-param/_config.js

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,16 +6,19 @@ export default test({
66
const div = target.querySelector('div');
77
ok(div);
88

9-
assert.equal(div.style.opacity, '0');
9+
assert.equal(div.style.color, 'blue');
1010

1111
component.visible = false;
12-
assert.equal(div.style.opacity, '1');
12+
assert.equal(div.style.color, 'yellow');
1313

1414
// change param
1515
raf.tick(1);
1616
component.param = true;
1717
component.visible = true;
1818

19-
assert.equal(div.style.opacity, '1');
19+
assert.equal(div.style.color, 'red');
20+
21+
component.visible = false;
22+
assert.equal(div.style.color, 'green');
2023
}
2124
});

packages/svelte/tests/runtime-legacy/samples/transition-css-in-out-in-with-param/main.svelte

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,24 +3,24 @@
33
export let param = false;
44
55
function getInParam() {
6-
return {
7-
duration: param ? 20 : 10,
8-
css: t => {
9-
return `opacity: ${t}`;
6+
return {
7+
duration: 100,
8+
css: (t) => {
9+
return `color: ${param ? 'red' : 'blue'}`;
1010
}
1111
};
1212
}
1313
1414
function getOutParam() {
15-
return {
16-
duration: param ? 15 : 5,
17-
css: t => {
18-
return `opacity: ${t}`;
15+
return {
16+
duration: 100,
17+
css: (t) => {
18+
return `color: ${param ? 'green' : 'yellow'}`;
1919
}
2020
};
2121
}
2222
</script>
2323

2424
{#if visible}
2525
<div in:getInParam out:getOutParam></div>
26-
{/if}
26+
{/if}

0 commit comments

Comments
 (0)