animate: 动画
本教程共 50 篇 · 第 25 篇 · 更新于 2026-08-05 · 约 6 分钟阅读
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 支持三个参数:
| 参数 | 类型 | 说明 |
|---|---|---|
delay | number | 延迟开始时间(毫秒) |
duration | number 或函数 | 动画时长 |
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(包含 from 和 to 两个 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;
};
from 和 to 都是 DOMRect 对象,包含元素在重排前后的位置和尺寸信息。
| 属性 | 说明 |
|---|---|
left、top | 左上角坐标 |
right、bottom | 右下角坐标 |
width、height | 宽高 |
x、y | 等同于 left、top |
Note跟过渡一样,优先用
css而不是tick。css利用 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}块的直接子元素上flip的duration可以是函数,根据位移距离动态计算时长- 自定义动画函数接收
node、{ from, to }(DOMRect)和params - 动画可以与过渡组合使用,互不冲突