Smashing Animations Part 6: <use> CSS 사용자 정의 속성을 활용한 멋진 SVG | Andy Clarke


svg-애니메이션 css-커스텀-속성 #웹-디자인 #인터랙티브 #성능-최적화

핵심 요약

첫 번째 요약 - 주요 개념 및 필요성

SVG에서 <symbol><use> 요소를 사용할 때 Shadow DOM 경계가 외부 CSS 스타일링을 막아 애니메이션과 동적 변경이 불가능한 문제가 발생합니다. 이 문제를 **CSS 커스텀 속성(CSS Custom Properties)**으로 해결할 수 있습니다. 커스텀 속성은 Shadow DOM 경계를 통과할 수 있어 각 <use> 인스턴스에 고유한 색상, 움직임, 스케일을 동적으로 전달하고 제어할 수 있게 해줍니다. 이를 통해 단일 SVG 정의를 재사용하면서도 개별 맞춤형 애니메이션을 구현하여 코드의 가벼움과 유지보수성을 극대화할 수 있습니다.


두 번째 요약 - 상세 해설

1. Shadow DOM 경계 이해하기

문제점

SVG에서 <symbol> 내부의 콘텐츠를 <use> 요소로 참조하면 브라우저는 Shadow DOM에 복사본을 생성합니다. 이로 인해:

  • <use> 인스턴스는 자체 캡슐화된 복사본이 됨
  • 외부 CSS가 Shadow DOM 내부 요소에 영향을 미치지 못함
  • CSS Cascade가 <use> 경계에서 중단됨

구체적 예시:

/* 일반 상황에서는 작동 */
.tapping {
  animation: tapping 1s ease-in-out infinite;
}

하지만 <use> 요소에 tapping 클래스를 적용해도 애니메이션이 작동하지 않습니다.

재사용성 문제

문자 애니메이션이 필요한 경우, 전통적으로는:

  • 각 인스턴스마다 SVG를 복제해야 함
  • 코드 중복 증가
  • 유지보수 어려워짐

2. CSS 커스텀 속성으로 해결하기

핵심 개념

CSS 커스텀 속성은 Shadow DOM 경계를 통과할 수 있습니다.

외부에서 정의한 커스텀 속성값이 <symbol> 내부 요소의 var() 함수로 참조되면, 값이 자동으로 캐스케이드됩니다.

구현 방식

Step 1: 심볼 내부에 커스텀 속성 정의

<symbol id="outlaw-1">
  <g class="outlaw-1-foot" style="
    transform-origin: bottom right; 
    transform-box: fill-box; 
    transform: rotate(var(--foot-rotate));">
    <!-- ... -->
  </g>
</symbol>

Step 2: 커스텀 속성을 사용한 애니메이션 정의

@keyframes tapping {
  0%, 60%, 100% { --foot-rotate: 0deg; }
  20% { --foot-rotate: -5deg; }
  40% { --foot-rotate: 2deg; }
}

use[data-outlaw="1"] {
  --foot-rotate: 0deg;
  animation: tapping 1s ease-in-out infinite;
}

Step 3: 각 인스턴스에 고유한 값 전달

use[data-outlaw="1"] { --foot-rotate: 0deg; }
use[data-outlaw="2"] { --foot-rotate: 0deg; }

3. 다중 값 전달로 심볼 커스터마이징

여러 속성 동시 제어

한 개의 심볼에 여러 커스텀 속성을 정의할 수 있습니다:

<g class="eyelids" style="
  fill: var(--eyelids-colour, #f7bea1);
  opacity: var(--eyelids-opacity, 1);
  transform: var(--eyelids-scale, 0);"
>

인스턴스별 맞춤 설정

use[data-outlaw="1"] {
  --eyelids-colour: #f7bea1; 
  --eyelids-opacity: 1;
}

use[data-outlaw="2"] {
  --eyelids-colour: #ba7e5e; 
  --eyelids-opacity: 0;
}

브라우저 지원: 현대 브라우저에서 완벽하게 지원됩니다.

4. 다색상 아이콘 시스템

사용 사례

동일한 아이콘 모양을 테마에 따라 다른 색상으로 표시하려면:

Step 1: 심볼 정의

<symbol id="icon-bluesky">
  <path fill="var(--icon-fill, currentColor)" d="..." />
</symbol>

Step 2: 위치별 다른 색상 적용

<header>
  <svg xmlns="http://www.w3.org/2000/svg">
    <use href="#icon-bluesky" style="--icon-fill: #2d373b;" />
  </svg>
</header>

<footer>
  <svg xmlns="http://www.w3.org/2000/svg">
    <use href="#icon-bluesky" style="--icon-fill: #590d1a;" />
  </svg>
</footer>

장점:

  • SVG 파일 크기 감소
  • 단일 정의로 무제한 변형
  • 동적 색상 변경 가능

5. 데이터 시각화와 CSS 커스텀 속성

개념

커스텀 속성을 사용하여 데이터 값을 시각적 속성(길이, 크기, 색상)에 바인딩할 수 있습니다.

실제 사례: 서부영화 보안관 통계 인포그래픽

세 가지 심볼 정의:

<symbol id="career-bar">
  <rect
    height="10"
    width="var(--career-length, 100)" 
    fill="var(--career-colour, #f7bea1)"
  />
</symbol>

<symbol id="arrests-badge">
  <path 
    fill="var(--arrest-color, #d0985f)" 
    transform="scale(var(--arrest-scale, 1))"
  />
</symbol>

<symbol id="kills-icon">
  <path fill="var(--kill-colour, #769099)" />
</symbol>

커스텀 속성 역할:

  • --career-length: 경력 기간 표시 막대 너비 조정
  • --career-colour: 경력 기간 막대 색상 변경
  • --arrest-scale: 체포 배지 크기 제어
  • --kill-colour: 킬 아이콘 색상 지정

각 보안관 프로필 구성:

<g id="wyatt-earp">
  <use href="#career-bar" style="--career-length: 400; --career-color: #769099;"/>
  <use href="#arrests-badge" style="--arrest-scale: 2;" />
  <use href="#kills-icon" style="--kill-color: #769099;" />
</g>

<g id="pat-garrett">
  <use href="#career-bar" style="--career-length: 300; --career-color: #f7bea1;"/>
  <use href="#arrests-badge" style="--arrest-scale: 2;" />
  <use href="#kills-icon" style="--kill-color: #f7bea1;" />
</g>

호버 애니메이션 추가:

@keyframes pulse {
  0%, 100% { --arrest-scale: 1; }
  50% { --arrest-scale: 1.2; }
}

use[href="#arrests-badge"]:hover {
  animation: pulse 1s ease-in-out infinite;
}

6. 주변 애니메이션(Ambient Animations) 구현

목표

재사용 가능한 캐릭터에 생생한 미묘한 움직임을 더하기:

  • 눈 깜빡임
  • 발 두드리기
  • 콧수염 떨림

6.1 깜빡임 애니메이션

심볼 설정:

<symbol id="outlaw-1" viewBox="0 0 712 2552">
  <g class="eyelids" style="opacity: var(--eyelids-opacity, 1);">
    <!-- ... -->
  </g>
</symbol>

애니메이션 정의:

@keyframes blink {
  0%, 92% { --eyelids-opacity: 0; }
  93%, 94% { --eyelids-opacity: 1; }
  95%, 97% { --eyelids-opacity: 0.1; }
  98%, 100% { --eyelids-opacity: 0; }
}

모든 캐릭터에 적용:

use[data-outlaw] {
  --blink-duration: 4s;
  --eyelids-opacity: 1;
  animation: blink var(--blink-duration) infinite var(--blink-delay);
}

개별 깜빡임 시간 오프셋:

use[data-outlaw="1"] { --blink-delay: 1s; }
use[data-outlaw="2"] { --blink-delay: 2s; }
use[data-outlaw="7"] { --blink-delay: 3s; }

6.2 발 두드리기 애니메이션

심볼 설정:

<symbol id="outlaw-1" viewBox="0 0 712 2552">
  <g class="outlaw-1-foot" style="
    transform-origin: bottom right; 
    transform-box: fill-box; 
    transform: rotate(var(--foot-rotate));">
  </g>
</symbol>

애니메이션:

@keyframes tapping {
  0%, 60%, 100% { --foot-rotate: 0deg; }
  20% { --foot-rotate: -5deg; }
  40% { --foot-rotate: 2deg; }
}

use[data-outlaw] {
  --foot-rotate: 0deg;
  animation: tapping 1s ease-in-out infinite;
}

6.3 콧수염 떨림 애니메이션

심볼 설정:

<symbol id="outlaw-1" viewBox="0 0 712 2552">
  <g class="outlaw-1-tashe" style="
    transform: translateX(var(--jiggle-x, 0px));"
  >
    <!-- ... -->
  </g>
</symbol>

애니메이션:

@keyframes jiggle {
  0%, 100% { --jiggle-x: 0px; }
  20% { --jiggle-x: -3px; }
  40% { --jiggle-x: 2px; }
  60% { --jiggle-x: -1px; }
  80% { --jiggle-x: 4px; }
}

통합 적용:

use[data-outlaw] {
  --blink-duration: 4s;
  --eyelids-opacity: 1;
  --foot-rotate: 0deg;
  --jiggle-x: 0px;
  animation: 
    blink var(--blink-duration) infinite var(--blink-delay),
    jiggle 1s ease-in-out infinite,
    tapping 1s ease-in-out infinite;
}

결과: 마크업을 최소화하면서 여러 애니메이션을 조화롭게 조율하여 캐릭터를 생생하게 표현

7. 주의사항 및 해결책

일반적인 함정

1. var() 함수 누락

  • 문제: CSS 커스텀 속성이 var()로 참조되지 않으면 작동 안 함
  • 해결: fill: var(--my-color)처럼 명시적으로 사용

2. 폴백 값 누락

  • 문제: 커스텀 속성이 정의되지 않으면 요소가 렌더링되지 않을 수 있음
  • 해결: opacity: var(--eyelids-opacity, 1); 형식으로 기본값 포함

3. 인라인 스타일 우선순위

  • 문제: 인라인 스타일이 외부 CSS를 덮어씀
  • 해결: CSS 캐스케이드 규칙을 이해하고 적절히 순서 조정

디버깅 팁

DevTools 활용:

  1. <use> 인스턴스 선택
  2. Computed Styles 패널에서 활성 커스텀 속성 확인
  3. 예상된 값이 전달되었는지 검증

8. 결론

요점:

  • <symbol><use> 요소는 우아하지만 때때로 까다로움
  • Shadow DOM 경계가 문제가 되지만 CSS 커스텀 속성이 해결책 제공
  • 커스텀 속성을 통해 색상, 움직임, 개성을 Shadow DOM 경계 너머로 전달 가능
  • 더 깨끗하고 가벼우며 즐거운 애니메이션 구현 가능

실용적 팁

즉시 적용 가능한 팁

  1. 모든 커스텀 속성에 폴백 값 설정하기

    • var(--property, fallback-value) 형식 필수
    • 예: fill="var(--icon-fill, currentColor)"
  2. 인라인 스타일로 커스텀 속성 전달

    <use href="#icon" style="--icon-fill: #2d373b;" />
    
  3. data 속성과 함께 사용하기

    use[data-outlaw="1"] { --blink-delay: 1s; }
    
  4. 여러 애니메이션 조율하기

    • 공통 --duration 속성으로 통합 제어
    • animation 단축 속성으로 여러 애니메이션 연쇄

주의사항

  • CSS 커스텀 속성은 브라우저 지원이 광범위하지만, 매우 오래된 브라우저에서 미지원
  • 인라인 스타일의 스타일시트 우선순위 차이 이해 필요
  • SVG 요소의 style 속성에 정의된 커스텀 속성이 외부 CSS보다 우선

핵심 코드 예제

기본 패턴: Shadow DOM 통과

<!-- 심볼 정의 -->
<symbol id="my-icon">
  <path fill="var(--icon-fill, black)" d="..." />
</symbol>

<!-- 인스턴스 사용 -->
<use href="#my-icon" style="--icon-fill: red;" />

애니메이션 패턴

/* 커스텀 속성을 사용한 애니메이션 정의 */
@keyframes move {
  0% { --x: 0; }
  100% { --x: 100px; }
}

/* 적용 */
element {
  --x: 0;
  animation: move 1s;
  transform: translateX(var(--x));
}

데이터 바인딩 패턴

<use href="#bar" style="--length: 300px; --color: #ff6b6b;" />
#bar {
  width: var(--length, 100px);
  background: var(--color, gray);
}

참고 자료 및 학습 리소스

Smashing Magazine 관련 기사

웹 표준 및 문서

참고 프로젝트


태그 요약

주요 개념: SVG 재사용성, Shadow DOM 해결, CSS 커스텀 속성 활용

적용 분야: 인터랙티브 웹 디자인, 데이터 시각화, 가벼운 애니메이션

핵심 기술: <symbol>, <use>, @keyframes, CSS 변수, CSS 캐스케이드

1개의 좋아요