Skip to content

Svelte 5: Multiple item crossfade is buggy #10252

Open
@brunnerh

Description

@brunnerh

Describe the bug

When multiple items change their position at the same time, crossfade can misbehave.
Items can transition to nowhere and transition in from locations where no items can ever be.
The issue happens with or without animate:flip, the reproduction example uses it to show how it ideally should look, as this works in Svelte 4.

A secondary issue would be what happens when multiple such transitions are triggered in quick succession.
In Svelte 4 the layout can break completely (elements settle in the wrong locations), in Svelte 5 the position resets to the correct target after the transition ends.

Reproduction

Code
<script>
	import { quintOut } from 'svelte/easing';
	import { crossfade } from 'svelte/transition';
	import { flip } from 'svelte/animate';

	const [send, receive] = crossfade({
		fallback(node) {
			const style = getComputedStyle(node);
			const transform = style.transform === 'none' ? '' : style.transform;

			return {
				duration: 600,
				easing: quintOut,
				css: (t) => `
					transform: ${transform} scale(${t});
					opacity: ${t}
				`
			};
		}
	});

	let items = [
		{ id: 1, team: 'a', description: 'Dianna' },
		{ id: 2, team: 'a', description: 'Oda' },
		{ id: 3, team: 'a', description: 'Anna' },
		{ id: 4, team: 'b', description: 'Keenan' },
		{ id: 5, team: 'b', description: 'Craig' },
		{ id: 6, team: 'b', description: 'Alyce' },
	];

	function shuffle() {
		items = items.map(item => ({ ...item, team: Math.random() > 0.5 ? 'a' : 'b' }));
	}
</script>

<button on:click={shuffle}>random</button>
<div class="board">
	<div class="left">
		<h2>Team A</h2>
		<div class="list">
			{#each items.filter((t) => t.team == 'a') as item (item.id)}
				<button in:receive={{ key: item.id }} out:send={{ key: item.id }} animate:flip
					on:click={() => item.team = item.team == 'a' ? 'b' : 'a'}>
					{item.description}
				</button>
			{/each}
		</div>
	</div>

	<div class="right">
		<h2>Team B</h2>
		<div class="list">
			{#each items.filter((t) => t.team == 'b') as item (item.id)}
				<button in:receive={{ key: item.id }} out:send={{ key: item.id }} animate:flip
					on:click={() => item.team = item.team == 'a' ? 'b' : 'a'}>
					{item.description}
				</button>
			{/each}
		</div>
	</div>
</div>

<style>
	.board {
		max-width: 36em;
		margin: 0 auto;
		display: grid;
		grid-template-columns: 1fr 1fr;
		gap: 32px;
	}

	h2 {
		font-size: 2em;
		font-weight: 200;
		user-select: none;
	}

	button {
		top: 0;
		left: 0;
		display: block;
		font-size: 1em;
		line-height: 1;
		padding: 0.5em;
		border: none;
		border-radius: 2px;
		user-select: none;
		color: black;
		text-align: left;
	}

	.list {
		display: flex;
		flex-direction: column;
		gap: 8px;
	}

	.left button {
		background-color: hsl(0, 100%, 83%);
	}

	.right button {
		background-color: hsl(231, 100%, 87%);
	}
</style>

Svelte 4
Svelte 5

Click "random" to cause multiple element transitions at once. If no issue is apparent, I can also provide recordings.

Click it quickly to cause transition overlap/interruptions, which should break permanently in Svelte 4 and temporarily in Svelte 5.

Logs

No response

System Info

REPL - Svelte v5.0.0-next.37
Tested in Chrome and Firefox
Windows 10

Severity

annoyance

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions