스매싱 애니메이션 파트 5: <symbol>, <use>, CSS 미디어 쿼리를 활용한 적응형 SVG 구축 | Andy Clarke


반응형 SVG 애니메이션 구축 기법

태그: #SVG #반응형디자인 #CSS애니메이션 #웹개발 #최적화


개요

SVG는 확장 가능하지만 화면 크기에 따라 내부 요소의 위치와 크기를 조정하는 것은 쉽지 않다. 이 문서는 <symbol>, <use>, CSS Media Queries를 활용하여 화면 크기에 따라 적응하는 "적응형 SVG"를 구축하는 방법을 설명한다.


문제 정의

SVG의 한계

  • viewBox의 제약: SVG 내부 요소의 위치는 원본 viewBox의 좌표 시스템에 고정되어 있어, 데스크톱과 모바일 간 레이아웃을 쉽게 변경할 수 없다

  • 애니메이션 문제: viewBox가 변경되면 요소 위치에 의존하는 애니메이션과 상호작용이 중단된다

  • 종횡비 문제: 16:9 비율의 SVG가 작은 화면에서는 임팩트를 잃을 수 있으며, 3:4와 같은 세로 방향이 더 적합할 수 있다

해결 과제

  1. 작은 화면에는 한 버전 제공 (1080×1440)

  2. 큰 화면에는 다른 버전 제공 (1920×1080)

  3. 아트워크를 한 번만 정의 (DRY 원칙)

  4. 요소의 크기와 위치 조정 가능


시도한 해결 방법들

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>


이 방법의 장점

  1. 콘텐츠 중복 없이 요소를 재배치하는 적응형 SVG 구축 가능

  2. 추가 자산 로드 없음: 브라우저가 한 번만 다운로드

  3. JavaScript 의존성 없음: 순수 CSS와 HTML로 해결

  4. 빠르고 유연함: CSS가 레이아웃 전환을 처리

  5. 유지 관리 용이: 각 그래픽을 숨겨진 심볼 라이브러리에 한 번만 정의하면 됨

  6. 일관성 보장: 동일한 심볼을 참조하므로 화면 크기와 관계없이 외관이 일관됨


결론

<symbol>, <use>, CSS Media Queries, 그리고 특정 transform을 결합하여 콘텐츠를 복제하거나, 추가 자산을 로드하거나, JavaScript에 의존하지 않고도 요소를 재배치하는 적응형 SVG를 구축할 수 있다. 각 그래픽을 숨겨진 심볼 라이브러리에 한 번만 정의하면 된다. 그런 다음 필요에 따라 여러 보이는 SVG 내부에서 해당 그래픽을 재사용할 수 있다. CSS가 레이아웃 전환을 처리하므로 결과는 빠르고 유연하다.

웹에서 가장 강력한 기술 중 일부는 큰 프레임워크나 복잡한 도구가 필요하지 않다는 것을 상기시킨다 - 약간의 SVG 노하우와 기본의 영리한 사용만 있으면 된다.

1개의 좋아요