Published 12/2/2025 · 6 min read
Tags: svg
Lesson 23: SVG + JavaScript
SVG elements are part of the DOM, just like HTML elements. That means you can select them, modify them, animate them, and respond to events — all with the JavaScript you already know.
Selecting SVG Elements
Standard DOM methods work on SVG:
// By ID
const circle = document.getElementById("my-circle");
// By class
const bars = document.querySelectorAll(".bar");
// By tag
const paths = document.getElementsByTagName("path");
<svg id="chart" width="400" height="200">
<circle id="my-circle" cx="100" cy="100" r="50" fill="#3b82f6" />
<rect class="bar" x="200" y="50" width="40" height="100" fill="#10b981" />
</svg>
Modifying Attributes
Use getAttribute() and setAttribute():
const circle = document.getElementById("my-circle");
// Read
const radius = circle.getAttribute("r");
console.log(radius); // "50"
// Write
circle.setAttribute("r", "75");
circle.setAttribute("fill", "#ef4444");
Common Attributes to Modify
- Position:
x,y,cx,cy - Size:
width,height,r,rx,ry - Appearance:
fill,stroke,stroke-width,opacity - Transform:
transform - Path:
d
Modifying Styles
SVG elements support inline styles and CSS classes:
const rect = document.querySelector(".bar");
// Inline style
rect.style.fill = "#8b5cf6";
rect.style.opacity = "0.5";
// CSS classes
rect.classList.add("highlighted");
rect.classList.remove("dimmed");
rect.classList.toggle("active");
Creating SVG Elements
SVG elements must be created with the SVG namespace:
const svgNS = "http://www.w3.org/2000/svg";
// Create a circle
const circle = document.createElementNS(svgNS, "circle");
circle.setAttribute("cx", "100");
circle.setAttribute("cy", "100");
circle.setAttribute("r", "30");
circle.setAttribute("fill", "#3b82f6");
// Add to SVG
const svg = document.getElementById("my-svg");
svg.appendChild(circle);
Helper Function
function createSVGElement(tag, attributes = {}) {
const element = document.createElementNS("http://www.w3.org/2000/svg", tag);
for (const [key, value] of Object.entries(attributes)) {
element.setAttribute(key, value);
}
return element;
}
// Usage
const rect = createSVGElement("rect", {
x: 10,
y: 10,
width: 100,
height: 50,
fill: "#10b981",
});
svg.appendChild(rect);
Event Handling
SVG elements support the same events as HTML:
const circle = document.getElementById("my-circle");
circle.addEventListener("click", (e) => {
console.log("Circle clicked!");
e.target.setAttribute("fill", "#ef4444");
});
circle.addEventListener("mouseenter", () => {
circle.setAttribute("r", "60");
});
circle.addEventListener("mouseleave", () => {
circle.setAttribute("r", "50");
});
Pointer Events
Control which parts of an SVG respond to events:
/* Only the fill responds to clicks */
.shape {
pointer-events: fill;
}
/* Only the stroke responds */
.shape {
pointer-events: stroke;
}
/* Both fill and stroke */
.shape {
pointer-events: all;
}
/* Ignore events entirely */
.shape {
pointer-events: none;
}
Animation with JavaScript
Using requestAnimationFrame
const circle = document.getElementById("my-circle");
let angle = 0;
function animate() {
angle += 0.02;
const x = 200 + Math.cos(angle) * 100;
const y = 150 + Math.sin(angle) * 100;
circle.setAttribute("cx", x);
circle.setAttribute("cy", y);
requestAnimationFrame(animate);
}
animate();
Animating Path Data
const path = document.getElementById("wave");
let offset = 0;
function animateWave() {
offset += 0.1;
const d = `M 0,50
Q 25,${30 + Math.sin(offset) * 20} 50,50
T 100,50 T 150,50 T 200,50`;
path.setAttribute("d", d);
requestAnimationFrame(animateWave);
}
animateWave();
Using CSS Transitions
Let JavaScript trigger, let CSS animate:
.circle {
transition: r 0.3s ease, fill 0.3s ease;
}
circle.setAttribute("r", "80"); // CSS handles the animation
Practical Examples
Interactive Bar Chart
<svg id="chart" width="400" height="200">
<!-- Bars will be added by JavaScript -->
</svg>
<div id="tooltip" class="tooltip"></div>
const data = [
{ label: "Jan", value: 45 },
{ label: "Feb", value: 62 },
{ label: "Mar", value: 58 },
{ label: "Apr", value: 81 },
{ label: "May", value: 73 },
];
const svg = document.getElementById("chart");
const tooltip = document.getElementById("tooltip");
const barWidth = 50;
const gap = 20;
const maxValue = Math.max(...data.map((d) => d.value));
data.forEach((item, index) => {
const barHeight = (item.value / maxValue) * 150;
const x = index * (barWidth + gap) + 30;
const y = 180 - barHeight;
const bar = createSVGElement("rect", {
x,
y,
width: barWidth,
height: barHeight,
fill: "#3b82f6",
class: "bar",
});
bar.addEventListener("mouseenter", (e) => {
bar.setAttribute("fill", "#1d4ed8");
tooltip.textContent = `${item.label}: ${item.value}`;
tooltip.style.display = "block";
tooltip.style.left = `${e.pageX + 10}px`;
tooltip.style.top = `${e.pageY - 20}px`;
});
bar.addEventListener("mouseleave", () => {
bar.setAttribute("fill", "#3b82f6");
tooltip.style.display = "none";
});
svg.appendChild(bar);
});
Draggable Circle
const circle = document.getElementById("draggable");
let isDragging = false;
let offset = { x: 0, y: 0 };
circle.addEventListener("mousedown", (e) => {
isDragging = true;
const cx = parseFloat(circle.getAttribute("cx"));
const cy = parseFloat(circle.getAttribute("cy"));
offset.x = e.clientX - cx;
offset.y = e.clientY - cy;
});
document.addEventListener("mousemove", (e) => {
if (!isDragging) return;
circle.setAttribute("cx", e.clientX - offset.x);
circle.setAttribute("cy", e.clientY - offset.y);
});
document.addEventListener("mouseup", () => {
isDragging = false;
});
Dynamic Pie Chart
function createPieChart(data, cx, cy, radius) {
let currentAngle = -Math.PI / 2;
const total = data.reduce((sum, d) => sum + d.value, 0);
data.forEach((item) => {
const sliceAngle = (item.value / total) * Math.PI * 2;
const endAngle = currentAngle + sliceAngle;
const x1 = cx + radius * Math.cos(currentAngle);
const y1 = cy + radius * Math.sin(currentAngle);
const x2 = cx + radius * Math.cos(endAngle);
const y2 = cy + radius * Math.sin(endAngle);
const largeArc = sliceAngle > Math.PI ? 1 : 0;
const d = `M ${cx},${cy} L ${x1},${y1} A ${radius},${radius} 0 ${largeArc},1 ${x2},${y2} Z`;
const slice = createSVGElement("path", {
d,
fill: item.color,
});
svg.appendChild(slice);
currentAngle = endAngle;
});
}
createPieChart(
[
{ value: 30, color: "#3b82f6" },
{ value: 25, color: "#10b981" },
{ value: 20, color: "#f59e0b" },
{ value: 25, color: "#ef4444" },
],
150,
150,
100
);
Getting SVG Dimensions
const svg = document.getElementById("my-svg");
// The actual rendered size
const bbox = svg.getBoundingClientRect();
console.log(bbox.width, bbox.height);
// The viewBox dimensions
const viewBox = svg.viewBox.baseVal;
console.log(viewBox.width, viewBox.height);
// For individual elements
const circle = document.getElementById("my-circle");
const circleBBox = circle.getBBox();
console.log(circleBBox.x, circleBBox.y, circleBBox.width, circleBBox.height);
Coordinate Conversion
Converting between screen coordinates and SVG coordinates:
function screenToSVG(svg, screenX, screenY) {
const point = svg.createSVGPoint();
point.x = screenX;
point.y = screenY;
const ctm = svg.getScreenCTM().inverse();
return point.matrixTransform(ctm);
}
svg.addEventListener("click", (e) => {
const svgPoint = screenToSVG(svg, e.clientX, e.clientY);
console.log(`SVG coordinates: ${svgPoint.x}, ${svgPoint.y}`);
});
Exercises
Exercise 23.1: Click Counter
Create a circle that displays a number. Each click increments the number.
Exercise 23.2: Random Dots
Create a button that adds a randomly positioned, randomly coloured circle to an SVG each time it’s clicked.
Exercise 23.3: Hover Highlight
Create multiple rectangles. When hovering over one, dim all the others.
Exercise 23.4: Simple Drawing App
Create an SVG canvas where clicking creates circles at the click position.
Exercise 23.5: Animated Progress Ring
Create a circular progress indicator that animates from 0% to 100% using stroke-dasharray.
Key Takeaways
- SVG elements are DOM nodes — use standard methods to select and modify them
- Create SVG elements with
createElementNS()using the SVG namespace - Modify attributes with
getAttribute()/setAttribute() - SVG supports all standard events (click, hover, drag, etc.)
- Use
requestAnimationFramefor smooth JavaScript animations - Convert screen coordinates to SVG coordinates for accurate positioning
- Combine JavaScript control with CSS transitions for smooth effects