타이밍 차트: SMIL 애니메이션을 위한 청사진
요약
SVG를 활용한 웹 애니메이션 기법에 대해 다루며, 특히 SMIL(Synchronized Multimedia Integration Language)을 소개합니다. SVG는 일반 HTML/CSS보다 넓은 환경에서 사용 가능하며, CSS와 JavaScript 외에도 JS 없이 모든 것을 애니메이션화할 수 있는 SMIL이 여전히 유효함을 강조합니다.
핵심 포인트
- SVG는 단순 div보다 장점이 많아 웹 애니메이션에 적합하다.
- SMIL은 JavaScript 없이 SVG 전체를 애니메이션화하는 방법이다.
- SMIL은 하나의 요소와 단 하나의 속성만 대상으로 할 수 있어 복잡할 수 있다.
- 애니메이션 계획 시 '타이밍 차트'를 활용하여 구조화하는 것이 좋다.
웹상의 모든 것이 기본적으로 박스 형태라는 것을 알고 있지만, 원형처럼 위장하는 많은 애니메이션 <div>들을 발견할 수 있습니다. 하지만 실제 <circle> 요소를 접해본 적이 있다면, 그것들이 훨씬 더 많은 장점을 가지고 있다는 것을 알게 될 것입니다. SVG로 꾸며진 이들은 평범한 HTML/CSS를 입은 <div>보다 더 넓은 범위의 환경에 들어맞습니다. <img>는 엄격하게 .html을 허용하지 않습니다.
<img> 태그는 이름이 암시하는 것만큼 정적이지 않습니다(https://html.spec.whatwg.org/multipage/embedded-content.html#attr-img-src). 아쉽게도 <img> 태그로 SVG 파일을 로드할 경우 임베디드 JavaScript는 작동하지 않지만, CSS 애니메이션은 완벽하게 작동합니다. 많은 SVG 속성들이 CSS 속성 대응 기능을 가지고 있으며, 기하학적 속성들은 2024년부터 주요 브라우저에서 지원되어 왔습니다. viewBox와 같이 애니메이션을 적용하고 싶은 일부 속성은 아직 동등한 기능이 없습니다.
JavaScript와 CSS 외에도, SVG를 애니메이션화하는 또 다른 방법이 있습니다: Synchronized Multimedia Integration Language (SMIL). 그 단점에도 불구하고, 여전히 학습할 가치가 있습니다. CSS 애니메이션과 마찬가지로 SMIL 애니메이션 역시 <img> 태그에서 작동하며, JavaScript 없이 SVG의 모든 것을 완전히 애니메이션화할 수 있습니다.
SMIL에 대해 들어본 적이 없거나 복습이 필요한 경우, Andy Clarke의 잘 알려진 기사를 확인해 보세요. 그런 다음 애니메이션을 계획하고 SMIL 마크업을 더 관리하기 쉽게 만드는 방법을 살펴보겠습니다.
The Break Up
SMIL은 문제가 있습니다. 바로 부풀어 오르기 쉽다는 것입니다. CSS나 JavaScript의 경우 여러 속성을 각 키프레임에 나열하고 애니메이션을 쉽게 재사용할 수 있는 반면, 각 SMIL 태그는 한 번에 하나의 요소와 그 요소의 단 하나의 속성만을 대상으로 할 수 있습니다. 속성은 값 목록(list of values)을 통해 애니메이션될 수 있지만, 여전히 하나의 태그, 하나의 요소, 하나의 속성입니다. 색상과 불투명도 변경을 실행하는 가장 짧은 방법은 다음과 같습니다:
<animate
attributeName="fill"
to="someOtherColor"...
나쁘지 않지만, 애니메이션에 포함되는 모든 요소에 대해 반복되어야 한다는 점을 고려해 보세요. SMIL 애니메이션은 CSS와 동등한 길이보다 훨씬 길어질 수 있습니다.
새로운 애니메이션을 시작할 때 작업을 더 쉽게 하려면, 우리가 애니메이션하려는 모든 요소와 속성을 계획하고 각 태그에 대한 설명적인 ID 목록을 만드는 것이 좋습니다.
애니메이션 시간과 공간 차트 그리기
저는 **타이밍 차트(timing chart)**라고 불리는 것을 사용하여 애니메이션을 계획하는 것을 좋아합니다. 타이밍 차트는 효과적으로 선분입니다; 어떤 사람들은 수평선을, 다른 사람들은 수직선을 선택하는데, 이는 선분이 평행하게 움직이고, 겹치고, 간격이 있거나 없어도 서로를 따라갈 수 있기 때문에 전체 애니메이션에 대한 훌륭한 비유가 됩니다. 애니메이션과 마찬가지로 말이죠.
지금은 애니메이션이 시작하고 멈추는 시점만 관심이 있습니다. 차트를 그릴 때, 중간의 선들은 무시하고 대신 각 구성 요소 애니메이션을 위한 선을 그려서 시작점과 끝점을 표시할 것입니다. 저는 원과 막대로 타이밍을 주석 처리하는 것을 좋아합니다. 어떤 도구를 사용하든 차트를 그릴 수 있으며, 전체를 이루는 작은 애니메이션들 사이의 상대적인 타이밍만 명확하다면 반드시 정확한 비율일 필요는 없습니다. 게다가, 지속 시간에 대한 레이블을 추가하는 것은 비례적으로 그리려는 노력을 피할 수 있는 쉬운 요령입니다.
여기에 제가 여러 개의 애니메이션으로 타이밍 차트를 설정하는 일반적인 데모가 있습니다:
여기서 Johan Grobler가 만든 Pen colorAndOpacityChange [forked]을 참고할 수 있습니다.
여기서 주목해야 할 중요한 점은 타이밍 차트의 선들이 애니메이션이 시간적으로 배열된 순서에 따라 배치된다는 것입니다. 한 애니메이션 부분이 다음 부분으로 이어지고, 그 다음 부분으로 이어지며, 이런 식으로 계속됩니다. 이는 애니메이션의 각 부분이 시간이 흐르면서 어떻게 함께 실행되고 연쇄되는지를 시각화합니다.
S(yncbase)MIL
SMIL의 큰 부분은 동기화(synchronization)입니다. 이름에서도 알 수 있듯이 말이죠. 그리고 애니메이션이 언제 시작해야 하는지 지정하는 방법이 여러 가지 있습니다 (여기에 브라우저가 무엇을 지원하는지 확인할 테스트 사례가 있습니다). 가장 유용한 방법 중 하나는 syncbase 값을 사용하는 것입니다. 이는 SMIL 태그의 ID 뒤에 .begin 또는 .end를 붙이고, 선택적으로 양수 또는 음수의 오프셋을 추가한 형태입니다.
색상 및 불투명도 변경이 포함된 이전 애니메이션 예시에서 이어서 설명해 보겠습니다. 만약 우리가 불투명도 애니메이션이 색상 애니메이션이 끝나는 시간보다 300밀리초(ms) 전에 시작하기를 원한다면, 산술 연산을 사용할 수 있습니다. 또는 두 번째 애니메이션은 colorChange.end - 300ms라는 syncbase 값을 사용할 수 있습니다. 이렇게 하면 두 애니메이션 사이의 상대적인 타이밍이 명확해집니다.
<!-- 절대 시간에서 시작합니다 -->
<animate
id="colorChange"
...
syncbase 값을 사용하면, 한 애니메이션의 시작점이 다른 애니메이션의 .begin 또는 .end 시점과 상대적인 시간 위치에 놓이게 됩니다. 양수 오프셋은 시작점을 오른쪽(시간적으로 전진)으로 이동시키고, 음수 오프셋은 왼쪽(시간적으로 후퇴)으로 이동시킵니다.
Johan Grobler가 만든 Pen syncbase.end [forked]을 참고해 보세요.
음수 오프셋(negative offsets)을 가진 무언가가 있는데, 이는 문서가 로드되기 전이나 클릭이 발생했을 때의 시간을 지정할 수 있다는 것입니다. 컴퓨터는 미래를 예측할 수 없습니다 (적어도 아직은요). 그들이 할 수 있는 최선은 애니메이션이 미리 시작되도록 미래를 엿본 것처럼 해당 지점으로 점프하는 것입니다. 두 번째 애니메이션은 말하자면 충분한 공간이 있을 때만 처음부터 끝까지 실행됩니다.
Johan Grobler가 만든 Pen syncbase.begin [forked]을 확인해 보세요.
Syncbase 값은 단순히 .end에서 .begin으로 애니메이션을 연결하는 것만 허용하지 않습니다. 주(primary) 애니메이션를 선택하세요. 일반적으로 가장 먼저 떠오르는 애니메이션이 그룹의 가장 좋은 표현입니다. 모든 보조 애니메이션은 begin="primary.begin"으로 설정할 수 있습니다. 저는 강조를 위해 ID #primary만 사용했습니다. 이런 방식으로, 다른 모든 애니메이션은 그 시작점을 기준으로 시작합니다. 이렇게 애니메이션을 중첩하면, 예를 들어 나중에 그룹 전체가 다른 시간에 시작되기를 원할 때 유지보수가 줄어듭니다.
이 아이디어를 실제로 적용하여 로딩 인디케이터(loading indicator) 또는 스피너(spinner)를 만들고, 그런 다음 각 부분이 차지하는 상대적인 타이밍을 변경하는 것이 전체 애니메이션의 효과를 어떻게 바꾸는지 탐구해 보겠습니다:
1단계: 이미지 접근 방식 선택
브라우저는 prefers-reduced-motion 미디어 기능을 광범위하게 지원하며 Val Head가 다른 기사에서 이를 심층적으로 설명했습니다. 무언가를 배치하는 것을 고려할 때 이 사용자 선호도를 반드시 존중해야 합니다. 사실, 이는 협상의 여지가 없는 문제입니다.
SMIL에서 사용자의 prefers-reduced-motion 설정을 준수하는 데에는 다양한 접근 방식이 있습니다. 각각 장단점이 있습니다. 초기에 어떤 것이 사용 사례에 가장 적합할지 평가하면 나중에 부분적인 재작성을 할 필요를 줄일 수 있습니다.
예를 들어, 단순한 <img> 대신 <picture> 요소를 사용하는 것을 고려할 수 있습니다. 왜냐하면 <picture>는 media 속성에서 움직임 감소(reduced motion) 선호도에 대한 폴백(fallback)으로 사용될 수 있는 여러 개의 <source> 요소를 지원하기 때문입니다.
또는 인라인 CSS @media 쿼리를 사용하는 단일 SVG 파일로, 이를 통해 버전을 전환할 수 있습니다. 하지만 이는 다양한 환경에서 문제가 될 수 있는 접근 방식입니다. 그러나 브라우저는 지속적으로 변화하고 있으므로 미래에는 문제가 아닐 수도 있습니다.
CSS background-image를 사용하는 것도 고려해 볼 수 있는데, 이는 미디어 쿼리인 @media (prefers-reduced-motion) 안에 스타일을 감싸서 움직임 감소 선호도에 대한 폴백으로 정적 이미지를 설정할 수 있기 때문입니다.
우리가 활용할 수 있는 옵션은 훨씬 더 많습니다! 예를 들어, SVG의 <view> 요소도 움직임 선호도에 따라 콘텐츠를 전환하는 데 사용될 수 있습니다.
또는 모든 것을 하나로 묶고 싶다면, 파일을 완전히 전환하는 대신 어떤 애니메이션이 시작될지 제어하기 위해 JavaScript의 .matchMedia()와 유용한 SMIL DOM 인터페이스를 사용할 수 있습니다.
이를 위해 저는 움직임(motion)을 피하고 opacity 애니메이션에 집중하여, 이는 일반적으로 문제가 적은 경향이 있기 때문입니다. 이처럼 상호 작용하지 않는 애니메이션의 경우 <img> 태그에 로드할 수 있습니다. 여기에 움직임을 추가할 때는 가장 적절한 버전의 애니메이션을 보여주기 위해 <picture> 경로를 이용할 수 있습니다.
2단계: 그래픽 그리기 (Draw The Graphics)
우리는 고전적인 세 점 스피너(three-dot spinner)의 자체 버전을 만들 것입니다:
Pen StaticDots [forked]을 Johan Grobler가 만든 것을 참고합니다.
SVG 마법사들은 텍스트 편집기에서 모든 것을 직접 할 수 있을지도 모릅니다 [https://www.smashingmagazine.com/2024/09/svg-coding-examples-recipes-writing-vectors-by-hand/]. 만약 마크업이 어떻게 렌더링될지 시각화하는 데 어려움을 겪고 있다면, Inkscape와 같은 그래픽 편집기를 사용하는 것을 추천합니다. 다시 한번, Andy Clarke는 자신의 그림을 최적화하고 구조화하는 과정에 대한 훌륭한 글을 작성했습니다.
참고: Inkscape에는 문제가 있습니다. 레이어(Layers) 창에서 요소의 ID로 예상되는 것을 설정하면, 실제로는 Inkscape가 내부적으로 사용하는 메타데이터 속성 값을 설정하게 됩니다. 실제 요소의 ID를 설정하려면 Inkscape의 개체 속성(object properties) 또는 XML 편집기 창을 사용하십시오. 다른 편집기를 사용할 경우 결과는 다를 수 있습니다. 또한, Inkscape에서는 그림 작업이 완료되면 불필요한 메타데이터를 제거하기 위해 파일을 최적화된 SVG로 저장하는 것을 기억하십시오.
3단계: 애니메이션 개요 작성(Outline The Animation)
좋습니다. 우리는 여전히 opacity 애니메이션 아이디어를 고수할 것입니다. 점들이 나타났다 사라지게 됩니다. 이를 위해 별도의 <animate> 태그를 사용할 것입니다. 총 6개의 태그가 필요합니다.
우리의 명명 규칙은 간단할 것입니다: #fadeIn과 #fadeOut이라고 부르고, 각 쌍의 태그를 구별하기 위해 태그 ID에 각각 Left, Middle, 또는 Right을 접미사로 붙일 것입니다. 자신만의 ID를 만들 때 의미가 통하는 규칙을 따르도록 노력해 보세요.
왼쪽 점에 대한 페이드인(<animate>) 태그:
<animate
id="fadeInLeft"
href="#leftDot"
...
그리고 가운데 점에 대한 페이드아웃(<animate>) 태그:
<animate
id="fadeOutMiddle"
href="#middleDot"
...
4단계: 애니메이션 시간 조정(Time The Animations)
우리가 이 6개의 애니메이션을 시간에 따라 배치할 수 있는 무한한 방법이 있습니다. 몇 가지 예시와 그 타이밍 차트들을 함께 살펴보면서, 부분들의 배열 변경이 전체 애니메이션의 시각적 효과에 어떻게 영향을 미치는지 확인해 봅시다.
선택지를 조금 좁히기 위해, 모든 <animate> 태그는 동일한 dur 값을 사용하고, 어떤 syncbase 값에도 오프셋(offset)이 없을 것입니다.
왼쪽에서 오른쪽으로 읽는 문화권 출신인 사람에게는 같은 패턴으로 화면에 나타나는 점들이 자연스럽게 느껴질 것입니다. 마지막에는 모든 점들이 함께 사라지도록 시작해 봅시다:
Johan Grobler가 만든 Pen dotsVersion1 [forked]을 확인해 보세요.
Syncbase 값들은 점들이 사라질 때 페이드인(fade-in)을 간격을 두고 시작하고 루프를 재시작합니다:
<animate
id="fadeInLeft"
...
...
모든 페이드아웃(fade-out)이 같은 시간에 끝나기 때문에, 어떤 것을 사용하여 루프를 재시작할지는 임의적인 선택입니다. 여기서는 #fadeOutLeft를 주 애니메이션으로 간주하고, syncbase 값 fadeOutLeft.begin을 사용하여 다른 페이드아웃들도 이에 동기화하겠습니다. 나중에 페이드아웃 시간을 이동시키고 싶다면, #fadeOutLeft가 시작하는 시점만 변경하면 됩니다.
<animate
id="fadeOutLeft"
...
...
다른 타이밍 옵션들
그룹으로 페이드아웃(group fade-out) 하는 대신, 페이드인처럼 간격을 두고 진행시킬 수도 있습니다:
AI 자동 생성 콘텐츠
본 콘텐츠는 Smashing Magazine의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기