动画与过渡 #
一、过渡概述 #
Svelte 内置了强大的过渡和动画系统,可以轻松添加进入/离开动画和元素移动动画。
text
┌─────────────────────────────────────────────────────────────┐
│ Svelte 动画系统 │
├─────────────────────────────────────────────────────────────┤
│ │
│ transition: 进入和离开动画 │
│ ├── fade 淡入淡出 │
│ ├── fly 飞入飞出 │
│ ├── slide 滑动 │
│ ├── scale 缩放 │
│ ├── draw SVG 描边 │
│ └── blur 模糊 │
│ │
│ in: 仅进入动画 │
│ out: 仅离开动画 │
│ │
│ animate: 元素移动动画(FLIP) │
│ │
└─────────────────────────────────────────────────────────────┘
二、基本过渡 #
2.1 transition 指令 #
svelte
<script>
import { fade } from 'svelte/transition';
let visible = true;
</script>
<button onclick={() => visible = !visible}>
Toggle
</button>
{#if visible}
<div transition:fade>
This fades in and out
</div>
{/if}
2.2 内置过渡效果 #
svelte
<script>
import {
fade,
fly,
slide,
scale,
blur,
draw
} from 'svelte/transition';
let visible = true;
</script>
<button onclick={() => visible = !visible}>Toggle</button>
{#if visible}
<div transition:fade>Fade</div>
<div transition:fly={{ y: 50 }}>Fly</div>
<div transition:slide>Slide</div>
<div transition:scale>Scale</div>
<div transition:blur>Blur</div>
{/if}
2.3 过渡参数 #
svelte
<script>
import { fade, fly, slide } from 'svelte/transition';
</script>
<div transition:fade={{ duration: 500 }}>
Fade with duration
</div>
<div transition:fly={{ y: 100, duration: 1000, delay: 200 }}>
Fly with options
</div>
<div transition:slide={{ duration: 300, easing: elasticOut }}>
Slide with easing
</div>
三、进入和离开动画 #
3.1 分离进入和离开 #
svelte
<script>
import { fade, fly } from 'svelte/transition';
let visible = true;
</script>
{#if visible}
<div
in:fly={{ y: 50, duration: 300 }}
out:fade={{ duration: 500 }}
>
Different in/out animations
</div>
{/if}
3.2 交错动画 #
svelte
<script>
import { fade } from 'svelte/transition';
let items = ['a', 'b', 'c', 'd', 'e'];
let visible = true;
</script>
<button onclick={() => visible = !visible}>Toggle</button>
{#if visible}
{#each items as item, i}
<div in:fade={{ delay: i * 100, duration: 300 }}>
{item}
</div>
{/each}
{/if}
3.3 自定义过渡函数 #
svelte
<script>
function typewriter(node, { speed = 50 }) {
const text = node.textContent;
const duration = text.length * speed;
return {
duration,
tick: (t) => {
const i = Math.floor(text.length * t);
node.textContent = text.slice(0, i);
}
};
}
let visible = true;
</script>
<button onclick={() => visible = !visible}>Toggle</button>
{#if visible}
<p transition:typewriter={{ speed: 30 }}>
This text types itself!
</p>
{/if}
四、FLIP 动画 #
4.1 animate 指令 #
svelte
<script>
import { flip } from 'svelte/animate';
import { fade } from 'svelte/transition';
let items = [1, 2, 3, 4, 5];
function shuffle() {
items = items.sort(() => Math.random() - 0.5);
}
</script>
<button onclick={shuffle}>Shuffle</button>
{#each items as item (item)}
<div animate:flip transition:fade>
{item}
</div>
{/each}
4.2 FLIP 参数 #
svelte
<script>
import { flip } from 'svelte/animate';
let items = [1, 2, 3, 4, 5];
function shuffle() {
items = items.sort(() => Math.random() - 0.5);
}
</script>
{#each items as item (item)}
<div animate:flip={{ duration: 300, easing: elasticOut }}>
{item}
</div>
{/each}
4.3 列表动画示例 #
svelte
<script>
import { flip } from 'svelte/animate';
import { fade, fly } from 'svelte/transition';
let items = $state([
{ id: 1, text: 'Item 1' },
{ id: 2, text: 'Item 2' },
{ id: 3, text: 'Item 3' }
]);
function addItem() {
const id = Math.max(...items.map(i => i.id), 0) + 1;
items = [...items, { id, text: `Item ${id}` }];
}
function removeItem(id) {
items = items.filter(i => i.id !== id);
}
function shuffle() {
items = items.sort(() => Math.random() - 0.5);
}
</script>
<button onclick={addItem}>Add</button>
<button onclick={shuffle}>Shuffle</button>
<ul>
{#each items as item (item.id)}
<li animate:flip transition:fade>
{item.text}
<button onclick={() => removeItem(item.id)}>×</button>
</li>
{/each}
</ul>
五、过渡事件 #
5.1 监听过渡事件 #
svelte
<script>
import { fly } from 'svelte/transition';
let visible = false;
function handleIntroStart() {
console.log('Intro started');
}
function handleIntroEnd() {
console.log('Intro ended');
}
function handleOutroStart() {
console.log('Outro started');
}
function handleOutroEnd() {
console.log('Outro ended');
}
</script>
<button onclick={() => visible = !visible}>Toggle</button>
{#if visible}
<div
transition:fly
on:introstart={handleIntroStart}
on:introend={handleIntroEnd}
on:outrostart={handleOutroStart}
on:outroend={handleOutroEnd}
>
Animated element
</div>
{/if}
5.2 过渡完成后的操作 #
svelte
<script>
import { fade } from 'svelte/transition';
let visible = false;
let actionComplete = false;
function handleOutroEnd() {
actionComplete = true;
console.log('Element removed, action complete');
}
</script>
<button onclick={() => visible = !visible}>Toggle</button>
{#if visible}
<div
transition:fade
on:outroend={handleOutroEnd}
>
Content
</div>
{/if}
六、缓动函数 #
6.1 内置缓动 #
svelte
<script>
import { fly } from 'svelte/transition';
import {
linear,
easeIn,
easeOut,
easeInOut,
sineIn,
sineOut,
sineInOut,
quadIn,
quadOut,
quadInOut,
cubicIn,
cubicOut,
cubicInOut,
elasticIn,
elasticOut,
elasticInOut,
backIn,
backOut,
backInOut,
bounceIn,
bounceOut,
bounceInOut
} from 'svelte/easing';
</script>
<div transition:fly={{ y: 50, easing: linear }}>Linear</div>
<div transition:fly={{ y: 50, easing: easeOut }}>Ease Out</div>
<div transition:fly={{ y: 50, easing: elasticOut }}>Elastic Out</div>
<div transition:fly={{ y: 50, easing: bounceOut }}>Bounce Out</div>
6.2 自定义缓动 #
svelte
<script>
function customEasing(t) {
return t < 0.5
? 4 * t * t * t
: 1 - Math.pow(-2 * t + 2, 3) / 2;
}
</script>
<div transition:fly={{ y: 50, easing: customEasing }}>
Custom easing
</div>
七、SVG 动画 #
7.1 draw 过渡 #
svelte
<script>
import { draw } from 'svelte/transition';
let visible = true;
</script>
<button onclick={() => visible = !visible}>Toggle</button>
{#if visible}
<svg width="200" height="200">
<path
d="M 10 10 L 190 10 L 190 190 L 10 190 Z"
stroke="red"
stroke-width="3"
fill="none"
transition:draw={{ duration: 2000 }}
/>
</svg>
{/if}
7.2 路径动画 #
svelte
<script>
import { draw } from 'svelte/transition';
let visible = true;
</script>
<button onclick={() => visible = !visible}>Toggle</button>
{#if visible}
<svg width="200" height="200" viewBox="0 0 200 200">
<circle
cx="100"
cy="100"
r="80"
stroke="#ff3e00"
stroke-width="4"
fill="none"
transition:draw={{ duration: 1000 }}
/>
</svg>
{/if}
八、实际应用示例 #
8.1 通知组件 #
svelte
<script>
import { fly, fade } from 'svelte/transition';
import { flip } from 'svelte/animate';
let notifications = $state([]);
function addNotification(type = 'info', message = '') {
const id = Date.now();
notifications = [...notifications, { id, type, message }];
setTimeout(() => {
removeNotification(id);
}, 3000);
}
function removeNotification(id) {
notifications = notifications.filter(n => n.id !== id);
}
</script>
<div class="notifications">
{#each notifications as notification (notification.id)}
<div
class="notification {notification.type}"
animate:flip
in:fly={{ x: 200, duration: 300 }}
out:fade={{ duration: 200 }}
>
<span>{notification.message}</span>
<button onclick={() => removeNotification(notification.id)}>×</button>
</div>
{/each}
</div>
<button onclick={() => addNotification('info', 'Info message')}>
Add Info
</button>
<button onclick={() => addNotification('success', 'Success!')}>
Add Success
</button>
<button onclick={() => addNotification('error', 'Error occurred')}>
Add Error
</button>
<style>
.notifications {
position: fixed;
top: 1rem;
right: 1rem;
width: 300px;
}
.notification {
padding: 1rem;
margin-bottom: 0.5rem;
border-radius: 4px;
display: flex;
justify-content: space-between;
align-items: center;
}
.info { background: #e3f2fd; color: #1976d2; }
.success { background: #e8f5e9; color: #388e3c; }
.error { background: #ffebee; color: #d32f2f; }
</style>
8.2 模态框动画 #
svelte
<script>
import { fade, fly } from 'svelte/transition';
let { isOpen = false, onClose } = $props();
</script>
{#if isOpen}
<div
class="overlay"
transition:fade={{ duration: 200 }}
onclick={onClose}
>
<div
class="modal"
transition:fly={{ y: -50, duration: 300 }}
onclick={(e) => e.stopPropagation()}
>
<slot />
</div>
</div>
{/if}
<style>
.overlay {
position: fixed;
inset: 0;
background: rgba(0, 0, 0, 0.5);
display: flex;
align-items: center;
justify-content: center;
}
.modal {
background: white;
border-radius: 8px;
padding: 2rem;
max-width: 500px;
width: 100%;
}
</style>
8.3 图片画廊 #
svelte
<script>
import { fade, fly, scale } from 'svelte/transition';
import { flip } from 'svelte/animate';
let images = [
{ id: 1, src: 'image1.jpg', title: 'Image 1' },
{ id: 2, src: 'image2.jpg', title: 'Image 2' },
{ id: 3, src: 'image3.jpg', title: 'Image 3' }
];
let selectedImage = $state(null);
function selectImage(image) {
selectedImage = image;
}
function closeModal() {
selectedImage = null;
}
</script>
<div class="gallery">
{#each images as image (image.id)}
<div
class="thumbnail"
animate:flip
onclick={() => selectImage(image)}
>
<img src={image.src} alt={image.title} />
</div>
{/each}
</div>
{#if selectedImage}
<div
class="lightbox"
transition:fade
onclick={closeModal}
>
<img
src={selectedImage.src}
alt={selectedImage.title}
in:scale={{ start: 0.5 }}
out:fade={{ duration: 100 }}
/>
</div>
{/if}
九、总结 #
| 指令 | 说明 |
|---|---|
transition: |
进入和离开动画 |
in: |
仅进入动画 |
out: |
仅离开动画 |
animate: |
元素移动动画 |
内置过渡:
| 过渡 | 说明 |
|---|---|
fade |
淡入淡出 |
fly |
飞入飞出 |
slide |
滑动 |
scale |
缩放 |
blur |
模糊 |
draw |
SVG 描边 |
动画要点:
- 使用
transition:添加过渡效果 in:和out:可以分别设置animate:flip实现列表动画- 使用缓动函数控制动画曲线
- 监听过渡事件处理完成逻辑
最后更新:2026-03-28