From 277370a79de464f60c7288890a262d16341a1d87 Mon Sep 17 00:00:00 2001 From: Simon H <5968653+dummdidumm@users.noreply.github.com> Date: Thu, 11 Jul 2024 15:39:56 +0200 Subject: [PATCH] fix: properly delay intro transitions (#12389) * fix: properly delay intro transitions WAAPI applies the styles of a delayed animation only when that animation starts. In the case of fade-in transitions that means the element is visible, then goes invisible and fades in. Fix that by never applying a delay on intro transitions, instead add keyframes of the initial state for the duration of the delay. Fixes #10876 * fix bug, make test pass * make test more selfcontained, test outro delay aswell and add functionality for that in animation-helpers * lint --------- Co-authored-by: Rich Harris --- .changeset/tricky-ears-shout.md | 5 ++ .../client/dom/elements/transitions.js | 18 +++++-- packages/svelte/tests/animation-helpers.js | 12 +++-- .../samples/transition-delayed/_config.js | 48 +++++++++++++++++++ .../samples/transition-delayed/main.svelte | 17 +++++++ 5 files changed, 93 insertions(+), 7 deletions(-) create mode 100644 .changeset/tricky-ears-shout.md create mode 100644 packages/svelte/tests/runtime-runes/samples/transition-delayed/_config.js create mode 100644 packages/svelte/tests/runtime-runes/samples/transition-delayed/main.svelte diff --git a/.changeset/tricky-ears-shout.md b/.changeset/tricky-ears-shout.md new file mode 100644 index 0000000000..b401ad279b --- /dev/null +++ b/.changeset/tricky-ears-shout.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: properly delay intro transitions diff --git a/packages/svelte/src/internal/client/dom/elements/transitions.js b/packages/svelte/src/internal/client/dom/elements/transitions.js index cc96692881..706d03084d 100644 --- a/packages/svelte/src/internal/client/dom/elements/transitions.js +++ b/packages/svelte/src/internal/client/dom/elements/transitions.js @@ -268,6 +268,8 @@ export function transition(flags, element, get_fn, get_params) { * @returns {import('#client').Animation} */ function animate(element, options, counterpart, t2, callback) { + var is_intro = t2 === 1; + if (is_function(options)) { // In the case of a deferred transition (such as `crossfade`), `option` will be // a function rather than an `AnimationConfig`. We need to call this function @@ -276,7 +278,7 @@ function animate(element, options, counterpart, t2, callback) { var a; queue_micro_task(() => { - var o = options({ direction: t2 === 1 ? 'in' : 'out' }); + var o = options({ direction: is_intro ? 'in' : 'out' }); a = animate(element, o, counterpart, t2, callback); }); @@ -322,6 +324,16 @@ function animate(element, options, counterpart, t2, callback) { 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 + if (is_intro && delay > 0) { + let m = Math.ceil(delay / (1000 / 60)); + let keyframe = css_to_keyframe(css(0, 1)); + 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); @@ -329,8 +341,8 @@ function animate(element, options, counterpart, t2, callback) { } animation = element.animate(keyframes, { - delay, - duration, + delay: is_intro ? 0 : delay, + duration: duration + (is_intro ? delay : 0), easing: 'linear', fill: 'forwards' }); diff --git a/packages/svelte/tests/animation-helpers.js b/packages/svelte/tests/animation-helpers.js index 44fb2c45ef..e7c27721ef 100644 --- a/packages/svelte/tests/animation-helpers.js +++ b/packages/svelte/tests/animation-helpers.js @@ -35,6 +35,7 @@ class Animation { #target; #keyframes; #duration; + #delay; #offset = raf.time; @@ -47,12 +48,13 @@ class Animation { /** * @param {HTMLElement} target * @param {Keyframe[]} keyframes - * @param {{ duration: number }} options // TODO add delay + * @param {{ duration: number, delay: number }} options */ - constructor(target, keyframes, { duration }) { + constructor(target, keyframes, { duration, delay }) { this.#target = target; this.#keyframes = keyframes; this.#duration = duration; + this.#delay = delay ?? 0; // Promise-like semantics, but call callbacks immediately on raf.tick this.finished = { @@ -73,7 +75,9 @@ class Animation { } _update() { - this.currentTime = raf.time - this.#offset; + this.currentTime = raf.time - this.#offset - this.#delay; + if (this.currentTime < 0) return; + const target_frame = this.currentTime / this.#duration; this.#apply_keyframe(target_frame); @@ -168,7 +172,7 @@ function interpolate(a, b, p) { /** * @param {Keyframe[]} keyframes - * @param {{duration: number}} options + * @param {{duration: number, delay: number}} options * @returns {globalThis.Animation} */ HTMLElement.prototype.animate = function (keyframes, options) { diff --git a/packages/svelte/tests/runtime-runes/samples/transition-delayed/_config.js b/packages/svelte/tests/runtime-runes/samples/transition-delayed/_config.js new file mode 100644 index 0000000000..2d0d426824 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/transition-delayed/_config.js @@ -0,0 +1,48 @@ +import { flushSync } from '../../../../src/index-client.js'; +import { test } from '../../test'; + +export default test({ + test({ assert, raf, target }) { + const btn = target.querySelector('button'); + + // in + btn?.click(); + flushSync(); + assert.htmlEqual( + target.innerHTML, + '

delayed fade

' + ); + raf.tick(1); + assert.htmlEqual( + target.innerHTML, + '

delayed fade

' + ); + + raf.tick(99); + assert.htmlEqual( + target.innerHTML, + '

delayed fade

' + ); + + raf.tick(150); + assert.htmlEqual( + target.innerHTML, + '

delayed fade

' + ); + + raf.tick(200); + assert.htmlEqual(target.innerHTML, '

delayed fade

'); + + // out + btn?.click(); + flushSync(); + raf.tick(275); + assert.htmlEqual(target.innerHTML, '

delayed fade

'); + + raf.tick(350); + assert.htmlEqual( + target.innerHTML, + '

delayed fade

' + ); + } +}); diff --git a/packages/svelte/tests/runtime-runes/samples/transition-delayed/main.svelte b/packages/svelte/tests/runtime-runes/samples/transition-delayed/main.svelte new file mode 100644 index 0000000000..509c81e259 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/transition-delayed/main.svelte @@ -0,0 +1,17 @@ + + + + +{#if visible} +

delayed fade

+{/if}