반응형 SVG 애니메이션 구축 기법
태그: #SVG #반응형디자인 #CSS애니메이션 #웹개발 #최적화
개요
SVG는 확장 가능하지만 화면 크기에 따라 내부 요소의 위치와 크기를 조정하는 것은 쉽지 않다. 이 문서는 <symbol>, <use>, CSS Media Queries를 활용하여 화면 크기에 따라 적응하는 "적응형 SVG"를 구축하는 방법을 설명한다.
문제 정의
SVG의 한계
-
viewBox의 제약: SVG 내부 요소의 위치는 원본 viewBox의 좌표 시스템에 고정되어 있어, 데스크톱과 모바일 간 레이아웃을 쉽게 변경할 수 없다
-
애니메이션 문제: viewBox가 변경되면 요소 위치에 의존하는 애니메이션과 상호작용이 중단된다
-
종횡비 문제: 16:9 비율의 SVG가 작은 화면에서는 임팩트를 잃을 수 있으며, 3:4와 같은 세로 방향이 더 적합할 수 있다
해결 과제
-
작은 화면에는 한 버전 제공 (1080×1440)
-
큰 화면에는 다른 버전 제공 (1920×1080)
-
아트워크를 한 번만 정의 (DRY 원칙)
-
요소의 크기와 위치 조정 가능
시도한 해결 방법들
1. SVG 표시/숨김 방식
<svg id="svg-small" viewBox="0 0 1080 1440">
<!-- ... -->
</svg>
<svg id="svg-large" viewBox="0 0 1920 1080">
<!--... -->
</svg>
#svg-small { display: block; }
#svg-large { display: none; }
@media (min-width: 64rem) {
#svg-small { display: none; }
#svg-mobile { display: block; }
}
문제점: 두 SVG 버전이 모두 로드되어 불필요한 코드를 다운로드하게 됨
2. JavaScript를 이용한 SVG 교체
if (window.matchMedia('(min-width: 64rem)').matches) {
svgContainer.innerHTML = desktopSVG;
} else {
svgContainer.innerHTML = mobileSVG;
}
문제점:
-
JavaScript가 디자인 표시에 필수가 됨
-
두 SVG가 여전히 로드되어 DOM 복잡성과 불필요한 용량 추가
-
두 버전의 아트워크를 유지 관리해야 하므로 작은 업데이트에도 시간이 두 배로 소요됨
해결책: Symbol 라이브러리와 Multiple Uses
Symbol 요소의 이해
<symbol> 요소는 재사용 가능한 SVG 요소를 정의하여 유지 관리성을 높이고 코드 중복을 줄인다. SVG의 컴포넌트와 같아서 한 번 생성하면 필요한 곳에 재사용할 수 있다.
<svg xmlns="http://www.w3.org/2000/svg" style="display: none;">
<symbol id="quick-draw-body" viewBox="0 0 620 700">
<g class="quick-draw-body">[…]</g>
</symbol>
<!-- ... -->
</svg>
<use href="#quick-draw-body" />
핵심 특징:
-
각
<symbol>은 자체 viewBox를 가져야 하며, 이는 내부 좌표 시스템을 정의함 -
<use>요소를 사용하여 동일한 심볼을 다른 위치나 크기로, 심지어 다른 SVG에서도 여러 번 삽입 가능 -
코드의 일관성을 유지하고 경량화할 수 있음
구현 단계
1단계: 개별 viewBox를 위한 내보내기
일반적인 프로세스와의 차이점:
-
일반적으로는 모든 요소를 동일한 viewBox 크기로 내보냄
-
심볼을 생성할 때는 각 요소가 자체 특정 viewBox를 가져야 함
Sketch에서 내보내기:
-
각 요소를 개별 크기의 SVG로 내보냄
-
예: Quick Draw McGraw의 모자는 294×182의 viewBox 크기를 가짐
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 294 182">
<!-- ... -->
</svg>
SVG를 Symbol로 변환:
<svg xmlns="http://www.w3.org/2000/svg" style="display: none;">
<symbol id="quick-draw-hat" viewBox="0 0 294 182">
<g class="quick-draw-hat">[…]</g>
</symbol>
</svg>
모든 나머지 요소에 대해 이 과정을 반복하면, 심볼을 업데이트할 때 사용된 모든 인스턴스에 자동으로 변경사항이 적용됨
2단계: 여러 SVG에서 Symbol 사용
두 개의 SVG 생성 (작은 화면용과 큰 화면용):
<svg class="svg-small" viewBox="0 0 1080 1440">
<!-- ... -->
</svg>
<svg class="svg-large" viewBox="0 0 1920 1080">
<!-- ... -->
</svg>
양쪽에 심볼 링크 삽입:
<svg class="svg-small" viewBox="0 0 1080 1440">
<use href="#quick-draw-hat" />
</svg>
<svg class="svg-large" viewBox="0 0 1920 1080">
<use href="#quick-draw-hat" />
</svg>
3단계: Symbol 위치 지정
<use>를 사용하여 레이아웃에 심볼을 배치한 후, 다음 단계는 위치를 지정하는 것이다. 심볼은 <g> 그룹처럼 동작하므로 width, height, transform과 같은 속성을 사용하여 크기와 이동이 가능하다:
<svg class="svg-small" viewBox="0 0 1080 1440">
<use href="#quick-draw-hat" width="294" height="182" transform="translate(-30,610)"/>
</svg>
<svg class="svg-large" viewBox="0 0 1920 1080">
<use href="#quick-draw-hat" width="294" height="182" transform="translate(350,270)"/>
</svg>
장점:
-
SVG 내부 요소를 재배치하지 않고
<use>참조를 이동 -
내부 레이아웃이 깨끗하게 유지되고 파일 크기가 작게 유지됨
-
아트워크를 복제하지 않으므로 브라우저가 한 번만 로드
-
대역폭이 줄어들고 페이지 렌더링 속도가 빨라짐
-
항상 동일한 심볼을 참조하므로 화면 크기에 관계없이 외관이 일관되게 유지됨
4단계: Use 요소 애니메이션
제약사항: <use> 요소 자체는 애니메이션할 수 있지만, <symbol> 내부의 요소는 애니메이션할 수 없다. <use>는 쉽게 타겟팅할 수 없는 shadow DOM 클론을 생성하기 때문이다.
해결 방법: 각 <symbol> 내부에서 애니메이션하고 싶은 부분 주위에 <g> 요소를 추가:
<symbol id="quick-draw-hat" viewBox="0 0 294 182">
<g class="quick-draw-hat">
<!-- ... -->
</g>
</symbol>
속성 서브스트링 선택자를 사용하여 애니메이션 적용:
use[href="#quick-draw-hat"] {
animation-delay: 0.5s;
animation-direction: alternate;
animation-duration: 1s;
animation-iteration-count: infinite;
animation-name: hat-rock;
animation-timing-function: ease-in-out;
transform-origin: center bottom;
}
@keyframes hat-rock {
from { transform: rotate(-2deg); }
to { transform: rotate(2deg); }
}
5단계: Media Queries로 표시 제어
두 개의 보이는 SVG를 생성한 후, 어떤 화면 크기에서 어떤 버전을 표시할지 결정한다.
기본적으로 작은 화면 SVG 표시:
.svg-small { display: block; }
.svg-large { display: none; }
64rem 이상에서 큰 화면 SVG로 전환:
@media (min-width: 64rem) {
.svg-small { display: none; }
.svg-large { display: block; }
}
이점:
-
한 번에 하나의 SVG만 표시되어 레이아웃이 간단하고 DOM이 불필요한 잡동사니로부터 자유로움
-
두 보이는 SVG가 동일한 숨겨진
<symbol>라이브러리를 참조하므로, 브라우저는<use>요소가 두 레이아웃에 얼마나 많이 나타나든 아트워크를 한 번만 다운로드함
실용적인 팁
Symbol 사용 시 주의사항
-
각
<symbol>은 반드시 자체 viewBox를 가져야 함 -
Sketch와 같은 앱에서 SVG 요소를 내보낼 때 특별한 주의가 필요함
-
개별 부분을 움직이고 싶다면 각각을 자신만의 심볼로 만들고 각
<use>를 애니메이션해야 함
코드 예제: 완전한 구조
<!-- 숨겨진 Symbol 라이브러리 -->
<svg xmlns="http://www.w3.org/2000/svg" style="display: none;">
<symbol id="quick-draw-hat" viewBox="0 0 294 182">
<g class="quick-draw-hat">[…]</g>
</symbol>
<!-- 더 많은 심볼들... -->
</svg>
<!-- 작은 화면용 SVG -->
<svg class="svg-small" viewBox="0 0 1080 1440">
<use href="#quick-draw-hat" width="294" height="182" transform="translate(-30,610)"/>
</svg>
<!-- 큰 화면용 SVG -->
<svg class="svg-large" viewBox="0 0 1920 1080">
<use href="#quick-draw-hat" width="294" height="182" transform="translate(350,270)"/>
</svg>
이 방법의 장점
-
콘텐츠 중복 없이 요소를 재배치하는 적응형 SVG 구축 가능
-
추가 자산 로드 없음: 브라우저가 한 번만 다운로드
-
JavaScript 의존성 없음: 순수 CSS와 HTML로 해결
-
빠르고 유연함: CSS가 레이아웃 전환을 처리
-
유지 관리 용이: 각 그래픽을 숨겨진 심볼 라이브러리에 한 번만 정의하면 됨
-
일관성 보장: 동일한 심볼을 참조하므로 화면 크기와 관계없이 외관이 일관됨
결론
<symbol>, <use>, CSS Media Queries, 그리고 특정 transform을 결합하여 콘텐츠를 복제하거나, 추가 자산을 로드하거나, JavaScript에 의존하지 않고도 요소를 재배치하는 적응형 SVG를 구축할 수 있다. 각 그래픽을 숨겨진 심볼 라이브러리에 한 번만 정의하면 된다. 그런 다음 필요에 따라 여러 보이는 SVG 내부에서 해당 그래픽을 재사용할 수 있다. CSS가 레이아웃 전환을 처리하므로 결과는 빠르고 유연하다.
웹에서 가장 강력한 기술 중 일부는 큰 프레임워크나 복잡한 도구가 필요하지 않다는 것을 상기시킨다 - 약간의 SVG 노하우와 기본의 영리한 사용만 있으면 된다.