Skip to content

Commit 4ad5b73

Browse files
authored
fix: further animation transition improvements (#10138)
* fix: further animation transition improvements * clever hack
1 parent 960fe30 commit 4ad5b73

File tree

2 files changed

+43
-1
lines changed

2 files changed

+43
-1
lines changed

.changeset/slow-beds-shave.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: further animation transition improvements

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

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -369,6 +369,8 @@ function create_transition(dom, init, direction, effect) {
369369
},
370370
// out
371371
o() {
372+
// @ts-ignore
373+
const has_keyed_transition = dom.__animate;
372374
const needs_reverse = direction === 'both' && curr_direction !== 'out';
373375
curr_direction = 'out';
374376
if (animation === null || cancelled) {
@@ -385,6 +387,35 @@ function create_transition(dom, init, direction, effect) {
385387
/** @type {Animation | TickAnimation} */ (animation).play();
386388
}
387389
}
390+
// If we're outroing an element that has an animation, then we need to fix
391+
// its position to ensure it behaves nicely without causing layout shift.
392+
if (has_keyed_transition) {
393+
const style = getComputedStyle(dom);
394+
const position = style.position;
395+
396+
if (position !== 'absolute' && position !== 'fixed') {
397+
const { width, height } = style;
398+
const a = dom.getBoundingClientRect();
399+
dom.style.position = 'absolute';
400+
401+
dom.style.width = width;
402+
dom.style.height = height;
403+
const b = dom.getBoundingClientRect();
404+
if (a.left !== b.left || a.top !== b.top) {
405+
// Previously, in the Svelte 4, we'd just apply the transform the the DOM element. However,
406+
// because we're now using Web Animations, we can't do that as it won't work properly if the
407+
// animation is also making use of the same transformations. So instead, we apply an instantaneous
408+
// animation and pause it on the first frame, just applying the same behavior.
409+
const style = getComputedStyle(dom);
410+
const transform = style.transform === 'none' ? '' : style.transform;
411+
const frame = {
412+
transform: `${transform} translate(${a.left - b.left}px, ${a.top - b.top}px)`
413+
};
414+
const animation = dom.animate([frame, frame], { duration: 1 });
415+
animation.pause();
416+
}
417+
}
418+
}
388419
},
389420
// cancel
390421
c() {
@@ -432,10 +463,16 @@ function is_transition_block(block) {
432463
export function bind_transition(dom, get_transition_fn, props_fn, direction, global) {
433464
const transition_effect = /** @type {import('./types.js').EffectSignal} */ (current_effect);
434465
const block = current_block;
466+
const is_keyed_transition = direction === 'key';
435467

436468
let can_show_intro_on_mount = true;
437469
let can_apply_lazy_transitions = false;
438470

471+
if (is_keyed_transition) {
472+
// @ts-ignore
473+
dom.__animate = true;
474+
}
475+
439476
/** @type {import('./types.js').Block | null} */
440477
let transition_block = block;
441478
while (transition_block !== null) {
@@ -479,7 +516,7 @@ export function bind_transition(dom, get_transition_fn, props_fn, direction, glo
479516
const init = (from) =>
480517
untrack(() => {
481518
const props = props_fn === null ? {} : props_fn();
482-
return direction === 'key'
519+
return is_keyed_transition
483520
? /** @type {import('./types.js').AnimateFn<any>} */ (transition_fn)(
484521
dom,
485522
{ from: /** @type {DOMRect} */ (from), to: dom.getBoundingClientRect() },

0 commit comments

Comments
 (0)