JavaScript 交互
SVG 元素是 DOM 的一部分,可以通过 JavaScript 进行完全的操作和交互。
获取和操作 SVG 元素
javascript
// 获取 SVG 元素
const svg = document.querySelector("svg")
const circle = document.getElementById("myCircle")
const rect = document.querySelector("rect")
// 修改属性
circle.setAttribute("r", "60")
circle.setAttribute("fill", "blue")
rect.style.fill = "red" // 使用 style 属性
// 获取属性
const radius = circle.getAttribute("r")
const fillColor = circle.getAttribute("fill")动态创建 SVG 元素
javascript
// 创建 SVG 元素
const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg")
svg.setAttribute("width", "400")
svg.setAttribute("height", "300")
// 创建圆形
const circle = document.createElementNS("http://www.w3.org/2000/svg", "circle")
circle.setAttribute("cx", "200")
circle.setAttribute("cy", "150")
circle.setAttribute("r", "50")
circle.setAttribute("fill", "blue")
// 添加到 SVG
svg.appendChild(circle)
document.body.appendChild(svg)使用 JavaScript 操作 SVG 的完整示例
html
<svg id="interactiveSvg" width="400" height="300" xmlns="http://www.w3.org/2000/svg">
<circle id="myCircle" cx="200" cy="150" r="50" fill="blue" />
<rect id="myRect" x="100" y="100" width="100" height="80" fill="red" />
</svg>
<button onclick="changeColor()">改变颜色</button>
<button onclick="animateCircle()">动画圆形</button>
<button onclick="addShape()">添加形状</button>
<script>
const circle = document.getElementById("myCircle")
const rect = document.getElementById("myRect")
function changeColor() {
const colors = ["red", "blue", "green", "yellow", "purple"]
const randomColor = colors[Math.floor(Math.random() * colors.length)]
circle.setAttribute("fill", randomColor)
}
function animateCircle() {
let radius = 50
const interval = setInterval(() => {
radius += 5
circle.setAttribute("r", radius)
if (radius > 100) {
clearInterval(interval)
circle.setAttribute("r", "50")
}
}, 50)
}
function addShape() {
const svg = document.getElementById("interactiveSvg")
const newCircle = document.createElementNS(
"http://www.w3.org/2000/svg",
"circle"
)
newCircle.setAttribute("cx", Math.random() * 400)
newCircle.setAttribute("cy", Math.random() * 300)
newCircle.setAttribute("r", "20")
newCircle.setAttribute("fill", "orange")
svg.appendChild(newCircle)
}
</script>事件处理
SVG 元素支持标准的 DOM 事件,可以轻松实现交互功能。
鼠标事件
html
<svg width="400" height="300" xmlns="http://www.w3.org/2000/svg">
<circle
id="clickableCircle"
cx="200"
cy="150"
r="50"
fill="blue"
style="cursor: pointer;" />
</svg>
<script>
const circle = document.getElementById("clickableCircle")
// 点击事件
circle.addEventListener("click", (e) => {
const colors = ["red", "blue", "green", "yellow", "purple"]
const randomColor = colors[Math.floor(Math.random() * colors.length)]
circle.setAttribute("fill", randomColor)
})
// 鼠标悬停
circle.addEventListener("mouseenter", () => {
circle.setAttribute("r", "60")
})
circle.addEventListener("mouseleave", () => {
circle.setAttribute("r", "50")
})
// 鼠标移动
circle.addEventListener("mousemove", (e) => {
const rect = circle.getBoundingClientRect()
const x = e.clientX - rect.left
const y = e.clientY - rect.top
console.log(`鼠标位置: (${x}, ${y})`)
})
</script>触摸事件(移动端)
html
<svg width="400" height="300" xmlns="http://www.w3.org/2000/svg">
<circle id="touchableCircle" cx="200" cy="150" r="50" fill="green" />
</svg>
<script>
const circle = document.getElementById("touchableCircle")
circle.addEventListener("touchstart", (e) => {
e.preventDefault()
circle.setAttribute("fill", "red")
})
circle.addEventListener("touchend", (e) => {
e.preventDefault()
circle.setAttribute("fill", "green")
})
</script>拖拽功能
html
<svg id="dragSvg" width="400" height="300" xmlns="http://www.w3.org/2000/svg">
<circle
id="draggableCircle"
cx="200"
cy="150"
r="50"
fill="blue"
style="cursor: move;" />
</svg>
<script>
const circle = document.getElementById("draggableCircle")
const svg = document.getElementById("dragSvg")
let isDragging = false
let offset = { x: 0, y: 0 }
circle.addEventListener("mousedown", (e) => {
isDragging = true
const rect = svg.getBoundingClientRect()
const cx = parseFloat(circle.getAttribute("cx"))
const cy = parseFloat(circle.getAttribute("cy"))
offset.x = e.clientX - rect.left - cx
offset.y = e.clientY - rect.top - cy
})
svg.addEventListener("mousemove", (e) => {
if (isDragging) {
const rect = svg.getBoundingClientRect()
const x = e.clientX - rect.left - offset.x
const y = e.clientY - rect.top - offset.y
circle.setAttribute("cx", x)
circle.setAttribute("cy", y)
}
})
svg.addEventListener("mouseup", () => {
isDragging = false
})
svg.addEventListener("mouseleave", () => {
isDragging = false
})
</script>交互式 SVG 应用示例
html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>交互式 SVG 示例</title>
<style>
body {
font-family: Arial, sans-serif;
padding: 20px;
background-color: #f5f5f5;
}
.container {
background: white;
padding: 20px;
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}
.controls {
margin: 20px 0;
}
button {
padding: 10px 20px;
margin: 5px;
cursor: pointer;
background: #4caf50;
color: white;
border: none;
border-radius: 4px;
}
button:hover {
background: #45a049;
}
svg {
border: 1px solid #ddd;
background: white;
}
</style>
</head>
<body>
<div class="container">
<h1>交互式 SVG 示例</h1>
<div class="controls">
<button onclick="addRandomShape()">添加随机形状</button>
<button onclick="clearAll()">清空</button>
<button onclick="animateAll()">动画所有</button>
</div>
<svg
id="interactiveSvg"
width="600"
height="400"
xmlns="http://www.w3.org/2000/svg">
<defs>
<linearGradient id="grad1" x1="0%" y1="0%" x2="100%" y2="100%">
<stop offset="0%" stop-color="#ff6b6b" />
<stop offset="100%" stop-color="#4ecdc4" />
</linearGradient>
</defs>
<rect width="600" height="400" fill="url(#grad1)" opacity="0.1" />
</svg>
</div>
<script>
const svg = document.getElementById("interactiveSvg")
let shapeCount = 0
function addRandomShape() {
const shapes = ["circle", "rect", "ellipse"]
const shapeType = shapes[Math.floor(Math.random() * shapes.length)]
const shape = document.createElementNS(
"http://www.w3.org/2000/svg",
shapeType
)
const colors = ["#ff6b6b", "#4ecdc4", "#ffe66d", "#a8e6cf", "#ff8b94"]
const color = colors[Math.floor(Math.random() * colors.length)]
shape.setAttribute("fill", color)
shape.setAttribute("stroke", "#333")
shape.setAttribute("stroke-width", "2")
shape.style.cursor = "pointer"
shape.setAttribute("class", "interactive-shape")
if (shapeType === "circle") {
shape.setAttribute("cx", Math.random() * 500 + 50)
shape.setAttribute("cy", Math.random() * 300 + 50)
shape.setAttribute("r", Math.random() * 30 + 20)
} else if (shapeType === "rect") {
shape.setAttribute("x", Math.random() * 500)
shape.setAttribute("y", Math.random() * 300)
shape.setAttribute("width", Math.random() * 60 + 40)
shape.setAttribute("height", Math.random() * 60 + 40)
shape.setAttribute("rx", "5")
} else if (shapeType === "ellipse") {
shape.setAttribute("cx", Math.random() * 500 + 50)
shape.setAttribute("cy", Math.random() * 300 + 50)
shape.setAttribute("rx", Math.random() * 40 + 30)
shape.setAttribute("ry", Math.random() * 30 + 20)
}
// 添加交互事件
shape.addEventListener("click", () => {
const newColor = colors[Math.floor(Math.random() * colors.length)]
shape.setAttribute("fill", newColor)
})
shape.addEventListener("mouseenter", () => {
shape.setAttribute("opacity", "0.7")
})
shape.addEventListener("mouseleave", () => {
shape.setAttribute("opacity", "1")
})
svg.appendChild(shape)
shapeCount++
}
function clearAll() {
const shapes = svg.querySelectorAll(".interactive-shape")
shapes.forEach((shape) => shape.remove())
shapeCount = 0
}
function animateAll() {
const shapes = svg.querySelectorAll(".interactive-shape")
shapes.forEach((shape, index) => {
const animate = document.createElementNS(
"http://www.w3.org/2000/svg",
"animateTransform"
)
animate.setAttribute("attributeName", "transform")
animate.setAttribute("type", "rotate")
animate.setAttribute(
"from",
`0 ${shape.getAttribute("cx") || 0} ${shape.getAttribute("cy") || 0}`
)
animate.setAttribute(
"to",
`360 ${shape.getAttribute("cx") || 0} ${shape.getAttribute("cy") || 0}`
)
animate.setAttribute("dur", "2s")
animate.setAttribute("repeatCount", "indefinite")
shape.appendChild(animate)
})
}
</script>
</body>
</html>响应式设计
SVG 天生支持响应式设计,可以通过多种方式实现自适应布局。
viewBox 属性
viewBox 是 SVG 响应式设计的核心,它定义了 SVG 的坐标系统和可见区域。
html
<!-- 使用 viewBox 实现响应式 -->
<svg viewBox="0 0 400 300" xmlns="http://www.w3.org/2000/svg">
<circle cx="200" cy="150" r="50" fill="blue" />
<rect x="100" y="100" width="200" height="100" fill="red" />
</svg>
<style>
svg {
width: 100%;
height: auto;
max-width: 800px;
}
</style>preserveAspectRatio 属性
控制 SVG 如何适应容器,保持宽高比。
html
<!-- 保持宽高比,居中显示 -->
<svg
viewBox="0 0 400 300"
preserveAspectRatio="xMidYMid meet"
width="100%"
height="auto">
<!-- SVG 内容 -->
</svg>
<!-- 填充整个容器,可能裁剪 -->
<svg
viewBox="0 0 400 300"
preserveAspectRatio="xMidYMid slice"
width="100%"
height="auto">
<!-- SVG 内容 -->
</svg>preserveAspectRatio 值说明:
| 值 | 说明 |
|---|---|
xMinYMin meet | 左上对齐,保持比例 |
xMidYMid meet | 居中,保持比例(默认) |
xMaxYMax meet | 右下对齐,保持比例 |
xMinYMin slice | 左上对齐,填充容器 |
xMidYMid slice | 居中,填充容器 |
none | 不保持宽高比,拉伸填充 |
响应式 SVG 示例
html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>响应式 SVG</title>
<style>
.responsive-container {
width: 100%;
max-width: 800px;
margin: 0 auto;
}
.responsive-svg {
width: 100%;
height: auto;
border: 1px solid #ddd;
}
@media (max-width: 768px) {
.responsive-svg {
max-height: 300px;
}
}
</style>
</head>
<body>
<div class="responsive-container">
<h2>响应式 SVG 示例</h2>
<svg
class="responsive-svg"
viewBox="0 0 400 300"
preserveAspectRatio="xMidYMid meet"
xmlns="http://www.w3.org/2000/svg">
<defs>
<linearGradient id="responsiveGrad" x1="0%" y1="0%" x2="100%" y2="100%">
<stop offset="0%" stop-color="#ff6b6b" />
<stop offset="100%" stop-color="#4ecdc4" />
</linearGradient>
</defs>
<rect width="400" height="300" fill="url(#responsiveGrad)" opacity="0.3" />
<circle cx="200" cy="150" r="80" fill="blue" opacity="0.7" />
<text x="200" y="160" text-anchor="middle" fill="white" font-size="24">
响应式 SVG
</text>
</svg>
</div>
</body>
</html>性能优化
1. 使用 <use> 元素复用图形
html
<svg width="400" height="300" xmlns="http://www.w3.org/2000/svg">
<defs>
<circle id="reusableCircle" cx="0" cy="0" r="20" fill="blue" />
</defs>
<!-- 复用圆形,而不是创建多个 -->
<use href="#reusableCircle" x="50" y="50" />
<use href="#reusableCircle" x="150" y="50" />
<use href="#reusableCircle" x="250" y="50" />
<use href="#reusableCircle" x="350" y="50" />
</svg>2. 使用 CSS 动画替代 SMIL 动画
html
<!-- 不推荐:SMIL 动画(部分浏览器已弃用) -->
<circle cx="100" cy="100" r="50" fill="blue">
<animate attributeName="r" from="50" to="100" dur="2s" repeatCount="indefinite" />
</circle>
<!-- 推荐:CSS 动画 -->
<style>
.animated-circle {
animation: pulse 2s infinite;
}
@keyframes pulse {
0%,
100% {
r: 50;
}
50% {
r: 100;
}
}
</style>
<circle class="animated-circle" cx="100" cy="100" r="50" fill="blue" />3. 减少 DOM 操作
javascript
// 不好的做法:频繁操作 DOM
for (let i = 0; i < 1000; i++) {
const circle = document.createElementNS("http://www.w3.org/2000/svg", "circle")
svg.appendChild(circle)
}
// 好的做法:使用 DocumentFragment
const fragment = document.createDocumentFragment()
for (let i = 0; i < 1000; i++) {
const circle = document.createElementNS("http://www.w3.org/2000/svg", "circle")
fragment.appendChild(circle)
}
svg.appendChild(fragment)4. 优化复杂路径
html
<!-- 简化路径数据 -->
<path d="M10,10 L20,20 L30,10 Z" />
<!-- 使用简化的命令 -->
<path d="M10,10 L20,20 30,10 Z" />5. 使用 CSS 控制样式
html
<!-- 不推荐:内联样式 -->
<circle cx="100" cy="100" r="50" fill="blue" stroke="red" stroke-width="2" />
<!-- 推荐:使用 CSS -->
<style>
.my-circle {
fill: blue;
stroke: red;
stroke-width: 2;
}
</style>
<circle class="my-circle" cx="100" cy="100" r="50" />最佳实践
1. 命名空间
始终使用正确的命名空间创建 SVG 元素。
javascript
// 正确
const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg")
const circle = document.createElementNS("http://www.w3.org/2000/svg", "circle")
// 错误(会创建 HTML 元素,不是 SVG 元素)
const svg = document.createElement("svg")2. 可访问性
为 SVG 添加适当的可访问性属性。
html
<svg role="img" aria-label="描述性文本" xmlns="http://www.w3.org/2000/svg">
<title>图形标题</title>
<desc>图形的详细描述</desc>
<!-- SVG 内容 -->
</svg>3. 语义化结构
使用 <g> 元素组织相关的 SVG 元素。
html
<svg width="400" height="300" xmlns="http://www.w3.org/2000/svg">
<g id="house" transform="translate(100, 50)">
<rect x="0" y="0" width="200" height="150" fill="brown" />
<polygon points="0,0 100,-50 200,0" fill="red" />
</g>
<g id="tree" transform="translate(50, 100)">
<rect x="0" y="0" width="30" height="100" fill="brown" />
<circle cx="15" cy="0" r="40" fill="green" />
</g>
</svg>4. 代码组织
将可复用的定义放在 <defs> 中。
html
<svg width="400" height="300" xmlns="http://www.w3.org/2000/svg">
<defs>
<!-- 渐变定义 -->
<linearGradient id="myGradient">
<stop offset="0%" stop-color="red" />
<stop offset="100%" stop-color="blue" />
</linearGradient>
<!-- 滤镜定义 -->
<filter id="myFilter">
<feGaussianBlur stdDeviation="3" />
</filter>
<!-- 可复用图形 -->
<circle id="reusableCircle" r="20" fill="url(#myGradient)" />
</defs>
<!-- 使用定义 -->
<use href="#reusableCircle" x="100" y="100" />
</svg>5. 错误处理
javascript
// 检查浏览器支持
if (typeof SVGRect !== "undefined") {
// SVG 支持
} else {
// 不支持 SVG,提供降级方案
}
// 检查元素是否存在
const svg = document.querySelector("svg")
if (svg) {
// 操作 SVG
}常见问题
1. SVG 不显示
问题:SVG 元素不显示或显示不正确。
解决方案:
- 检查命名空间是否正确
- 确认
width和height属性已设置 - 检查
viewBox是否正确 - 验证路径数据格式
html
<!-- 确保包含命名空间 -->
<svg xmlns="http://www.w3.org/2000/svg" width="400" height="300">
<!-- 内容 -->
</svg>2. 样式不生效
问题:CSS 样式无法应用到 SVG 元素。
解决方案:
- 使用
fill而不是background-color - 使用
stroke而不是border - 某些属性需要使用
setAttribute而不是style
css
/* SVG 样式 */
svg circle {
fill: blue; /* 不是 background-color */
stroke: red; /* 不是 border */
stroke-width: 2; /* 不是 border-width */
}3. 动画不流畅
问题:SVG 动画卡顿或不流畅。
解决方案:
- 使用 CSS 动画替代 SMIL 动画
- 减少同时运行的动画数量
- 使用
transform属性进行动画(硬件加速) - 避免动画复杂的路径
css
/* 使用 transform 进行动画(性能更好) */
.animated {
transform: translate(0, 0);
transition: transform 0.3s ease;
}
.animated:hover {
transform: translate(10px, 10px);
}4. 跨域问题
问题:外部 SVG 文件无法加载或操作。
解决方案:
- 确保服务器设置了正确的 CORS 头
- 使用同源 SVG 文件
- 考虑将 SVG 内联到 HTML 中
5. 响应式问题
问题:SVG 在不同屏幕尺寸下显示不正确。
解决方案:
- 使用
viewBox而不是固定的width和height - 设置
preserveAspectRatio属性 - 使用 CSS 控制 SVG 尺寸
html
<!-- 响应式 SVG -->
<svg
viewBox="0 0 400 300"
preserveAspectRatio="xMidYMid meet"
style="width: 100%; height: auto;">
<!-- 内容 -->
</svg>SVG vs Canvas 详细对比
| 特性 | SVG | Canvas |
|---|---|---|
| 图形类型 | 矢量图 | 位图 |
| DOM 支持 | 是,每个元素都是 DOM 节点 | 否,只有一个 canvas 元素 |
| 事件处理 | 原生支持,每个元素可独立处理 | 需要手动计算坐标 |
| 缩放质量 | 无损缩放 | 可能模糊 |
| 性能 | 适合少量复杂图形 | 适合大量简单图形 |
| 文件大小 | 复杂图形可能较大 | 通常较小 |
| 学习曲线 | 中等 | 简单 |
| 适用场景 | 图标、地图、可缩放图形、交互式图形 | 游戏、图表、图像处理、动画 |
| 可访问性 | 支持,可添加文本描述 | 不支持 |
| 样式控制 | CSS 完全支持 | 需要通过 JavaScript |
| 动画 | CSS/JS/SMIL | JavaScript |
| 文本渲染 | 原生支持,可选中 | 需要手动实现 |
选择建议:
-
使用 SVG 当:
- 需要可缩放的图形
- 需要交互性(点击、悬停等)
- 图形数量较少但复杂
- 需要可访问性支持
- 需要 CSS 样式控制
-
使用 Canvas 当:
- 需要处理大量图形
- 需要像素级操作
- 需要高性能动画
- 图形不需要交互
- 需要图像处理功能
实际应用案例
1. 数据可视化
html
<svg id="chart" width="600" height="400" xmlns="http://www.w3.org/2000/svg">
<defs>
<linearGradient id="barGradient" x1="0%" y1="0%" x2="0%" y2="100%">
<stop offset="0%" stop-color="#4ecdc4" />
<stop offset="100%" stop-color="#44a08d" />
</linearGradient>
</defs>
<!-- 坐标轴 -->
<line x1="50" y1="350" x2="550" y2="350" stroke="black" stroke-width="2" />
<line x1="50" y1="50" x2="50" y2="350" stroke="black" stroke-width="2" />
<!-- 数据柱 -->
<rect x="100" y="250" width="60" height="100" fill="url(#barGradient)" />
<rect x="200" y="200" width="60" height="150" fill="url(#barGradient)" />
<rect x="300" y="150" width="60" height="200" fill="url(#barGradient)" />
<rect x="400" y="100" width="60" height="250" fill="url(#barGradient)" />
<rect x="500" y="180" width="60" height="170" fill="url(#barGradient)" />
<!-- 标签 -->
<text x="130" y="370" text-anchor="middle" font-size="14">Q1</text>
<text x="230" y="370" text-anchor="middle" font-size="14">Q2</text>
<text x="330" y="370" text-anchor="middle" font-size="14">Q3</text>
<text x="430" y="370" text-anchor="middle" font-size="14">Q4</text>
<text x="530" y="370" text-anchor="middle" font-size="14">Q5</text>
</svg>2. 图标系统
html
<svg style="display: none;" xmlns="http://www.w3.org/2000/svg">
<defs>
<symbol id="icon-home" viewBox="0 0 24 24">
<path d="M10 20v-6h4v6h5v-8h3L12 3 2 12h3v8z" fill="currentColor" />
</symbol>
<symbol id="icon-user" viewBox="0 0 24 24">
<path
d="M12 12c2.21 0 4-1.79 4-4s-1.79-4-4-4-4 1.79-4 4 1.79 4 4 4zm0 2c-2.67 0-8 1.34-8 4v2h16v-2c0-2.66-5.33-4-8-4z"
fill="currentColor" />
</symbol>
</defs>
</svg>
<!-- 使用图标 -->
<svg class="icon" width="24" height="24">
<use href="#icon-home" />
</svg>
<style>
.icon {
fill: currentColor;
width: 24px;
height: 24px;
}
</style>3. 加载动画
html
<svg class="spinner" width="50" height="50" xmlns="http://www.w3.org/2000/svg">
<circle
cx="25"
cy="25"
r="20"
fill="none"
stroke="#4ecdc4"
stroke-width="4"
stroke-dasharray="31.416"
stroke-dashoffset="31.416">
<animate
attributeName="stroke-dasharray"
values="0 31.416;15.708 15.708;0 31.416;0 31.416"
dur="2s"
repeatCount="indefinite" />
<animate
attributeName="stroke-dashoffset"
values="0;-15.708;-31.416;-31.416"
dur="2s"
repeatCount="indefinite" />
</circle>
</svg>
<style>
.spinner {
animation: rotate 2s linear infinite;
}
@keyframes rotate {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}
</style>总结
SVG 是一个强大的矢量图形技术,具有以下优势:
- 可缩放性:无损缩放,适合各种屏幕尺寸
- 交互性:原生支持事件处理,易于实现交互
- 可访问性:支持文本描述和语义化标记
- 样式控制:完全支持 CSS
- DOM 集成:作为 DOM 的一部分,易于操作
关键要点:
- 使用
viewBox实现响应式设计 - 使用
<defs>和<use>复用图形 - 优先使用 CSS 动画而非 SMIL
- 为复杂图形添加适当的可访问性属性
- 根据场景选择 SVG 或 Canvas
通过合理使用 SVG,可以创建出美观、交互性强、性能良好的图形应用。