Lottie Inspector

스크롤·제스처에 반응하는 Lottie 인터랙션 만들기

INTERACTION

Lottie의 진짜 힘은 "자동 재생되는 GIF 대체품"이 아니라 진행률(progress)을 코드로 쥘 수 있다는 데 있습니다. 진행률을 스크롤·제스처·상태에 바인딩하는 순간 애니메이션은 UI의 일부가 됩니다.

핵심 개념 — 애니메이션은 0~1 타임라인이다

모든 Lottie 런타임은 "몇 번째 프레임을 보여줄지"를 외부에서 지정하는 API를 제공합니다. 인터랙션 설계는 결국 어떤 입력값을 0~1 진행률로 매핑할 것인가의 문제입니다.

입력매핑 예대표 사례
스크롤 오프셋섹션 진입~이탈 → 0~1스토리텔링 랜딩 페이지
드래그 거리당김 거리 → 0~1Pull-to-refresh
호버 상태enter → 재생, leave → 역재생아이콘 마이크로 인터랙션
토글 상태on 구간/off 구간 분할 재생좋아요·북마크 버튼

웹 — 스크롤 연동 기본형

const anim = lottie.loadAnimation({
  container: box, renderer: "svg",
  autoplay: false, loop: false,
  path: "/assets/story.json",
});

const section = document.querySelector("#story");
addEventListener("scroll", () => {
  const r = section.getBoundingClientRect();
  // 섹션이 화면을 통과하는 정도를 0~1로
  const t = Math.min(1, Math.max(0,
    (innerHeight - r.top) / (innerHeight + r.height)));
  anim.goToAndStop(t * (anim.totalFrames - 1), true);
});
스크롤 핸들러에서 매 프레임 goToAndStop을 호출해도 되지만, requestAnimationFrame으로 스로틀하고 IntersectionObserver로 화면 밖에서는 리스너를 떼는 것이 성능상 안전합니다.

토글 버튼 — 구간 분할 재생

좋아요 버튼처럼 켜짐/꺼짐이 있는 인터랙션은 디자이너에게 한 파일에 두 구간을 담아 달라고 요청하는 것이 정석입니다. 예: 0~30프레임 = 켜지는 모션, 30~60프레임 = 꺼지는 모션.

function setLiked(on) {
  anim.playSegments(on ? [0, 30] : [30, 60], true);
}

구간 경계 프레임이 정확히 어디인지는 에셋을 Lottie Inspector에서 천천히(0.25×) 재생해 보면 쉽게 찾을 수 있습니다.

모바일 앱에서는

설계 팁

성능이 무너지는 지점

위의 스크롤 기본형 코드를 실제 랜딩 페이지에 그대로 붙였다가 프레임 드랍을 제대로 겪은 적이 있습니다. 스크롤 이벤트는 브라우저와 입력 장치에 따라 한 프레임 안에 여러 번 발생할 수 있는데, 그때마다 goToAndStop을 호출하면 SVG 렌더러가 매번 트리를 다시 그립니다. 레이어가 30개쯤 되는 에셋이었는데, 트랙패드로 빠르게 스크롤하면 메인 스레드가 밀리면서 애니메이션만이 아니라 페이지 전체가 버벅였습니다.

해결은 의외로 단순했습니다. 스크롤 핸들러에서는 진행률 값만 변수에 저장하고, 실제 goToAndStop 호출은 requestAnimationFrame 루프에서 한 프레임에 한 번만 하도록 분리했습니다. 값이 갱신되지 않은 프레임에는 호출 자체를 건너뜁니다.

let pending = null;
addEventListener("scroll", () => { pending = calcProgress(); });

function tick() {
  if (pending !== null) {
    anim.goToAndStop(pending * (anim.totalFrames - 1), true);
    pending = null;
  }
  requestAnimationFrame(tick);
}
requestAnimationFrame(tick);

제 프로젝트 기준으로 이 분리만으로 스크롤 중 프레임 시간이 눈에 띄게 안정됐고, 여기에 IntersectionObserver로 섹션이 화면 밖에 있을 때 리스너를 떼도록 하자 다른 섹션을 스크롤할 때의 부하까지 사라졌습니다. 그래도 무겁다면 코드가 아니라 에셋 자체를 손볼 차례입니다. 레이어 수와 이펙트를 줄이는 방법은 Lottie 파일 최적화 글에 따로 정리했습니다.

디자이너에게 요청하는 법

인터랙티브 Lottie에서 겪는 문제의 절반은 코드가 아니라 에셋 단계에서 생깁니다. 자동 재생용으로 만든 파일을 그대로 받아 스크럽에 붙였더니 특정 지점에서 모션이 뚝뚝 끊겼고, 이걸 코드로 어떻게든 보정하려다 시간을 버린 경험이 있습니다. 그 뒤로는 요청 단계에서 아래 세 가지를 스펙으로 정리해 먼저 전달합니다.

항목전달할 내용이유
구간 경계"0~30 켜짐, 30~60 꺼짐"처럼 프레임 번호를 숫자로 명시playSegments 경계가 한 프레임만 어긋나도 모션이 튑니다
이징 굽기표현식이나 보간에 의존하지 말고 키프레임에 구워 달라고 요청스크럽은 어느 프레임에서든 멈출 수 있어야 자연스럽습니다
총 프레임 수스크럽 거리 대비 충분한 프레임(제 기준 스크롤 한 화면당 60프레임 이상)프레임이 부족하면 스크럽이 계단처럼 끊겨 보입니다

특히 이징은 말로 설명하기가 어려워서, 저는 "중간 어느 지점에서 멈춰도 어색하지 않아야 합니다"라는 문장을 요청서에 그대로 넣습니다. 받은 에셋은 Lottie Inspector에서 느리게 재생하며 구간 경계와 끊김 여부를 먼저 확인하고, 문제가 있으면 프레임 번호를 찍어 돌려보냅니다. 디자이너와 주고받는 전달 과정 전반은 디자이너-개발자 핸드오프 글에서 더 다뤘습니다.