398 lines
8.4 KiB
Markdown
398 lines
8.4 KiB
Markdown
---
|
|
id: frontend-svg-patterns
|
|
title: SVG — Scaling / Animation / Sprite / React
|
|
category: Coding
|
|
status: draft
|
|
source_trust_level: B
|
|
verification_status: conceptual
|
|
created_at: 2026-05-09
|
|
updated_at: 2026-05-09
|
|
tags: [frontend, svg, vector, vibe-coding]
|
|
tech_stack: { language: "SVG / CSS / TS", applicable_to: ["Frontend"] }
|
|
applied_in: []
|
|
aliases: [SVG, vector graphics, SVG sprite, viewBox, lucide-react, animation]
|
|
---
|
|
|
|
# SVG Patterns
|
|
|
|
> Vector graphics. **Scalable, small, scriptable, themeable**. Icon / illustration / chart / animation. PNG 보다 거의 항상 좋음 (단순 graphic).
|
|
|
|
## 📖 핵심 개념
|
|
- viewBox: coordinate system.
|
|
- preserveAspectRatio: scaling.
|
|
- currentColor: 부모 색 따름.
|
|
- Sprite: 여러 icon 한 file.
|
|
|
|
## 💻 코드 패턴
|
|
|
|
### 기본 SVG
|
|
```html
|
|
<svg width="100" height="100" viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg">
|
|
<circle cx="50" cy="50" r="40" fill="hotpink" />
|
|
<text x="50" y="55" text-anchor="middle" fill="white">Hi</text>
|
|
</svg>
|
|
```
|
|
|
|
### viewBox 가 핵심
|
|
```html
|
|
<!-- 항상 viewBox 사용 -->
|
|
<svg viewBox="0 0 24 24">
|
|
<!-- 0,0 부터 24x24 coordinate -->
|
|
</svg>
|
|
|
|
<!-- width / height 안 — CSS 로 -->
|
|
<svg viewBox="0 0 24 24" style="width: 24px; height: 24px;">
|
|
```
|
|
|
|
→ Scalable. CSS 로 size 제어.
|
|
|
|
### currentColor (theme 친화)
|
|
```html
|
|
<svg viewBox="0 0 24 24" fill="currentColor">
|
|
<path d="M5 13l4 4L19 7" />
|
|
</svg>
|
|
```
|
|
|
|
```css
|
|
.icon { color: blue; } /* SVG fill 도 blue */
|
|
.icon:hover { color: red; } /* 자동 hover */
|
|
```
|
|
|
|
→ 부모 color 따름. Theme / dark mode 자동.
|
|
|
|
### Inline SVG (modern)
|
|
```tsx
|
|
function CheckIcon() {
|
|
return (
|
|
<svg viewBox="0 0 24 24" width="24" height="24" fill="currentColor">
|
|
<path d="M5 13l4 4L19 7" stroke="currentColor" strokeWidth="2" fill="none" strokeLinecap="round" strokeLinejoin="round" />
|
|
</svg>
|
|
);
|
|
}
|
|
```
|
|
|
|
### lucide-react (icon library)
|
|
```bash
|
|
yarn add lucide-react
|
|
```
|
|
|
|
```tsx
|
|
import { Heart, Home, Settings, ChevronRight } from 'lucide-react';
|
|
|
|
<Heart size={24} className="text-red-500" />
|
|
<Home className="size-6 text-gray-600" />
|
|
```
|
|
|
|
→ Tree-shakable. 큰 set.
|
|
|
|
### Icon system (자체)
|
|
```tsx
|
|
// icons/index.ts
|
|
export { default as CheckIcon } from './check.svg';
|
|
export { default as CloseIcon } from './close.svg';
|
|
// ...
|
|
|
|
// 사용
|
|
import { CheckIcon } from '@/icons';
|
|
<CheckIcon className="size-4" />
|
|
```
|
|
|
|
```ts
|
|
// vite.config.ts — SVG → React
|
|
import svgr from 'vite-plugin-svgr';
|
|
plugins: [svgr()];
|
|
```
|
|
|
|
→ SVG file → React component 자동.
|
|
|
|
### SVG sprite (1 fetch, 많은 icon)
|
|
```html
|
|
<!-- icons.svg -->
|
|
<svg xmlns="http://www.w3.org/2000/svg" style="display:none">
|
|
<symbol id="check" viewBox="0 0 24 24">
|
|
<path d="M5 13l4 4L19 7" />
|
|
</symbol>
|
|
<symbol id="close" viewBox="0 0 24 24">
|
|
<path d="M6 6L18 18M6 18L18 6" />
|
|
</symbol>
|
|
</svg>
|
|
|
|
<!-- Use -->
|
|
<svg width="24" height="24"><use href="/icons.svg#check" /></svg>
|
|
```
|
|
|
|
→ 한 fetch — cache. 100 icon 도 OK.
|
|
|
|
### Stroke-based icon
|
|
```html
|
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
<circle cx="12" cy="12" r="10" />
|
|
<line x1="12" y1="8" x2="12" y2="12" />
|
|
<circle cx="12" cy="16" r="0.5" fill="currentColor" />
|
|
</svg>
|
|
```
|
|
|
|
→ Lucide / Tabler / Phosphor 의 style.
|
|
|
|
### Filled icon
|
|
```html
|
|
<svg viewBox="0 0 24 24" fill="currentColor">
|
|
<path d="M12 21l-1.45-1.32C5.4 15.36 2 12.28 2 8.5 2 5.42 4.42 3 7.5 3c1.74 0 3.41.81 4.5 2.09C13.09 3.81 14.76 3 16.5 3 19.58 3 22 5.42 22 8.5c0 3.78-3.4 6.86-8.55 11.18L12 21z" />
|
|
</svg>
|
|
```
|
|
|
|
→ Solid icon (Material).
|
|
|
|
### CSS animation
|
|
```html
|
|
<svg viewBox="0 0 24 24">
|
|
<circle class="loader" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="2" fill="none" />
|
|
</svg>
|
|
```
|
|
|
|
```css
|
|
.loader {
|
|
stroke-dasharray: 60;
|
|
stroke-dashoffset: 0;
|
|
animation: loading 1s linear infinite;
|
|
}
|
|
|
|
@keyframes loading {
|
|
to { stroke-dashoffset: 60; }
|
|
}
|
|
```
|
|
|
|
→ SVG path = stroke-dash.
|
|
|
|
### SMIL animation (built-in)
|
|
```html
|
|
<svg viewBox="0 0 100 100">
|
|
<circle cx="50" cy="50" r="40" fill="red">
|
|
<animate attributeName="r" from="40" to="50" dur="1s" repeatCount="indefinite" />
|
|
</circle>
|
|
</svg>
|
|
```
|
|
|
|
→ JS 없이 animation. Browser 지원 OK.
|
|
|
|
### Path morphing (SVGator / GSAP / Lottie)
|
|
```ts
|
|
// Path A → Path B
|
|
gsap.to('#shape', {
|
|
attr: { d: 'M10,10 L90,90' },
|
|
duration: 1,
|
|
});
|
|
```
|
|
|
|
### Logo / illustration
|
|
```
|
|
Vector design tools:
|
|
- Figma → SVG export
|
|
- Illustrator
|
|
- Inkscape (OSS)
|
|
|
|
→ Path / shape 직접 export.
|
|
```
|
|
|
|
### Optimization
|
|
```bash
|
|
# SVGO
|
|
npx svgo input.svg
|
|
npx svgo *.svg
|
|
|
|
# 또는 SVGOMG (web)
|
|
```
|
|
|
|
→ 50% 작아지는 보통 — comments / metadata 제거.
|
|
|
|
### React + SVG
|
|
```tsx
|
|
// Inline (small icons)
|
|
<svg viewBox="0 0 24 24"><path d="..." /></svg>
|
|
|
|
// React component (vite-plugin-svgr)
|
|
import Icon from './icon.svg?react';
|
|
<Icon className="size-4" />
|
|
|
|
// img tag (큰 / 변동 X)
|
|
<img src="/logo.svg" alt="Logo" />
|
|
|
|
// 또는 url
|
|
import logoUrl from './logo.svg';
|
|
<img src={logoUrl} alt="Logo" />
|
|
```
|
|
|
|
→ Inline = themeable. img = cacheable.
|
|
|
|
### Charts (SVG-based)
|
|
```ts
|
|
// d3 / visx — SVG 직접
|
|
const path = d3.line()(data.map(d => [d.x, d.y]));
|
|
return <path d={path} stroke="blue" fill="none" />;
|
|
```
|
|
|
|
→ SVG = chart 의 자연.
|
|
|
|
### Patterns / gradients
|
|
```html
|
|
<svg viewBox="0 0 200 100">
|
|
<defs>
|
|
<linearGradient id="grad" x1="0%" y1="0%" x2="100%" y2="0%">
|
|
<stop offset="0%" stop-color="red" />
|
|
<stop offset="100%" stop-color="blue" />
|
|
</linearGradient>
|
|
|
|
<pattern id="dots" width="10" height="10" patternUnits="userSpaceOnUse">
|
|
<circle cx="5" cy="5" r="2" fill="black" />
|
|
</pattern>
|
|
</defs>
|
|
|
|
<rect width="200" height="50" fill="url(#grad)" />
|
|
<rect y="50" width="200" height="50" fill="url(#dots)" />
|
|
</svg>
|
|
```
|
|
|
|
### Filters
|
|
```html
|
|
<svg>
|
|
<defs>
|
|
<filter id="blur">
|
|
<feGaussianBlur stdDeviation="3" />
|
|
</filter>
|
|
|
|
<filter id="shadow">
|
|
<feDropShadow dx="2" dy="2" stdDeviation="3" />
|
|
</filter>
|
|
</defs>
|
|
|
|
<text filter="url(#shadow)">Shadow</text>
|
|
</svg>
|
|
```
|
|
|
|
### A11y
|
|
```html
|
|
<svg role="img" aria-labelledby="title">
|
|
<title id="title">Heart icon</title>
|
|
<path d="..." />
|
|
</svg>
|
|
|
|
<!-- 또는 decorative -->
|
|
<svg aria-hidden="true">...</svg>
|
|
```
|
|
|
|
→ Screen reader 친화.
|
|
|
|
### 1-line / Tailwind utility
|
|
```html
|
|
<svg class="size-6 text-red-500">...</svg>
|
|
```
|
|
|
|
→ Tailwind 가 SVG 자연.
|
|
|
|
### MathML / chart 기타
|
|
```
|
|
SVG: 자유 형식 vector.
|
|
Canvas: pixel — 큰 rendering.
|
|
WebGL: 3D / GPU.
|
|
|
|
→ Static / scalable / theme-friendly = SVG.
|
|
```
|
|
|
|
### Use cases
|
|
```
|
|
- Icon (Lucide / Heroicons)
|
|
- Logo
|
|
- Chart (D3 / Visx)
|
|
- Illustration
|
|
- Loading spinner
|
|
- Diagram (Mermaid / draw.io)
|
|
- Map / floor plan
|
|
```
|
|
|
|
### Bundle size
|
|
```
|
|
Inline SVG icon: ~500 bytes
|
|
PNG @1x / @2x / @3x: 5-50 KB
|
|
|
|
→ Icon = SVG 거의 항상.
|
|
```
|
|
|
|
### Generate at build
|
|
```ts
|
|
// 자동 component generation
|
|
import { generateSvgComponents } from 'svg-to-jsx';
|
|
generateSvgComponents('./icons/', './src/components/icons/');
|
|
```
|
|
|
|
### Optimization (icon font 보다)
|
|
```
|
|
Icon font:
|
|
+ 1 file load
|
|
- A11y 약함
|
|
- Fixed color 어려움
|
|
- CSS 만 styling
|
|
|
|
SVG sprite / inline:
|
|
+ A11y OK
|
|
+ 색 / size 자유
|
|
+ Animation 가능
|
|
+ Better fallback
|
|
|
|
→ 2024+ = SVG 가 더 좋음.
|
|
```
|
|
|
|
### Common 사이즈
|
|
```
|
|
size-4 (16px): inline text icon
|
|
size-5 (20px): button icon
|
|
size-6 (24px): main icon
|
|
size-8 (32px): large
|
|
size-12 (48px): hero
|
|
```
|
|
|
|
### Colored icons (multi-color)
|
|
```svg
|
|
<svg viewBox="0 0 24 24">
|
|
<path d="..." fill="red" />
|
|
<path d="..." fill="blue" />
|
|
</svg>
|
|
```
|
|
|
|
→ Theme 어려움. CSS variable 사용:
|
|
|
|
```svg
|
|
<svg viewBox="0 0 24 24" style="--primary: red; --secondary: blue;">
|
|
<path d="..." fill="var(--primary)" />
|
|
<path d="..." fill="var(--secondary)" />
|
|
</svg>
|
|
```
|
|
|
|
## 🤔 의사결정 기준
|
|
| 사용 | 추천 |
|
|
|---|---|
|
|
| Icon system | Lucide / 자체 SVG sprite |
|
|
| Logo | Inline SVG |
|
|
| Chart | SVG (D3 / Visx) |
|
|
| Illustration | SVG |
|
|
| Photo | PNG / WebP / AVIF |
|
|
| 3D | WebGL / Three.js |
|
|
|
|
## ❌ 안티패턴
|
|
- **viewBox 없음**: 안 scale.
|
|
- **Hard-coded color**: theme X. currentColor.
|
|
- **PNG icon (multi-resolution)**: 매 size 별 file. SVG 하나면.
|
|
- **Inline SVG 큰 (100+ path)**: HTML bloat. external file.
|
|
- **No optimization (raw export)**: 50% 큰.
|
|
- **A11y 무시**: title / aria-label.
|
|
|
|
## 🤖 LLM 활용 힌트
|
|
- viewBox + currentColor + sprite.
|
|
- Lucide / Heroicons / Tabler 가 modern.
|
|
- SVGO 자동 optimize.
|
|
- vite-plugin-svgr = React component.
|
|
|
|
## 🔗 관련 문서
|
|
- [[Frontend_Image_Optimization]]
|
|
- [[React_Charts_Library_Comparison]]
|
|
- [[Frontend_A11y_Testing]]
|