Lottie는 After Effects로 만든 애니메이션을 JSON으로 내보내 웹에서 SVG/Canvas로 부드럽게 재생하는 기술입니다. React에서는 가볍고 선명한 인터랙션을 구현할 때 매우 유용합니다. 이 글은 실무에서 바로 쓰는 통합 방법과 성능/SSR/접근성까지 핵심만 정리합니다.
1. 어떤 라이브러리를 쓸까?
가장 쉬운 선택은 lottie-react입니다. 내부적으로 lottie-web을 감싸 React 친화적 API와 ref 제어, 콜백을 제공합니다. 세밀한 초기화 옵션이 필요하면 lottie-web을 직접 사용할 수 있습니다.
권장: 빠른 개발은 lottie-react, 커스텀 초기화(assetsPath 등)나 독자 렌더러 튜닝이 필요하면 lottie-web 직접 사용을 고려합니다.
2. 설치
// 옵션 1: lottie-react (권장)
npm i lottie-react
// 옵션 2: lottie-web 직접 사용
npm i lottie-web3. lottie-react로 기본 통합
가장 단순한 예제입니다. JSON은 정적 import 또는 public 경로를 사용할 수 있습니다.
import React from 'react'
import Lottie from 'lottie-react'
import rocket from './assets/rocket.json'
export default function Hero() {
return (
<div style={{ width: 240 }}>
<Lottie animationData={rocket} loop autoplay />
</div>
)
}원격 JSON을 fetch해서 쓰고 싶다면 상태로 로드한 뒤 animationData에 전달합니다.
import React, { useEffect, useState } from 'react'
import Lottie from 'lottie-react'
export default function RemoteLottie() {
const [data, setData] = useState(null)
useEffect(() => {
fetch('https://assets10.lottiefiles.com/packages/lf20_rocket.json')
.then(r => r.json())
.then(setData)
}, [])
if (!data) return null
return <Lottie animationData={data} loop autoplay />
}4. 재생 제어(Play/Pause/Speed/Segments)
ref로 미세 제어가 가능합니다. 호버 재생, 구간 재생 등 UX를 쉽게 만들 수 있습니다.
import React, { useRef } from 'react'
import Lottie from 'lottie-react'
import check from './check.json'
export default function ControlDemo() {
const lottieRef = useRef(null)
return (
<div>
<Lottie
lottieRef={lottieRef}
animationData={check}
loop={false}
autoplay={false}
onComplete={() => console.log('완료')}
style={{ width: 160 }}
/>
<div style={{ display: 'flex', gap: 8, marginTop: 8 }}>
<button onClick={() => lottieRef.current?.play()}>Play</button>
<button onClick={() => lottieRef.current?.pause()}>Pause</button>
<button onClick={() => lottieRef.current?.stop()}>Stop</button>
<button onClick={() => lottieRef.current?.setSpeed(2)}>2x</button>
<button onClick={() => lottieRef.current?.playSegments([0, 45], true)}>0~45</button>
</div>
</div>
)
}5. lottie-web 직접 사용
초기화 옵션을 세밀하게 제어하거나 assetsPath가 필요한 이미지 기반 Lottie에 유용합니다.
import React, { useEffect, useRef } from 'react'
import lottie from 'lottie-web'
export default function BareLottie() {
const boxRef = useRef(null)
useEffect(() => {
if (!boxRef.current) return
const anim = lottie.loadAnimation({
container: boxRef.current,
renderer: 'svg', // 'canvas' 또는 'html' 가능
loop: false,
autoplay: false,
path: '/animations/like.json',
// animationData: json 객체로도 가능
assetsPath: '/animations/images/',
rendererSettings: { preserveAspectRatio: 'xMidYMid meet' }
})
return () => anim.destroy()
}, [])
return <div ref={boxRef} style={{ width: 200, height: 200 }} />
}6. 지연 로딩과 번들 최적화
애니메이션은 무겁습니다. 화면에 보일 때만 로드하고, JSON은 분리하세요.
import React, { useEffect, useRef, useState, Suspense } from 'react'
const Lottie = React.lazy(() => import('lottie-react'))
export default function LazyInView() {
const hostRef = useRef(null)
const [inView, setInView] = useState(false)
const [data, setData] = useState(null)
useEffect(() => {
const obs = new IntersectionObserver(([e]) => setInView(e.isIntersecting), { threshold: 0.2 })
if (hostRef.current) obs.observe(hostRef.current)
return () => obs.disconnect()
}, [])
useEffect(() => {
if (!inView || data) return
import('./heavy.json').then(m => setData(m.default)) // JSON 분리 로딩
}, [inView, data])
return (
<div ref={hostRef} style={{ minHeight: 220 }}>
{inView && data ? (
<Suspense fallback={null}>
<Lottie animationData={data} loop autoplay />
</Suspense>
) : null}
</div>
)
}팁: 여러 페이지에서 공용으로 쓰는 JSON은 CDN 또는 public 폴더로 옮기고 path로 스트리밍하면 초기 번들을 가볍게 유지할 수 있습니다.
7. Next.js/SSR 대응
서버에서 window가 없어 오류가 날 수 있습니다. 클라이언트 전용으로 로드하세요.
// pages/ 또는 app/ 어디서나
import dynamic from 'next/dynamic'
const Lottie = dynamic(() => import('lottie-react'), { ssr: false })
export default function Hero() {
return <Lottie animationData={require('../public/rocket.json')} loop autoplay />
}lottie-web을 직접 쓸 때는 useEffect 안에서만 loadAnimation을 호출하고, 컴포넌트 언마운트 시 destroy를 호출합니다.
8. 접근성과 사용자 설정(감소된 동작)
정보 전달 목적이라면 대체 텍스트를 제공하고, 장식용이면 스크린리더에 숨깁니다. 또한 사용자 설정(prefers-reduced-motion)을 존중하세요.
import React, { useEffect, useRef, useState } from 'react'
import Lottie from 'lottie-react'
import wave from './wave.json'
export default function A11yLottie() {
const [reduced, setReduced] = useState(false)
const ref = useRef(null)
useEffect(() => {
const m = window.matchMedia('(prefers-reduced-motion: reduce)')
const onChange = () => setReduced(m.matches)
setReduced(m.matches)
m.addEventListener('change', onChange)
return () => m.removeEventListener('change', onChange)
}, [])
return (
<Lottie
lottieRef={ref}
animationData={wave}
autoplay={!reduced}
loop={!reduced}
aria-label='파도 애니메이션'
role='img'
/>
)
}장식용이면 aria-hidden='true'를 부여합니다.
9. 성능 최적화 체크리스트
- JSON 용량을 줄이세요: 프레임레이트 낮추기, 불필요한 레이어 제거, 텍스트를 셰이프로 변환, LottieFiles Optimizer 활용을 권장합니다.
- 렌더러 선택: 대부분 svg가 선명하고 가벼우나, 수천 개의 벡터가 움직일 때는 canvas가 더 빠를 수 있습니다.
- 재생 범위 제한: 최초에는 정지(goToAndStop) 상태로 썸네일만 보여주고, 사용자 상호작용 시 playSegments로 필요한 구간만 재생합니다.
- 메모이제이션: animationData 객체를 매 렌더마다 새로 만들지 말고 import 또는 useMemo로 고정합니다.
- 오프스크린 중지: 탭 비활성, 뷰포트 밖에서는 pause해 CPU를 아낍니다(IntersectionObserver 연계).
10. 자주 만나는 이슈와 해결
- 크기 깨짐: 부모 컨테이너에 명확한 width/height를 주고 preserveAspectRatio를 설정합니다.
- 이미지 에셋 경로: AE에서 이미지 기반 Lottie는 assets 폴더를 사용합니다. lottie-web은 assetsPath 옵션으로 절대 경로를 지정하세요.
- 무한 루프 종료 감지: onLoopComplete 콜백에서 loop를 false로 바꾸거나 ref.stop()을 호출합니다.
- 큰 JSON의 초기 깜빡임: lazy 로딩 + skeleton을 쓰고 autoplay를 지연합니다.
11. 마무리
lottie-react로 시작해 빠르게 결과를 내고, 필요 시 lottie-web로 세밀 제어를 더하는 전략이 실무에 적합합니다. 지연 로딩, 접근성, SSR까지 고려하면 가볍고 매끄러운 인터랙션을 안정적으로 제공할 수 있습니다.
'React' 카테고리의 다른 글
| React로 멀티 스텝(Form Wizard) 폼 구현하기 (0) | 2026.05.16 |
|---|---|
| React 앱에서 클립보드 API 사용하기 (0) | 2026.05.15 |
| React로 마이크로 프론트엔드 구현하기 (0) | 2026.05.14 |
| React 앱에서 Atomic Design 패턴 적용하기 (0) | 2026.05.14 |
| React에서 IndexedDB를 이용한 오프라인 데이터 저장 (1) | 2026.05.13 |