From 03c6408de4f96e5d9c764d168f78820d81da57fa Mon Sep 17 00:00:00 2001 From: raythurnvoid <53383860+raythurnvoid@users.noreply.github.com> Date: Thu, 29 May 2025 14:48:30 +0100 Subject: [PATCH 1/3] fix: transitions might render animated elements without animation styles applied for the duration of some rendering frames when they starts --- .../src/internal/client/dom/elements/transitions.js | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/packages/svelte/src/internal/client/dom/elements/transitions.js b/packages/svelte/src/internal/client/dom/elements/transitions.js index cc895cbccbcc..88807f5f1540 100644 --- a/packages/svelte/src/internal/client/dom/elements/transitions.js +++ b/packages/svelte/src/internal/client/dom/elements/transitions.js @@ -381,9 +381,17 @@ function animate(element, options, counterpart, t2, on_finish) { // create a dummy animation that lasts as long as the delay (but with whatever devtools // multiplier is in effect). in the common case that it is `0`, we keep it anyway so that // the CSS keyframes aren't created until the DOM is updated - var animation = element.animate(keyframes, { duration: delay }); + // + // fill forwards to prevent the element to render without animation styles applied + // when entering the onfinish callback, can be verified by adding a breakpoint in the onfinish callback + // see https://github.com/sveltejs/svelte/issues/14732 + var animation = element.animate(keyframes, { duration: delay, fill: 'forwards' }); animation.onfinish = () => { + // have to manually cancel the dummy animation to remove it from the animations stack + // because it fill forwards + animation.cancel(); + // for bidirectional transitions, we start from the current position, // rather than doing a full intro/outro var t1 = counterpart?.t() ?? 1 - t2; From 2188cd083b51ce2f5c15fe3b97b342d109d31786 Mon Sep 17 00:00:00 2001 From: raythurnvoid <53383860+raythurnvoid@users.noreply.github.com> Date: Thu, 29 May 2025 17:04:48 +0100 Subject: [PATCH 2/3] add changeset --- .changeset/tiny-poems-scream.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/tiny-poems-scream.md diff --git a/.changeset/tiny-poems-scream.md b/.changeset/tiny-poems-scream.md new file mode 100644 index 000000000000..a81cea5d5178 --- /dev/null +++ b/.changeset/tiny-poems-scream.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: transitions might render animated elements without animation styles applied for the duration of some rendering frames when they start From 9dc4c2cf639574e4d559cdccec0a1cb77d592847 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Thu, 29 May 2025 15:03:34 -0400 Subject: [PATCH 3/3] Apply suggestions from code review --- .changeset/tiny-poems-scream.md | 2 +- .../svelte/src/internal/client/dom/elements/transitions.js | 6 ++---- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/.changeset/tiny-poems-scream.md b/.changeset/tiny-poems-scream.md index a81cea5d5178..813972867a0e 100644 --- a/.changeset/tiny-poems-scream.md +++ b/.changeset/tiny-poems-scream.md @@ -2,4 +2,4 @@ 'svelte': patch --- -fix: transitions might render animated elements without animation styles applied for the duration of some rendering frames when they start +fix: use `fill: 'forwards'` on transition animations to prevent flicker diff --git a/packages/svelte/src/internal/client/dom/elements/transitions.js b/packages/svelte/src/internal/client/dom/elements/transitions.js index 88807f5f1540..38100e982cce 100644 --- a/packages/svelte/src/internal/client/dom/elements/transitions.js +++ b/packages/svelte/src/internal/client/dom/elements/transitions.js @@ -382,14 +382,12 @@ function animate(element, options, counterpart, t2, on_finish) { // multiplier is in effect). in the common case that it is `0`, we keep it anyway so that // the CSS keyframes aren't created until the DOM is updated // - // fill forwards to prevent the element to render without animation styles applied - // when entering the onfinish callback, can be verified by adding a breakpoint in the onfinish callback + // fill forwards to prevent the element from rendering without styles applied // see https://github.com/sveltejs/svelte/issues/14732 var animation = element.animate(keyframes, { duration: delay, fill: 'forwards' }); animation.onfinish = () => { - // have to manually cancel the dummy animation to remove it from the animations stack - // because it fill forwards + // remove dummy animation from the stack to prevent conflict with main animation animation.cancel(); // for bidirectional transitions, we start from the current position,