From 4ad5b73341b7bdd775865dd1ecf0c010860f3859 Mon Sep 17 00:00:00 2001 From: Dominic Gannaway Date: Tue, 9 Jan 2024 23:58:10 +0000 Subject: [PATCH] fix: further animation transition improvements (#10138) * fix: further animation transition improvements * clever hack --- .changeset/slow-beds-shave.md | 5 +++ .../svelte/src/internal/client/transitions.js | 39 ++++++++++++++++++- 2 files changed, 43 insertions(+), 1 deletion(-) create mode 100644 .changeset/slow-beds-shave.md diff --git a/.changeset/slow-beds-shave.md b/.changeset/slow-beds-shave.md new file mode 100644 index 0000000000..793c66b6a0 --- /dev/null +++ b/.changeset/slow-beds-shave.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: further animation transition improvements diff --git a/packages/svelte/src/internal/client/transitions.js b/packages/svelte/src/internal/client/transitions.js index 6ce0f2f403..6334c6310f 100644 --- a/packages/svelte/src/internal/client/transitions.js +++ b/packages/svelte/src/internal/client/transitions.js @@ -369,6 +369,8 @@ function create_transition(dom, init, direction, effect) { }, // out o() { + // @ts-ignore + const has_keyed_transition = dom.__animate; const needs_reverse = direction === 'both' && curr_direction !== 'out'; curr_direction = 'out'; if (animation === null || cancelled) { @@ -385,6 +387,35 @@ function create_transition(dom, init, direction, effect) { /** @type {Animation | TickAnimation} */ (animation).play(); } } + // If we're outroing an element that has an animation, then we need to fix + // its position to ensure it behaves nicely without causing layout shift. + if (has_keyed_transition) { + const style = getComputedStyle(dom); + const position = style.position; + + if (position !== 'absolute' && position !== 'fixed') { + const { width, height } = style; + const a = dom.getBoundingClientRect(); + dom.style.position = 'absolute'; + + dom.style.width = width; + dom.style.height = height; + const b = dom.getBoundingClientRect(); + if (a.left !== b.left || a.top !== b.top) { + // Previously, in the Svelte 4, we'd just apply the transform the the DOM element. However, + // because we're now using Web Animations, we can't do that as it won't work properly if the + // animation is also making use of the same transformations. So instead, we apply an instantaneous + // animation and pause it on the first frame, just applying the same behavior. + const style = getComputedStyle(dom); + const transform = style.transform === 'none' ? '' : style.transform; + const frame = { + transform: `${transform} translate(${a.left - b.left}px, ${a.top - b.top}px)` + }; + const animation = dom.animate([frame, frame], { duration: 1 }); + animation.pause(); + } + } + } }, // cancel c() { @@ -432,10 +463,16 @@ function is_transition_block(block) { export function bind_transition(dom, get_transition_fn, props_fn, direction, global) { const transition_effect = /** @type {import('./types.js').EffectSignal} */ (current_effect); const block = current_block; + const is_keyed_transition = direction === 'key'; let can_show_intro_on_mount = true; let can_apply_lazy_transitions = false; + if (is_keyed_transition) { + // @ts-ignore + dom.__animate = true; + } + /** @type {import('./types.js').Block | null} */ let transition_block = block; while (transition_block !== null) { @@ -479,7 +516,7 @@ export function bind_transition(dom, get_transition_fn, props_fn, direction, glo const init = (from) => untrack(() => { const props = props_fn === null ? {} : props_fn(); - return direction === 'key' + return is_keyed_transition ? /** @type {import('./types.js').AnimateFn} */ (transition_fn)( dom, { from: /** @type {DOMRect} */ (from), to: dom.getBoundingClientRect() },