Transition과 Animation — 선언적으로 움직이는 UI 만들기
CSS 애니메이션을 직접 작성하는 대신,
transition:fade한 줄로 해결 — Svelte의 선언적 애니메이션입니다.
개념 정의
Transition 은 요소가 DOM에 추가되거나 제거될 때 실행되는 애니메이션입니다. Svelte는 fade, fly, slide, scale 등 내장 트랜지션을 제공하며, transition: 디렉티브로 적용합니다.
내장 Transition
<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 매개변수
<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: — 입장과 퇴장 분리
<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 함수
<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 이벤트
<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 — 리스트 애니메이션
리스트 항목의 순서가 바뀔 때 부드럽게 이동시킵니다.
<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 수정자를 쓰면 부모 블록의 변경에도 반응합니다.
{#if outerCondition}
{#if innerCondition}
<!-- 기본 — innerCondition 변경 시에만 트랜지션 -->
<div transition:fade>기본</div>
<!-- global — outerCondition 변경 시에도 트랜지션 -->
<div transition:fade|global>글로벌</div>
{/if}
{/if}
실전 예시 — 토스트 알림
<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으로 해결됩니다. 외부 애니메이션 라이브러리 없이도 상당히 많은 것을 할 수 있습니다.
댓글 로딩 중...