회사에서는 Unity 기반 앱 서비스가 메인이었고 인터랙티브한 화면이 많아 애니메이션은 주로 Spine을 사용하고 있었습니다.

학습 서비스를 하이브리드 웹앱 형태로 전환해 새로 만들게 됐고, 저는 플랫폼 프론트엔드 개발을 담당했습니다.

초기에는 기존 앱과 동일하게 Spine 애니메이션을 그대로 웹에 사용했는데 개발을 진행할수록 웹 환경과는 맞지 않은 것 같아 Lottie로 전환하게 됐어요.

Lottie가 더 적합하다고 판단한 이유

1. 메인 페이지의 무한 애니메이션

앱을 실행하면 사용자가 가장 먼저 접속하는 메인 페이지 전체 배경이 무한 루프로 계속 재생되는 애니메이션인데, 단순한 아이콘이나 캐릭터가 아니라 화면을 크게 차지하는 배경 애니메이션이었습니다. 문제는 초기 진입 시 로딩 시간이 길다는 점이었어요.

웹에서 Spine 애니메이션 구동 과정

  1. Spine 런타임 및 PixiJS 초기화
  2. skeleton / atlas 파일 네트워크 다운로드 및 파싱
  3. texture 이미지 네트워크 다운로드 및 디코딩
  4. GPU texture 업로드
  5. Spine 인스턴스 생성 및 상태 초기화
  6. 첫 프레임 렌더링

앱에서는 Spine 리소스가 앱 빌드 시점에 포함되어 로컬 리소스이지만, 웹에서는 애니메이션에 필요한 파일들을 네트워크 요청으로 다운로드한 뒤 파싱과 GPU 업로드가 진행됩니다. 모두 첫 화면 진입 이후에 시작되면서 앱에서는 설치 과정에 포함되던 작업이 웹에서는 로딩시간으로 드러났습니다.

2. 애니메이션 동시 실행

프로젝트에는 맵 꾸미기 기능이 있었는데 사용자가 아이템(놀이기구, 동물 등)을 자유롭게 배치하고 크기 조절이나 위치 이동이 가능했습니다. 맵 최초 입장 시 배치된 모든 애니메이션을 재생, 아이템 최초 배치 또는 클릭 시 그 아이템의 애니메이션이 재생되도록 구현했어요. 하나의 캔버스 안에서 여러 Spine 애니메이션이 동시에 실행되는 구조였습니다.

여러 Spine 애니메이션을 동시에 실행할 경우 각 애니메이션이 화면을 그리기 위한 계산을 따로 수행하게 되어 애니메이션 수가 늘어날수록 이 계산이 반복되면서 화면을 그리기 위한 작업이 늘어났습니다.

그래서 맵 최초 진입 시 배치된 아이템 수가 10개만 돼도 Spine 애니메이션을 한 번에 생성하고 초기화하는 시간이 길게 느껴졌습니다.

3. spine 생태계

개인적으로 이 부분이 가장 중요했어요.

프론트엔드 개발을 혼자 담당하고 있어 한 기능에 많은 시간을 투자하기 어려웠고, pixi-spine 애니메이션은 React에서 사용한 사례나 공식 문서 외 레퍼런스가 거의 없었습니다. 오류나 버그 상황을 만났을 때 해결 방법을 찾기 쉽지 않았고, 신규 기능을 추가하는 데도 시간이 오래 걸렸어요.

Spine 대체제 검토

1. GIF

구현도 쉽고 로딩도 빠르고 별도 라이브러리도 필요 없으니 가장 먼저 떠올렸습니다. 하지만 GIF는 화질 문제가 있었고, 전용 태블릿 환경이라 반응형 대응이 필요 없는 상황에서도 화질이 깨지는 것이 눈에 띄었어요. 또 애니메이션 중단, 재실행 등 프레임 제어 기능이 없습니다.

2. APNG

다음으로 생각한 건 애니메이션 PNG였습니다. spine으로 만들어진 애니메이션 몇개를 APNG로 export 해봤는데 파일 용량이 너무 컸고 GIF와 동일하게 프레임 제어에 한계가 있었어요.

3. Lottie

마지막으로 검토한 대안이 Lottie였습니다.

Lottie는 JSON 기반 애니메이션 포맷으로, 벡터 정보와 애니메이션 데이터를 텍스트 형태로 저장합니다.

Spine이나 스프라이트 기반 애니메이션과 비교했을 때 용량이 상대적으로 작고 해상도 문제도 없었어요. 웹에서 다루기 쉽고 React, Vue 레퍼런스가 풍부!! 합니다.

웹에서의 초기 로딩과 기능을 생각하면 프로젝트에 가장 잘 맞다고 판단했어요.

Lottie 도입 과정

1. 디자이너와의 협업

먼저 디자이너들이 사용하는 툴을 파악했습니다.

  • 이미지 제작: Photoshop / Illustrator
  • 애니메이션: After Effects

