
SVG Scripting
SVG(Scalable Vector Graphics)는 단순히 정적인 그래픽이 아니라 JavaScript를 사용하여 동적으로 조작할 수 있는 강력한 웹 기술이다. JavaScript로 SVG 요소를 생성, 수정, 애니메이션화하여 인터랙티브한 웹 경험을 만들 수 있다.
JavaScript로 SVG 요소 조작하기
SVG는 HTML과 마찬가지로 DOM(Document Object Model)을 가지고 있어 JavaScript로 쉽게 접근하고 조작할 수 있다. SVG 요소의 속성을 변경하거나 새로운 요소를 추가하는 것이 가능하다.
기본적인 SVG 요소 접근
JavaScript에서 SVG 요소에 접근하는 방법은 HTML 요소에 접근하는 방법과 거의 동일하다.
const svgElement = document.getElementById('mySvg');
const circleElement = document.getElementById('myCircle');
circleElement.setAttribute('r', '30');
circleElement.setAttribute('fill', 'blue');
위 코드는 ID가 ‘myCircle’인 SVG 원 요소를 찾아 반지름과 색상을 변경한다.
SVG 요소 동적 생성
JavaScript를 사용하여 새로운 SVG 요소를 동적으로 생성하고 추가할 수 있다.
const svgNS = "http://www.w3.org/2000/svg";
const svg = document.getElementById("mySvg");
const circle = document.createElementNS(svgNS, "circle");
circle.setAttribute("cx", "100");
circle.setAttribute("cy", "100");
circle.setAttribute("r", "50");
circle.setAttribute("fill", "red");
svg.appendChild(circle);
여기서 주목할 점은 일반 HTML 요소와 달리 SVG 요소를 생성할 때는 createElementNS 메서드를 사용하고 SVG 네임스페이스를 지정해야 한다는 것이다.
💡 중요 포인트:
• SVG 요소 생성 시에는 항상 createElementNS 메서드와 SVG 네임스페이스를 사용한다.
• SVG 요소에 스타일을 적용할 때는 setAttribute 메서드를 사용하거나 style 객체를 직접 조작한다.
SVG 요소에 이벤트 추가하기
SVG 요소에도 클릭, 마우스오버 등의 이벤트를 추가하여 사용자 상호작용을 구현할 수 있다.
기본 이벤트 핸들링
SVG 요소에 이벤트 리스너를 추가하는 예제다.
<svg id="mySvg" width="300" height="200">
<circle id="interactiveCircle" cx="100" cy="100" r="50" fill="green" /></svg>
<script>
const circle = document.getElementById('interactiveCircle');
circle.addEventListener('click', function() {
this.setAttribute('fill', 'red');
});
circle.addEventListener('mouseover', function() {
this.setAttribute('r', '60');
});
circle.addEventListener('mouseout', function() {
this.setAttribute('r', '50');
this.setAttribute('fill', 'green');
});
</script>
이 예제에서는 원을 클릭하면 색상이 변경되고, 마우스를 올리면 크기가 커졌다가 마우스를 떼면 원래대로 돌아온다.
드래그 앤 드롭 구현하기
SVG 요소에 드래그 앤 드롭 기능을 구현하는 예제다.
<svg id="svgContainer" width="400" height="300" style="border: 1px solid #ccc">
<circle id="draggableCircle" cx="100" cy="100" r="30" fill="purple" /></svg>
<script>
const circle = document.getElementById('draggableCircle');
let isDragging = false;
let offset = { x: 0, y: 0 };
circle.addEventListener('mousedown', function(event) {
isDragging = true;
const pt = svgContainer.createSVGPoint();
pt.x = event.clientX;
pt.y = event.clientY;
const svgPt = pt.matrixTransform(svgContainer.getScreenCTM().inverse());
offset.x = svgPt.x - parseFloat(circle.getAttribute('cx'));
offset.y = svgPt.y - parseFloat(circle.getAttribute('cy'));
});
document.addEventListener('mousemove', function(event) {
if (!isDragging) return;
const pt = svgContainer.createSVGPoint();
pt.x = event.clientX;
pt.y = event.clientY;
const svgPt = pt.matrixTransform(svgContainer.getScreenCTM().inverse());
circle.setAttribute('cx', svgPt.x - offset.x);
circle.setAttribute('cy', svgPt.y - offset.y);
});
document.addEventListener('mouseup', function() {
isDragging = false;
});
</script>
이 예제는 원을 드래그하여 SVG 캔버스 내 어디든 이동할 수 있게 한다. 마우스 좌표를 SVG 좌표계로 변환하는 방법에 주목할 필요가 있다.
SVG DOM 특징
SVG DOM은 HTML DOM과 유사하지만 몇 가지 중요한 차이점이 있다. SVG DOM을 효과적으로 조작하려면 이러한 특징을 이해하는 것이 중요하다.
특징 | HTML DOM | SVG DOM |
---|---|---|
요소 생성 | createElement() | createElementNS() |
속성 접근 | element.attribute = value | element.setAttribute(name, value) |
좌표계 | 픽셀 기반 | 사용자 정의 좌표계 |
스타일 적용 | CSS 또는 style 속성 | 속성 값 또는 CSS |
이벤트 처리 | 표준 DOM 이벤트 | 표준 DOM 이벤트 + SVG 특화 이벤트 |
SVG 좌표 변환
SVG 좌표계와 화면 좌표계 간의 변환은 대화형 SVG 작업에서 중요한 개념이다.
function getMousePositionInSVG(evt, svg) {
const pt = svg.createSVGPoint();
pt.x = evt.clientX;
pt.y = evt.clientY;
const svgPoint = pt.matrixTransform(svg.getScreenCTM().inverse());
return {
x: svgPoint.x,
y: svgPoint.y
};
}
이 함수는 마우스 이벤트에서 화면 좌표를 가져와 SVG 내부 좌표계로 변환한다.
실용적인 SVG 스크립팅 예제
SVG와 JavaScript를 함께 사용하는 몇 가지 실용적인 예제를 살펴보자.
대화형 데이터 시각화
간단한 막대 그래프를 생성하고 상호 작용을 추가하는 예제다.
<svg id="barChart" width="400" height="300">
<line x1="50" y1="250" x2="50" y2="50" stroke="black" />
<line x1="50" y1="250" x2="350" y2="250" stroke="black" /></svg>
<script>
const svg = document.getElementById('barChart');
const data = [45, 70, 30, 85, 60];
const barWidth = 40;
const spacing = 20;
const maxValue = 100;
const baseY = 250;
data.forEach((value, index) => {
const barHeight = (value / maxValue) * 200;
const x = 70 + (barWidth + spacing) * index;
const y = baseY - barHeight;
const bar = document.createElementNS("http://www.w3.org/2000/svg", "rect");
bar.setAttribute("x", x);
bar.setAttribute("y", y);
bar.setAttribute("width", barWidth);
bar.setAttribute("height", barHeight);
bar.setAttribute("fill", "steelblue");
bar.addEventListener('mouseover', function() {
this.setAttribute('fill', 'orange');
const text = document.createElementNS("http://www.w3.org/2000/svg", "text");
text.setAttribute("x", x + barWidth/2);
text.setAttribute("y", y - 10);
text.setAttribute("text-anchor", "middle");
text.setAttribute("id", "barValue");
text.textContent = value;
svg.appendChild(text);
});
bar.addEventListener('mouseout', function() {
this.setAttribute('fill', 'steelblue');
const text = document.getElementById('barValue');
if(text) svg.removeChild(text);
});
svg.appendChild(bar);
});
</script>
이 예제는 데이터 배열을 기반으로 대화형 막대 그래프를 생성한다. 각 막대에 마우스를 올리면 색상이 변경되고 데이터 값이 표시된다.
SVG 기반 도형 그리기 도구
간단한 SVG 그리기 도구를 구현하는 예제다.
<div>
<button id="rectBtn">사각형</button>
<button id="circleBtn">원</button>
<button id="clearBtn">지우기</button>
</div>
<svg id="drawingBoard" width="400" height="300" style="border: 1px solid #ccc; margin-top: 10px"></svg>
<script>
const svg = document.getElementById('drawingBoard');
const svgNS = "http://www.w3.org/2000/svg";
let currentTool = null;
let isDrawing = false;
let startPoint = { x: 0, y: 0 };
let currentShape = null;
document.getElementById('rectBtn').addEventListener('click', () => currentTool = 'rect');
document.getElementById('circleBtn').addEventListener('click', () => currentTool = 'circle');
document.getElementById('clearBtn').addEventListener('click', () => {
while (svg.lastChild) {
svg.removeChild(svg.lastChild);
}
});
svg.addEventListener('mousedown', startDrawing);
svg.addEventListener('mousemove', draw);
svg.addEventListener('mouseup', endDrawing);
function getMousePosition(evt) {
const pt = svg.createSVGPoint();
pt.x = evt.clientX;
pt.y = evt.clientY;
return pt.matrixTransform(svg.getScreenCTM().inverse());
}
function startDrawing(evt) {
if (!currentTool) return;
isDrawing = true;
const pt = getMousePosition(evt);
startPoint = { x: pt.x, y: pt.y };
if (currentTool === 'rect') {
currentShape = document.createElementNS(svgNS, 'rect');
currentShape.setAttribute('x', startPoint.x);
currentShape.setAttribute('y', startPoint.y);
currentShape.setAttribute('width', 0);
currentShape.setAttribute('height', 0);
currentShape.setAttribute('fill', 'transparent');
currentShape.setAttribute('stroke', 'black');
} else if (currentTool === 'circle') {
currentShape = document.createElementNS(svgNS, 'circle');
currentShape.setAttribute('cx', startPoint.x);
currentShape.setAttribute('cy', startPoint.y);
currentShape.setAttribute('r', 0);
currentShape.setAttribute('fill', 'transparent');
currentShape.setAttribute('stroke', 'black');
}
svg.appendChild(currentShape);
}
function draw(evt) {
if (!isDrawing || !currentShape) return;
const pt = getMousePosition(evt);
if (currentTool === 'rect') {
const width = pt.x - startPoint.x;
const height = pt.y - startPoint.y;
currentShape.setAttribute('width', Math.abs(width));
currentShape.setAttribute('height', Math.abs(height));
currentShape.setAttribute('x', width < 0 ? pt.x : startPoint.x);
currentShape.setAttribute('y', height < 0 ? pt.y : startPoint.y);
} else if (currentTool === 'circle') {
const dx = pt.x - startPoint.x;
const dy = pt.y - startPoint.y;
const radius = Math.sqrt(dx * dx + dy * dy);
currentShape.setAttribute('r', radius);
}
}
function endDrawing() {
isDrawing = false;
currentShape = null;
}
</script>[/code]
이 도구는 사용자가 버튼을 클릭하여 그리기 도구를 선택하고 SVG 캔버스에서 드래그하여 사각형이나 원을 그릴 수 있게 한다.
SVG 애니메이션과 SMIL 대신 JavaScript 사용
SMIL 애니메이션 대신 JavaScript를 사용하여 SVG 애니메이션을 구현할 수 있다. 이는 더 나은 브라우저 호환성과 애니메이션에 대한 더 많은 제어를 제공한다.
JavaScript로 간단한 애니메이션 구현
[code lang="markup"]<svg id="animationSvg" width="400" height="200">
<circle id="movingCircle" cx="50" cy="100" r="20" fill="red" /></svg>
<script>
const circle = document.getElementById('movingCircle');
let position = 50;
let direction = 1;
function animate() {
position += direction * 2;
if (position >= 350) direction = -1;
if (position <= 50) direction = 1;
circle.setAttribute('cx', position);
requestAnimationFrame(animate);
}
animate();
</script>[/code]
이 예제는 원이 SVG 캔버스의 왼쪽에서 오른쪽으로, 다시 왼쪽으로 움직이는 간단한 애니메이션을 보여준다.
💡 애니메이션 최적화 팁:
• 복잡한 애니메이션에는 항상 requestAnimationFrame을 사용한다.
• 성능을 위해 변형이 필요한 속성만 업데이트한다.
• 가능하면 SVG 변환(transform) 속성을 활용한다.
SVG와 React, Vue 등의 프레임워크 통합
모던 웹 프레임워크를 사용하면 SVG 스크립팅을 더 효율적으로 관리할 수 있다.
React에서의 SVG 활용
[code lang="js"]import React, { useState } from 'react';
const SVGComponent = () => {
const [radius, setRadius] = useState(20);
const handleMouseOver = () => {
setRadius(40);
};
const handleMouseOut = () => {
setRadius(20);
};
return (
<svg width="300" height="200">
<circle
cx="150"
cy="100"
r={radius}
fill="blue"
onMouseOver={handleMouseOver}
onMouseOut={handleMouseOut}
/>
</svg>
);
};
export default SVGComponent;
이 React 컴포넌트는 마우스를 올렸을 때 크기가 변하는 SVG 원을 보여준다. React의 상태 관리 기능을 사용하여 원의 반지름을 제어한다.
Vue.js에서의 SVG 활용
<template>
<svg width="300" height="200">
<circle
:cx="150"
:cy="100"
:r="radius"
fill="green"
@mouseover="radius = 40"
@mouseout="radius = 20"
/>
</svg>
</template>
<script>
export default {
data() {
return {
radius: 20
};
}
};
</script>
Vue.js에서도 반응형 데이터 바인딩을 통해 SVG 요소를 쉽게 조작할 수 있다.
SVG 스크립팅 최적화 기법
대규모 웹 애플리케이션에서 SVG를 사용할 때는 성능 최적화가 중요하다.
성능 최적화 팁
- 불필요한 요소 업데이트 피하기: 변경된 요소만 업데이트한다.
- DOM 조작 최소화: 대량의 SVG 요소를 추가할 때는 DocumentFragment를 사용한다.
- 복잡한 계산 캐싱: 좌표 변환이나 복잡한 계산 결과를 캐싱한다.
- requestAnimationFrame 사용: 애니메이션에는 setTimeout 대신 requestAnimationFrame을 사용한다.
- 오프스크린 렌더링 최소화: 화면에 보이지 않는 요소는 업데이트를 연기한다.
대량의 요소를 효율적으로 추가하는 방법
function addManyElements(svg, count) {const fragment = document.createDocumentFragment();
const svgNS = "http://www.w3.org/2000/svg";
for (let i = 0; i < count; i++) { const circle = document.createElementNS(svgNS, 'circle'); circle.setAttribute('cx', Math.random() * 400); circle.setAttribute('cy', Math.random() * 300); circle.setAttribute('r', Math.random() * 10 + 2); circle.setAttribute('fill', `rgb(${Math.random() * 255}, ${Math.random() * 255}, ${Math.random() * 255})`); fragment.appendChild(circle); } svg.appendChild(fragment); }[/code]
이 함수는 DocumentFragment를 사용하여 많은 수의 원을 SVG에 효율적으로 추가한다. 이렇게 하면 DOM 업데이트를 한 번만 수행하여 성능이 크게 향상된다.
💡 성능 개선을 위한 주요 기법:
• SVG 요소 수를 적절하게 관리한다.
• 복잡한 경로 대신 간단한 기본 도형을 사용한다.
• 대규모 데이터 시각화에는 캔버스(Canvas) 사용을 고려한다.
• GPU 가속을 위해 transform 속성을 활용한다.
실제 프로젝트에서의 SVG 스크립팅 적용
SVG 스크립팅은 다음과 같은 실제 프로젝트에서 활용할 수 있다.
활용 분야 | 사례 | 장점 |
---|---|---|
데이터 시각화 | 대화형 차트, 그래프, 대시보드 | 사용자 상호작용, 애니메이션 제어, 동적 데이터 업데이트 |
맵 및 다이어그램 | 인터랙티브 지도, 네트워크 다이어그램 | 확대/축소, 팬, 선택 기능 구현 |
그래픽 에디터 | 온라인 디자인 도구, 다이어그램 편집기 | 사용자가 생성한 콘텐츠, 실시간 미리보기 |
인터랙티브 인포그래픽 | 교육용 자료, 설명이 포함된 시각화 | 단계별 애니메이션, 사용자 진행 제어 |
SVG와 JavaScript를 함께 사용하면 웹 그래픽의 가능성이 크게 확장된다. 확장성, 접근성, 인터랙티브성을 모두 갖춘 풍부한 시각적 경험을 만들 수 있다. SVG 스크립팅을 마스터하면 웹 개발자와 디자이너 모두에게 귀중한 도구가 된다.