From 674615effb8d5c927bd57eedf9c176a98c0daada Mon Sep 17 00:00:00 2001 From: Dominic Gannaway Date: Tue, 9 Jan 2024 22:18:54 +0000 Subject: [PATCH 1/2] fix: further animation transition improvements --- .changeset/slow-beds-shave.md | 5 +++ packages/svelte/src/internal/client/each.js | 6 +++- .../svelte/src/internal/client/transitions.js | 33 ++++++++++++++++++- 3 files changed, 42 insertions(+), 2 deletions(-) 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 000000000000..793c66b6a0f9 --- /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/each.js b/packages/svelte/src/internal/client/each.js index e66574fba0e7..4d8c2d9853c2 100644 --- a/packages/svelte/src/internal/client/each.js +++ b/packages/svelte/src/internal/client/each.js @@ -735,6 +735,7 @@ export function destroy_each_item_block( controlled = false ) { const transitions = block.s; + const dom = block.d; if (apply_transitions && transitions !== null) { // We might have pending key transitions, if so remove them first @@ -750,10 +751,13 @@ export function destroy_each_item_block( if (transition_block !== null) { transition_block.push(block); } + if (dom !== null) { + // @ts-ignore + dom.__animate = false; + } return; } } - const dom = block.d; if (!controlled && dom !== null) { remove(dom); } diff --git a/packages/svelte/src/internal/client/transitions.js b/packages/svelte/src/internal/client/transitions.js index 6ce0f2f40345..cfed39820c16 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 === true; const needs_reverse = direction === 'both' && curr_direction !== 'out'; curr_direction = 'out'; if (animation === null || cancelled) { @@ -385,6 +387,29 @@ 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) { + const style = getComputedStyle(dom); + const transform = style.transform === 'none' ? '' : style.transform; + dom.style.transform = `${transform} translate(${a.left - b.left}px, ${ + a.top - b.top + }px)`; + } + } + } }, // cancel c() { @@ -432,10 +457,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 +510,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() }, From 876a75b49726289e88888d12051ee74f7c5f1916 Mon Sep 17 00:00:00 2001 From: Dominic Gannaway Date: Tue, 9 Jan 2024 23:04:34 +0000 Subject: [PATCH 2/2] clever hack --- packages/svelte/src/internal/client/each.js | 6 +----- packages/svelte/src/internal/client/transitions.js | 14 ++++++++++---- 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/packages/svelte/src/internal/client/each.js b/packages/svelte/src/internal/client/each.js index 4d8c2d9853c2..e66574fba0e7 100644 --- a/packages/svelte/src/internal/client/each.js +++ b/packages/svelte/src/internal/client/each.js @@ -735,7 +735,6 @@ export function destroy_each_item_block( controlled = false ) { const transitions = block.s; - const dom = block.d; if (apply_transitions && transitions !== null) { // We might have pending key transitions, if so remove them first @@ -751,13 +750,10 @@ export function destroy_each_item_block( if (transition_block !== null) { transition_block.push(block); } - if (dom !== null) { - // @ts-ignore - dom.__animate = false; - } return; } } + const dom = block.d; if (!controlled && dom !== null) { remove(dom); } diff --git a/packages/svelte/src/internal/client/transitions.js b/packages/svelte/src/internal/client/transitions.js index cfed39820c16..6334c6310fc0 100644 --- a/packages/svelte/src/internal/client/transitions.js +++ b/packages/svelte/src/internal/client/transitions.js @@ -370,7 +370,7 @@ function create_transition(dom, init, direction, effect) { // out o() { // @ts-ignore - const has_keyed_transition = dom.__animate === true; + const has_keyed_transition = dom.__animate; const needs_reverse = direction === 'both' && curr_direction !== 'out'; curr_direction = 'out'; if (animation === null || cancelled) { @@ -402,11 +402,17 @@ function create_transition(dom, init, direction, effect) { 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; - dom.style.transform = `${transform} translate(${a.left - b.left}px, ${ - a.top - b.top - }px)`; + 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(); } } }