이미 Lottie 제작에 필요한 툴을 사용하고 계셔서 메인 화면에 사용되는 일부 애니메이션만 Lottie로 요청했습니다.

다행히 작업 방식에 큰 부담은 없었고 디자이너들과의 협업도 비교적 수월하게 진행되었습니다.

2. Spine vs Lottie A/B 테스트

같은 애니메이션을 기준으로 Dev 서버에 배포해 테스트 했습니다. 첫 번째 계정에는 Spine 애니메이션이 보이게 하고 두 번째 QA 계정에는 Lottie 애니메이션이 보이게 적용했어요.

기획팀과 QA팀에 어느 쪽이 더 빠른 것 같은지 테스트 해달라고 요청해서 Lottie 쪽이 더 빠르게 느껴진다는 피드백을 받았습니다.

3. 비교 분석

Spine과 Lottie 중 어떤 방식을 선택할지 논의할 때 “조금 더 빠른 것 같다”, “왠지 가벼워 보인다”, “레퍼런스가 많다” 같은 의견만으로는 설득력이 부족하다고 생각했어요.

lottie로 바꾸는 건 한두 화면이 아니라 프로젝트 전체에 영향을 주고 개발 구조뿐 아니라 디자이너들의 애니메이션 제작 방식까지 함께 변경되어야 했습니다. 유지보수와 성능까지 함께 고려해야 했기 때문에 명확한 기준을 세워서 비교했습니다.

비교 대상

  • Lottie: lottie-react
  • Spine: @pixi/spine-pixi

    비교대상은 말고 프로젝트에서 사용한 라이브러리이며 Spine 실행에 필요한 WebGL 렌더링 환경을 제공하는 라이브러리로 사용했습니다.

비교 항목

  • 애니메이션 assets 파일과 실제 네트워크 크기
  • 초기 로딩 과정에서 소요되는 시간
  • 실행 라이브러리 번들 크기
  • 첫 프레임이 사용자에게 보여지기까지의 시간

파일 크기

처음에는 단순히 Lottie는 JSON이라 가볍고, Spine은 이미지가 있어서 무겁다 정도로만 인식했어요.

같은 애니메이션 파일 크기 비교

하지만 크롬 개발자 도구 네트워크 탭에서 확인해보면 실제 사이즈보다 작게 나오는 걸 볼 수 있을 거예요. 압축된 전송 크기, 압축 해제 후 크기, 브라우저가 실제로 처리하는 데이터 양이 각각 다르기 때문에 단순 파일 용량만으로는 판단하기 어렵습니다.

그래서 네트워크 탭에서 실제 다운로드 된 용량과 시간을 확인해보거나 브라우저의 PerformanceResourceTiming API를 사용해 실제로 네트워크를 통해 전달되고 처리된 바이트 수를 기준으로 측정할 수 있습니다.

const resources = performance.getEntriesByType("resource");
  • transferSize: 네트워크 요청 과정에서 전송된 바이트 크기
  • encodedBodySize: 압축된 응답 본문 크기
  • decodedBodySize: 압축 해제된 HTTP 응답 본문 크기

이 수치를 통해 asset이 가볍다 / 무겁다 가 아니라 브라우저가 실제로 얼만큼의 데이터를 처리하고 있는지를 기준으로 비교할 수 있습니다.

애니메이션 준비부터 첫 프레임

pixi-spine과 lottie-react는 동작이 달라서 내부 단계를 동일하게 맞추기 어렵습니다. 그래서 에셋이 사용 가능한 상태 준비 시간, 실행 인스턴스를 생성하는 데 걸린 시간, 첫 프레임이 보이는 시점을 정의해 비교했습니다.

에셋 로딩

에셋 준비 시간을 다운로드만 후 파싱/디코딩/내부 캐시 등록까지 포함한 준비 완료 기준으로 측정했습니다. lottie는 일반적으로 path 옵션을 사용하지만 Spine과 동일하게 에셋이 사용 가능한 시점을 비교하기 위해 JSON을 직접 fetch하는 방식으로 조건을 맞췄습니다.

// pixi-spine
const assetLoadStart = performance.now();
await PIXI.Assets.load(["spineJson", "spineAtlas"]);
const assetLoadEnd = performance.now();
console.log("[Spine] Asset ready time:", assetLoadEnd - assetLoadStart);

// lottie
const assetLoadStart2 = performance.now();
const res = await fetch("/lottie/test-animation.json", { cache: "no-store" });
const buf = await res.arrayBuffer();
const json = JSON.parse(new TextDecoder().decode(buf));
const assetLoadEnd2 = performance.now();
console.log("[Lottie] Asset ready time:", assetLoadEnd2 - assetLoadStart2);

애니메이션 객체 생성 단계

에셋이 준비된 이후 실제 애니메이션 객체를 만드는 시간으로 측정했습니다.

