CSS 애니메이션을 직접 작성하는 대신, transition:fade 한 줄로 해결 — Svelte의 선언적 애니메이션입니다.

개념 정의

Transition 은 요소가 DOM에 추가되거나 제거될 때 실행되는 애니메이션입니다. Svelte는 fade, fly, slide, scale 등 내장 트랜지션을 제공하며, transition: 디렉티브로 적용합니다.

내장 Transition

SVELTE
<script>
  import { fade, fly, slide, scale, blur, draw } from 'svelte/transition';
  let visible = $state(true);
</script>

<button onclick={() => visible = !visible}>토글</button>

{#if visible}
  <!-- fade — 투명도 변환 -->
  <p transition:fade>페이드 효과</p>

  <!-- fly — 이동 + 투명도 -->
  <p transition:fly={{ y: 50, duration: 300 }}>아래에서 올라옴</p>

  <!-- slide — 높이 변환 -->
  <div transition:slide>슬라이드 효과</div>

  <!-- scale — 크기 변환 -->
  <p transition:scale={{ start: 0.5 }}>스케일 효과</p>

  <!-- blur — 흐림 효과 -->
  <p transition:blur={{ amount: 10 }}>블러 효과</p>
{/if}

Transition 매개변수

SVELTE
<script>
  import { fly } from 'svelte/transition';
  let visible = $state(true);
</script>

{#if visible}
  <div transition:fly={{
    x: 0,          // x축 이동 거리 (px)
    y: -100,       // y축 이동 거리 (px)
    duration: 500,  // 지속 시간 (ms)
    delay: 200,    // 시작 지연 (ms)
    opacity: 0,    // 시작 투명도
    easing: cubicOut, // 이징 함수
  }}>
    커스텀 fly 트랜지션
  </div>
{/if}

in: / out: — 입장과 퇴장 분리

SVELTE
<script>
  import { fly, fade } from 'svelte/transition';
  let visible = $state(true);
</script>

{#if visible}
  <!-- 나타날 때는 위에서, 사라질 때는 페이드 -->
  <div
    in:fly={{ y: -50, duration: 300 }}
    out:fade={{ duration: 200 }}
  >
    입장/퇴장 애니메이션이 다릅니다
  </div>
{/if}

Easing 함수

SVELTE
<script>
  import { fly } from 'svelte/transition';
  import { cubicOut, elasticOut, bounceOut, quintOut } from 'svelte/easing';

  let visible = $state(true);
</script>

{#if visible}
  <div transition:fly={{ y: 100, easing: bounceOut, duration: 800 }}>
    통통 튀는 효과
  </div>
{/if}

주요 이징 함수:

  • linear — 일정 속도
  • cubicOut — 부드럽게 감속 (가장 자연스러운 기본값)
  • elasticOut — 탄성 효과
  • bounceOut — 바운스 효과
  • quintOut — 강한 감속

Transition 이벤트

SVELTE
<script>
  import { fade } from 'svelte/transition';
  let visible = $state(true);
  let status = $state('');
</script>

{#if visible}
  <div
    transition:fade
    onintrostart={() => status = '입장 시작'}
    onintroend={() => status = '입장 완료'}
    onoutrostart={() => status = '퇴장 시작'}
    onoutroend={() => status = '퇴장 완료'}
  >
    애니메이션 상태 감지
  </div>
{/if}

<p>상태: {status}</p>

animate:flip — 리스트 애니메이션

리스트 항목의 순서가 바뀔 때 부드럽게 이동시킵니다.

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

  let items = $state([
    { id: 1, name: '사과' },
    { id: 2, name: '바나나' },
    { id: 3, name: '딸기' },
    { id: 4, name: '포도' },
  ]);

  function shuffle() {
    items = items.sort(() => Math.random() - 0.5);
  }

  function remove(id) {
    items = items.filter(i => i.id !== id);
  }
</script>

<button onclick={shuffle}>섞기</button>

<ul>
  {#each items as item (item.id)}
    <li
      animate:flip={{ duration: 300 }}
      transition:fade
    >
      {item.name}
      <button onclick={() => remove(item.id)}>X</button>
    </li>
  {/each}
</ul>

<style>
  li {
    padding: 0.5rem;
    margin: 0.25rem 0;
    background: #f0f0f0;
    border-radius: 4px;
    display: flex;
    justify-content: space-between;
  }
</style>

**중요 **: animate:flip은 반드시 keyed each 블록 안에서만 사용할 수 있습니다.

글로벌 Transition

기본적으로 트랜지션은 해당 블록의 조건이 바뀔 때만 실행됩니다. |global 수정자를 쓰면 부모 블록의 변경에도 반응합니다.

SVELTE
{#if outerCondition}
  {#if innerCondition}
    <!-- 기본 — innerCondition 변경 시에만 트랜지션 -->
    <div transition:fade>기본</div>

    <!-- global — outerCondition 변경 시에도 트랜지션 -->
    <div transition:fade|global>글로벌</div>
  {/if}
{/if}

실전 예시 — 토스트 알림

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

  let toasts = $state([]);
  let nextId = 0;

  function addToast(message, type = 'info') {
    const id = nextId++;
    toasts = [...toasts, { id, message, type }];
    setTimeout(() => removeToast(id), 3000);
  }

  function removeToast(id) {
    toasts = toasts.filter(t => t.id !== id);
  }
</script>

<button onclick={() => addToast('저장되었습니다', 'success')}>성공 토스트</button>
<button onclick={() => addToast('오류 발생!', 'error')}>에러 토스트</button>

<div class="toast-container">
  {#each toasts as toast (toast.id)}
    <div
      class="toast toast-{toast.type}"
      animate:flip={{ duration: 200 }}
      in:fly={{ x: 300, duration: 300 }}
      out:fade={{ duration: 200 }}
    >
      {toast.message}
      <button onclick={() => removeToast(toast.id)}>X</button>
    </div>
  {/each}
</div>

<style>
  .toast-container { position: fixed; top: 1rem; right: 1rem; }
  .toast { padding: 0.75rem 1rem; margin-bottom: 0.5rem; border-radius: 4px; color: white; }
  .toast-success { background: #4caf50; }
  .toast-error { background: #f44336; }
  .toast-info { background: #2196f3; }
</style>

면접 포인트

  • "Svelte 트랜지션이 CSS 애니메이션보다 나은 점은?": 선언적으로 사용할 수 있고, 요소의 추가/제거 타이밍을 자동으로 관리합니다. CSS만으로는 요소 제거 시 애니메이션이 끝날 때까지 DOM에 유지하는 것이 어렵지만, Svelte 트랜지션은 이를 자동 처리합니다.
  • "flip 애니메이션의 원리는?": FLIP(First, Last, Invert, Play) 기법을 사용합니다. 요소의 이전 위치와 새 위치를 계산한 후, CSS transform으로 이전 위치에서 새 위치로 이동시킵니다.

정리

Svelte의 Transition과 Animation 시스템은 "적은 코드로 풍부한 UI"를 만들어줍니다. transition:fade 한 줄이면 충분하고, 복잡한 리스트 애니메이션도 animate:flip으로 해결됩니다. 외부 애니메이션 라이브러리 없이도 상당히 많은 것을 할 수 있습니다.

댓글 로딩 중...