커스텀 Transition — 나만의 애니메이션 만들기
내장 트랜지션으로 부족할 때, 직접 만들면 됩니다 — Svelte의 트랜지션 함수 인터페이스는 놀라울 만큼 단순합니다.
개념 정의
커스텀 Transition 은 Svelte의 트랜지션 인터페이스를 구현한 함수입니다. (node, params) => TransitionConfig 형태의 함수를 작성하면 transition:, in:, out: 디렉티브에서 사용할 수 있습니다.
TransitionConfig 인터페이스
interface TransitionConfig {
delay?: number; // 시작 지연 (ms)
duration?: number; // 지속 시간 (ms)
easing?: (t: number) => number; // 이징 함수
css?: (t: number, u: number) => string; // CSS 문자열 반환
tick?: (t: number, u: number) => void; // 프레임마다 실행
}
// t: 0(시작) → 1(끝), u: 1-t
CSS 기반 커스텀 트랜지션
CSS 기반은 메인 스레드를 차단하지 않아 성능이 좋습니다.
<script>
let visible = $state(true);
// 타이프라이터 효과
function typewriter(node, { speed = 50 }) {
const text = node.textContent;
const duration = text.length * speed;
return {
duration,
css: (t) => {
const i = Math.floor(text.length * t);
return `
clip-path: inset(0 ${100 - (i / text.length) * 100}% 0 0);
white-space: nowrap;
`;
}
};
}
// 회전하며 나타나기
function spin(node, { duration = 500, degrees = 360 }) {
return {
duration,
css: (t) => {
const eased = t;
return `
transform: rotate(${eased * degrees}deg) scale(${eased});
opacity: ${eased};
`;
}
};
}
// 흔들림 효과
function shake(node, { duration = 400, intensity = 10 }) {
return {
duration,
css: (t) => {
const offset = Math.sin(t * Math.PI * 6) * intensity * (1 - t);
return `transform: translateX(${offset}px);`;
}
};
}
</script>
<button onclick={() => visible = !visible}>토글</button>
{#if visible}
<p transition:typewriter={{ speed: 30 }}>
이 텍스트가 한 글자씩 나타납니다
</p>
<div transition:spin={{ duration: 800 }}>
회전하며 등장!
</div>
{/if}
tick 기반 커스텀 트랜지션
CSS로 표현할 수 없는 애니메이션은 tick을 사용합니다.
<script>
let visible = $state(true);
// 숫자 카운트업 효과
function countUp(node, { target = 100, duration = 1000 }) {
return {
duration,
tick: (t) => {
node.textContent = Math.floor(t * target).toLocaleString();
}
};
}
// 랜덤 문자 효과 (매트릭스 스타일)
function scramble(node, { duration = 800 }) {
const text = node.textContent;
const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
return {
duration,
tick: (t) => {
let result = '';
for (let i = 0; i < text.length; i++) {
if (i < text.length * t) {
result += text[i];
} else {
result += chars[Math.floor(Math.random() * chars.length)];
}
}
node.textContent = result;
}
};
}
</script>
{#if visible}
<span in:countUp={{ target: 1000, duration: 1500 }}>0</span>
<p in:scramble={{ duration: 1000 }}>안녕하세요 스벨트!</p>
{/if}
실전 예시 — 카드 플립 트랜지션
<script>
let visible = $state(true);
function cardFlip(node, { duration = 600, direction = 'horizontal' }) {
const axis = direction === 'horizontal' ? 'Y' : 'X';
return {
duration,
css: (t) => {
const degrees = (1 - t) * 180;
return `
transform: perspective(600px) rotate${axis}(${degrees}deg);
opacity: ${t};
backface-visibility: hidden;
`;
}
};
}
function ripple(node, { duration = 600 }) {
const style = getComputedStyle(node);
const width = parseFloat(style.width);
const height = parseFloat(style.height);
const maxDim = Math.max(width, height);
return {
duration,
css: (t) => `
clip-path: circle(${t * maxDim * 1.5}px at center);
opacity: ${Math.min(t * 2, 1)};
`
};
}
</script>
<button onclick={() => visible = !visible}>토글</button>
{#if visible}
<div class="card" transition:cardFlip={{ duration: 800 }}>
<h3>카드 플립</h3>
<p>3D 회전 트랜지션</p>
</div>
<div class="card" transition:ripple>
<h3>리플 효과</h3>
<p>원형으로 확장</p>
</div>
{/if}
<style>
.card { padding: 1.5rem; margin: 1rem 0; background: #f5f5f5; border-radius: 8px; }
</style>
트랜지션 함수 조합
<script>
import { cubicOut } from 'svelte/easing';
// 여러 효과를 조합하는 팩토리 함수
function combined(node, {
duration = 500,
y = 20,
scale = 0.95,
blur = 5,
easing = cubicOut
}) {
return {
duration,
easing,
css: (t) => `
transform: translateY(${(1 - t) * y}px) scale(${scale + (1 - scale) * t});
opacity: ${t};
filter: blur(${(1 - t) * blur}px);
`
};
}
</script>
{#if visible}
<div transition:combined={{ y: 30, blur: 8, duration: 400 }}>
복합 트랜지션 효과
</div>
{/if}
면접 포인트
- "CSS 기반과 tick 기반의 차이는?": CSS 기반 트랜지션은 CSS 애니메이션으로 변환되어 GPU에서 실행되므로 성능이 좋습니다. tick 기반은 매 프레임 JavaScript를 실행하므로 메인 스레드를 차지하지만, DOM 내용 변경 같은 CSS로 불가능한 효과를 구현할 수 있습니다.
- "t와 u 매개변수는 무엇인가요?": t는 0에서 1로 진행하는 트랜지션 진행률이고, u는 1-t입니다. in 트랜지션에서 t=0이 시작, t=1이 끝이며, out 트랜지션에서는 반대입니다.
정리
커스텀 트랜지션은 결국 "t 값(0→1)에 따라 CSS나 DOM을 어떻게 변화시킬지" 정의하는 함수입니다. 인터페이스가 단순하니 상상력만 있으면 어떤 애니메이션이든 만들 수 있습니다.
댓글 로딩 중...