// pixi-spine
const buildStart = performance.now();
const spineboy = Spine.from({
  skeleton: "spineJson",
  atlas: "spineAtlas",
  scale: 1,
});
const buildEnd = performance.now();
console.log("[Spine] Animation object creation time:", buildEnd - buildStart);

// lottie
const buildStart = performance.now();
const anim = lottie.loadAnimation({
  container,
  renderer: "canvas",
  loop: true,
  autoplay: true,
  animationData: json,
});
const buildEnd = performance.now();
console.log("[Lottie] Animation object creation time:", buildEnd - buildStart);

첫 렌더 시간

첫 프레임이 보이는 시점(TTFF)을 requestAnimationFrame 기준으로 측정했습니다.

// pixi-spine
spineboy.state.setAnimation(0, "run", true);
app.stage.addChild(spineboy);

const pageStart = performance.now();
requestAnimationFrame(() => {
  console.log("[Spine] First render painted:", performance.now() - pageStart);
});

// lottie
const pageStart = performance.now();
requestAnimationFrame(() => {
  console.log("[Lottie] First render painted:", performance.now() - pageStart);
});
측정 결과
Canvas LOG
SVG Log
Spine Log

상대적으로 Lottie는 JSON 파일 하나로 에셋 준비 시간이 짧지만 애니메이션 생성 단계에서는 Canvas 렌더링의 경우 path 계산과 keyframe 처리, draw 명령 준비 등의 연산이 수행되고 SVG 렌더링은 DOM 노드 생성이 중심이 되면서 Spine보다 오래 걸렸습니다.

반대로 Pixi-Spine은 에셋 로딩 단계에서 텍스처 디코딩과 준비를 선행하는 구조를 갖고 있어 애니메이션 인스턴스 생성 시간은 상대적으로 짧았지만 첫 렌더 시점에는 WebGL 기반 특성상 shader 준비, texture binding, vertex buffer 생성, 그리고 첫 draw call 등의 작업이 한꺼번에 수행되면서 세 방식 중 첫 프레임 렌더링 시간이 가장 느렸습니다.

번들 크기와 생태계

마지막으로 프로젝트 전반에 미치는 영향을 보기 위해 npm 기준 번들 크기와 저한테 가장 중요했던 생태계 지표도 함께 비교했습니다.

NPM Lottie-React
NPM PIXI-SPINE

Lottie 라이브러리는 애니메이션을 재생하는 데 필요한 코드의 크기가 수십~수백 KB 수준으로 비교적 작았고 dependencies도 단순해서 애니메이션을 사용하기 위해 추가로 포함되는 코드가 적은 걸 볼 수 있어요.

반면 Pixi-Spine은 PixiJS 위에서 동작하는 구조인데 애니메이션 한 개를 사용하더라도 WebGL 기반 렌더링에 필요한 코드가 함께 포함되어 라이브러리 크기 자체가 MB 단위였습니다.

주간 다운로드 수 차이는 컸습니다.

Lottie 관련 패키지는 웹에서 많이 사용되며 안정적인 다운로드 수를 유지하고 있고 React·Vue 등 프레임워크와 사용도 많은 레퍼런스를 찾아볼 수 있는데, Pixi-Spine은 웹 기준 사용 사례와 다운로드 수가 제한적이며 문제 발생 시 참고할 수 있는 사례도 게임이나 네이티브 중심의 자료 위주로 웹 관련 레퍼런스는 적었습니다.

이 비교로 Lottie는 초기 번들 크기, dependencies, reference 까지 고려했을 때 웹에서 더 사용하기 좋은 라이브러리라고 생각했습니다.

마무리

이렇게 정리한 지표를 바탕으로 팀원들과 논의 후 이번 프로젝트는 Lottie를 도입하기로 했습니다.

Spine은 앱이나 게임 환경에서는 좋은 선택지이지만 웹 환경에서는 에셋 구성과 로딩 흐름, 그리고 첫 화면이 나타나는 시점을 예측하고 공유하기가 쉽지 않았어요. 특히 문제가 발생했을 때 원인을 추적하거나 비개발자에게 성능 특성을 설명해야 하는 상황을 고려하면 부담이 크다고 판단했습니다.

반면 Lottie는 에셋 구조와 로딩 경로가 단순하며 웹 환경에서의 사용 사례와 레퍼런스를 쉽게 확인할 수 있었고 성능 측면에서도 에셋 준비, 객체 생성, 첫 프레임 렌더링 시점을 명확하게 분리해 설명할 수 있었습니다.

이번 비교를 통해 기술적인 구현보다 팀이 해당 선택을 이해하고 관리할 수 있는지가 더 중요하다 느꼈습니다.

이 프로젝트에서 Lottie를 도입한 경험은 단순한 성능 수치 이상의 판단 기준을 팀과 함께 정리할 수 있었다는 점에서 의미 있는 결정이었습니다.