动画与过渡 #

一、过渡概述 #

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