首页 / Svelte 5 入门教程 / animate: 动画

Svelte 5 入门教程

animate: 动画

本教程共 50 篇 · 第 25 篇 · 更新于 2026-08-05 · 约 6 分钟阅读

SvelteSvelte 5animate动画flip列表动画

25. animate: 动画

本节目标:学会用 animate: 指令为列表重排添加动画效果,掌握内置 flip 动画函数,理解动画与过渡的区别,能够编写自定义动画函数。

动画与过渡的区别

先搞清楚一个概念:过渡(transition)处理的是元素进入和离开 DOM 的动画。动画(animate)处理的是元素在列表中重新排列时的位移动画。

打个比方,过渡是演员上台下台的效果,动画是演员在舞台上换位置时的走位。

对比项transition:animate:
触发时机元素进入/离开 DOM列表项重排序
是否需要 key不需要必须有 key
添加/删除元素有动画无动画
位置变化无动画有动画

animate:flip

flip 是 Svelte 内置的动画函数,名字来自 FLIP 技术(First, Last, Invert, Play)。它计算元素重排前后的位置差,然后用动画过渡过去。

<script>
	import { flip } from 'svelte/animate';

	let items = $state([
		{ id: 1, text: '苹果' },
		{ id: 2, text: '香蕉' },
		{ id: 3, text: '橙子' }
	]);

	function shuffle() {
		items = [...items].sort(() => Math.random() - 0.5);
	}
</script>

<button onclick={shuffle}>打乱顺序</button>

<!-- 注意:必须使用 keyed each 块 -->
{#each items as item (item.id)}
	<li animate:flip style="padding: 8px; background: #f0f0f0; margin: 4px 0;">
		{item.text}
	</li>
{/each}
Note

animate: 只能用在带 key 的 {#each} 块的直接子元素上。没有 key,Svelte 无法追踪元素身份,也就无法做位移动画。

flip 的参数

flip 支持三个参数:

参数类型说明
delaynumber延迟开始时间(毫秒)
durationnumber 或函数动画时长
easing(t) => number缓动函数
{#each items as item (item.id)}
	<li animate:flip={{ delay: 200, duration: 400 }}>
		{item.text}
	</li>
{/each}

duration 可以是一个函数,接收位移距离作为参数,返回动画时长。这样可以让短距离移动更快,长距离移动更慢,看起来更自然。

{#each items as item (item.id)}
	<li
		animate:flip={{
			duration: (distance) => Math.sqrt(Math.abs(distance)) * 200
		}}
	>
		{item.text}
	</li>
{/each}

实战:待办列表拖拽排序

flip 最常见的场景是列表排序动画。来做一个简单的上移下移功能。

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

	let todos = $state([
		{ id: 1, text: '学 Svelte' },
		{ id: 2, text: '写项目' },
		{ id: 3, text: '部署上线' },
		{ id: 4, text: '分享经验' }
	]);

	function moveUp(index) {
		if (index === 0) return;
		[todos[index - 1], todos[index]] = [todos[index], todos[index - 1]];
		todos = [...todos];
	}

	function moveDown(index) {
		if (index === todos.length - 1) return;
		[todos[index + 1], todos[index]] = [todos[index], todos[index + 1]];
		todos = [...todos];
	}
</script>

{#each todos as todo, i (todo.id)}
	<div
		animate:flip={{ duration: 300, easing: quintOut }}
		style="display: flex; gap: 8px; padding: 8px; background: #f9f9f9; margin: 4px 0; border-radius: 4px;"
	>
		<span style="flex: 1;">{todo.text}</span>
		<button onclick={() => moveUp(i)}>上移</button>
		<button onclick={() => moveDown(i)}>下移</button>
	</div>
{/each}
Tip

交换数组元素时,记得创建新数组(todos = [...todos])来触发响应式更新。直接修改原数组不会触发 Svelte 的重渲染。

自定义动画函数

内置的 flip 不够用?你可以自己写。

动画函数接收三个参数:node(DOM 元素)、states(包含 fromto 两个 DOMRect)和 params(参数对象)。

<script>
	import { cubicOut } from 'svelte/easing';

	function whizz(node, { from, to }, params) {
		const dx = from.left - to.left;
		const dy = from.top - to.top;
		const distance = Math.sqrt(dx * dx + dy * dy);

		return {
			delay: 0,
			duration: Math.sqrt(distance) * 120,
			easing: cubicOut,
			css: (t, u) =>
				`transform: translate(${u * dx}px, ${u * dy}px) rotate(${t * 360}deg);`
		};
	}

	let items = $state([1, 2, 3, 4, 5]);

	function shuffle() {
		items = [...items].sort(() => Math.random() - 0.5);
	}
</script>

<button onclick={shuffle}>打乱</button>

{#each items as item (item)}
	<div
		animate:whizz
		style="display: inline-block; padding: 12px; margin: 4px; background: #6c5ce7; color: white; border-radius: 4px;"
	>
		{item}
	</div>
{/each}

这个 whizz 动画不仅会移动元素,还会同时旋转 360 度。

动画函数签名

type Animation = (
	node: HTMLElement,
	{ from: DOMRect, to: DOMRect },
	params: any
) => {
	delay?: number;
	duration?: number;
	easing?: (t: number) => number;
	css?: (t: number, u: number) => string;
	tick?: (t: number, u: number) => void;
};

fromto 都是 DOMRect 对象,包含元素在重排前后的位置和尺寸信息。

属性说明
lefttop左上角坐标
rightbottom右下角坐标
widthheight宽高
xy等同于 lefttop
Note

跟过渡一样,优先用 css 而不是 tickcss 利用 Web Animations API 在主线程外运行,性能更好。

动画与过渡组合

你可以同时给列表项添加过渡和动画。元素添加时播放过渡,重排时播放动画。

<script>
	import { flip } from 'svelte/animate';
	import { fade } from 'svelte/transition';

	let items = $state([1, 2, 3]);

	function add() {
		items = [...items, items.length + 1];
	}

	function remove() {
		items = items.slice(0, -1);
	}

	function shuffle() {
		items = [...items].sort(() => Math.random() - 0.5);
	}
</script>

<button onclick={add}>添加</button>
<button onclick={remove}>删除</button>
<button onclick={shuffle}>打乱</button>

{#each items as item (item)}
	<div
		animate:flip={{ duration: 300 }}
		in:fade={{ duration: 300 }}
		out:fade={{ duration: 300 }}
		style="display: inline-block; padding: 12px; margin: 4px; background: #74b9ff; border-radius: 4px;"
	>
		{item}
	</div>
{/each}

添加元素时淡入,删除时淡出,重排时用 flip 平滑移动。三种效果互不冲突。

本节回顾

  • animate: 处理列表重排时的位移动画,与过渡(进入/离开动画)是不同的概念
  • 内置 flip 函数基于 FLIP 技术,自动计算位置差并动画过渡
  • animate: 必须用在带 key 的 {#each} 块的直接子元素上
  • flipduration 可以是函数,根据位移距离动态计算时长
  • 自定义动画函数接收 node{ from, to }(DOMRect)和 params
  • 动画可以与过渡组合使用,互不冲突