요소 애니메이션
개별 DOM 요소에 애니메이션 적용하기
SSGOI
from0
to1
spring
Transition 예제
다양한 transition 효과를 사용하는 방법입니다:
import { transition } from '@ssgoi/react';
import { fade, scale, blur, slide, fly, rotate, bounce } from '@ssgoi/react/transitions';
/**
* [React] Auto Key Plugin
*
* 플러그인 사용 시 key를 생략할 수 있습니다.
* 파일:라인:컬럼 기반으로 고유 key가 자동 생성됩니다.
*
* Next.js 설정:
* ```ts
* // next.config.ts
* import SsgoiAutoKey from "@ssgoi/react/unplugin/webpack";
*
* const nextConfig = {
* webpack: (config) => {
* config.plugins.push(SsgoiAutoKey());
* return config;
* },
* };
* ```
*
* ⚠️ 주의: .map() 리스트에서는 JSX key만 있으면 됨 - 플러그인이 자동으로 처리
*
* 예시:
* // .map() 리스트에서는 JSX key만 있으면 됨
* // 플러그인이 자동으로 file:line:col:${jsxKey} 형태로 생성
* {items.map((item) => (
* <div
* key={item.id} // JSX key만 있으면 충분
* ref={transition(fade())}
* >
* {item.name}
* </div>
* ))}
*
* 플러그인 없이 사용 시에는 key가 필수입니다.
*/
// Fade 트랜지션
<div ref={transition(fade({ physics: { spring: { stiffness: 300, damping: 30 } } }))}>
Fade 효과
</div>
// Scale 트랜지션
<div ref={transition(scale({ physics: { spring: { stiffness: 300, damping: 30 } } }))}>
Scale 효과
</div>
// Blur 트랜지션
<div ref={transition(blur({ amount: 10, physics: { spring: { stiffness: 300, damping: 30 } } }))}>
Blur 효과
</div>
// Slide 트랜지션 (방향 지정)
<div ref={transition(slide({ direction: 'left', physics: { spring: { stiffness: 300, damping: 30 } } }))}>
Slide 효과
</div>
// Fly 트랜지션 (커스텀 위치)
<div ref={transition(fly({ x: 200, y: -50, physics: { spring: { stiffness: 300, damping: 30 } } }))}>
Fly 효과
</div>
// Rotate 트랜지션
<div ref={transition(rotate({ physics: { spring: { stiffness: 300, damping: 30 } } }))}>
Rotate 효과
</div>
// Bounce 트랜지션
<div ref={transition(bounce({ physics: { spring: { stiffness: 300, damping: 30 } } }))}>
Bounce 효과
</div>
// 명시적 key 사용 (플러그인 없이 사용 시 필수)
<div ref={transition({
key: "scale-element",
...scale({ physics: { spring: { stiffness: 300, damping: 30 } } })
})}>
Scale 효과
</div>
기본 구조
TransitionConfig 인터페이스
interface TransitionConfig {
physics?: {
// Spring: 스프링 물리 (ease-out 효과, 기본값)
spring?: {
stiffness: number; // 스프링 강성 (기본: 300)
damping: number; // 감쇠 계수 (기본: 30)
doubleSpring?: boolean | number; // 이중 스프링 효과 (기본: false)
};
// Inertia: 관성 물리 (ease-in 효과)
inertia?: {
acceleration: number; // 가속도 (기본: 500)
resistance: number; // 저항 계수 (기본: 10)
};
// Custom Integrator: 커스텀 물리 엔진
integrator?: () => Integrator;
};
// 두 가지 애니메이션 모드 중 하나 선택 (둘 다 사용 불가)
tick?: (progress: number) => void; // RAF 기반 애니메이션
css?: (progress: number) => StyleObject; // Web Animation API 기반 (권장)
prepare?: (element: HTMLElement) => void; // 애니메이션 시작 전 초기 설정
onStart?: () => void;
onEnd?: () => void;
}
물리 엔진 옵션
SSGOI는 다양한 물리 엔진을 지원하여 자연스러운 애니메이션을 구현합니다:
Spring (스프링 물리)
ease-out 효과를 제공하는 기본 물리 엔진입니다. 애니메이션이 빠르게 시작하여 목표 지점에 도달하면서 자연스럽게 감속합니다.
{
in: (element) => ({
physics: {
spring: {
stiffness: 300, // 스프링 강성 (높을수록 빠름)
damping: 30, // 감쇠 계수 (높을수록 탄성 감소)
doubleSpring: false // 이중 스프링 효과
}
},
css: (progress) => ({
opacity: progress,
transform: `translateY(${(1 - progress) * 20}px)`,
}),
})
}
Inertia (관성 물리)
ease-in 효과를 제공하는 물리 엔진입니다. 애니메이션이 천천히 시작하여 점점 가속됩니다.
{
in: (element) => ({
physics: {
inertia: {
acceleration: 500, // 가속도 (높을수록 빠르게 가속)
resistance: 10 // 저항 계수 (높을수록 감속)
}
},
css: (progress) => ({
opacity: progress,
transform: `scale(${0.8 + progress * 0.2})`,
}),
})
}
Custom Integrator (커스텀 물리 엔진)
직접 물리 엔진을 구현하여 사용할 수 있습니다.
{
in: (element) => ({
physics: {
integrator: () => ({
next: (dt, state) => {
// 커스텀 물리 계산 로직
return newState;
}
})
},
css: (progress) => ({
opacity: progress,
}),
})
}
애니메이션 모드: css vs tick
SSGOI는 두 가지 애니메이션 모드를 지원합니다:
css 모드 (권장)
{
in: (element) => ({
physics: { spring: { stiffness: 300, damping: 30 } },
css: (progress) => ({
opacity: progress,
transform: `translateY(${(1 - progress) * 20}px)`,
}),
})
}
- Web Animation API 사용으로 메인 스레드 부하 최소화
- 스프링 물리 연산을 미리 계산하여 키프레임으로 변환
- GPU 가속을 활용한 부드러운 애니메이션
- 성능이 더 좋아 대부분의 경우 권장
tick 모드
{
in: (element) => ({
physics: { spring: { stiffness: 300, damping: 30 } },
tick: (progress) => {
element.style.opacity = String(progress);
element.style.transform = `translateY(${(1 - progress) * 20}px)`;
},
})
}
- requestAnimationFrame 기반으로 매 프레임마다 콜백 실행
- DOM을 직접 조작해야 하는 복잡한 애니메이션에 적합
- 애니메이션 중간에 동적으로 값을 변경해야 할 때 유용
css와 tick은 동시에 사용할 수 없습니다. 하나만 선택하세요.
트랜지션 정의
interface Transition {
in?: (element: HTMLElement) => TransitionConfig;
out?: (element: HTMLElement) => TransitionConfig;
}
동작 원리
- 마운트 시: 요소가 DOM에 추가될 때
in함수 실행 - 언마운트 시: 요소가 제거되기 전
out함수 실행 - 애니메이션: 스프링 물리 엔진이 progress 생성
- in: 0 → 1
- out: 1 → 0
- tick 콜백: 매 프레임마다 호출되어 스타일 업데이트
트랜지션 프리셋
import { fade, scale /** 등등 */ } from "@ssgoi/react/transitions";
프레임워크별 사용법
import { transition } from "@ssgoi/react";
<div
ref={transition({
key: "unique-key",
in: (element) => ({
tick: (progress) => {
element.style.opacity = progress;
element.style.transform = `translateY(${20 * (1 - progress)}px)`;
},
}),
out: (element) => ({
tick: (progress) => {
element.style.opacity = 1 - progress;
},
}),
})}
>
Content
</div>
Progress 동작 방식
key
key는 SSGOI가 각 요소의 애니메이션 상태를 추적하는 데 사용하는 고유 식별자입니다:
- 고유성: 페이지 내에서 각
transition()호출마다 고유한 key가 필요합니다 - 상태 추적: DOM 요소가 생성되거나 삭제될 때도 애니메이션 상태를 유지할 수 있도록 합니다
- React 자동 생성: React에서는 Auto Key Plugin을 사용하면 key를 수동으로 지정하지 않아도 됩니다 (설정 방법은 위 React 탭의 주석 참조)
- 수동 지정: 다른 프레임워크에서는 명시적으로 key를 지정해야 합니다
in 애니메이션
- progress: 0 → 1
- 요소가 나타날 때 실행
- 투명도 0에서 1로, 작은 크기에서 원래 크기로
out 애니메이션
- progress: 1 → 0
- 요소가 사라질 때 실행
- 투명도 1에서 0으로, 원래 크기에서 작은 크기로
// 예시: in과 out의 차이
{
in: (element) => ({
tick: (progress) => {
// progress: 0 → 1
element.style.opacity = progress; // 0 → 1
}
}),
out: (element) => ({
tick: (progress) => {
// progress: 1 → 0
element.style.opacity = progress; // 1 → 0
}
})
}
prepare 콜백
애니메이션이 시작되기 전에 DOM 요소를 준비하는 단계입니다:
{
in: {
prepare: (element) => {
// tick이 실행되기 전에 초기 상태 설정
element.style.willChange = 'opacity, transform';
},
tick: (progress) => ({
opacity: progress,
transform: `translateY(${20 * (1 - progress)}px)`
})
}
}
TransitionScope
TransitionScope는 자식 요소들의 애니메이션 동작을 제어하는 컨테이너입니다. 부모와 자식이 동시에 마운트/언마운트될 때 불필요한 애니메이션을 건너뛸 수 있습니다.
사용 시나리오
- 페이지 전환: 페이지가 전환될 때 개별 요소들이 각각 애니메이션하지 않도록
- 모달/다이얼로그: 컨테이너와 함께 나타나는 콘텐츠의 중복 애니메이션 방지
- 리스트: 전체 리스트가 마운트/언마운트될 때 개별 아이템 애니메이션 건너뛰기
scope 옵션
interface TransitionOptions {
// ...
scope?: 'global' | 'local';
}
global(기본값): 항상 애니메이션 실행local: TransitionScope와 동시에 마운트/언마운트될 때 애니메이션 건너뜀
사용 예시
import { TransitionScope, transition } from '@ssgoi/react';
function MyComponent() {
const [show, setShow] = useState(true);
return (
<>
<button onClick={() => setShow(!show)}>Toggle</button>
{show && (
<TransitionScope>
<div style={{ padding: '2rem', border: '1px dashed #ccc' }}>
{/* local: Scope와 함께 마운트/언마운트 시 애니메이션 건너뜀 */}
<div
ref={transition({
key: 'local-child',
scope: 'local',
in: () => ({
css: (p) => ({ opacity: p, transform: `scale(${0.5 + p * 0.5})` }),
}),
out: () => ({
css: (p) => ({ opacity: p, transform: `scale(${0.5 + p * 0.5})` }),
}),
})}
>
Local Scope (Scope와 함께 나타남/사라짐)
</div>
{/* global: 항상 애니메이션 실행 */}
<div
ref={transition({
key: 'global-child',
// scope: 'global' 은 기본값
in: () => ({
css: (p) => ({ opacity: p, transform: `scale(${0.5 + p * 0.5})` }),
}),
out: () => ({
css: (p) => ({ opacity: p, transform: `scale(${0.5 + p * 0.5})` }),
}),
})}
>
Global Scope (항상 애니메이션)
</div>
</div>
</TransitionScope>
)}
</>
);
}
동작 방식
| 상황 | scope: 'local' | scope: 'global' |
|---|---|---|
| Scope와 동시에 마운트 | 애니메이션 건너뜀 | 애니메이션 실행 |
| Scope와 동시에 언마운트 | 애니메이션 건너뜀 | 애니메이션 실행 |
| Scope 내에서 개별 토글 | 애니메이션 실행 | 애니메이션 실행 |