前言
在现代 Web 开发中,图像呈现方式已从传统的位图(如 PNG、JPEG)逐步向矢量图形 演进。其中,SVG(Scalable Vector Graphics,可缩放矢量图形) 凭借其无损缩放、语义清晰、高度可编程等特性,已成为构建高性能、高保真、高交互性用户界面的核心技术之一。
一、什么是 SVG?
SVG 是一种基于 XML(可扩展标记语言) 的开放标准矢量图形格式,由 W3C(万维网联盟) 制定并维护。与位图不同,SVG 使用数学公式描述图形------通过点、线、曲线、多边形等几何元素构建图像,因此无论放大多少倍,图像始终清晰锐利,无锯齿、无失真。
1.1 SVG 的核心优势
| 特性 | 说明 |
|---|---|
| 可缩放性 | 无限缩放不失真,完美适配 Retina 屏、4K 屏等高 DPI 设备 |
| 文本可读 | 文件本质为纯文本,可被版本控制系统追踪、人工编辑 |
| 语义化 | 支持 title、desc 等语义标签,提升可访问性(Accessibility) |
| 样式可控 | 可通过 CSS 控制颜色、描边、透明度等视觉属性 |
| 脚本交互 | 支持 JavaScript 操作 DOM,实现动态效果与用户交互 |
| 体积高效 | 对于图标、Logo、简单插图等场景,文件体积通常小于位图 |
| SEO 友好 | 搜索引擎可索引 SVG 内容(尤其是内联形式) |
二、SVG 的基本结构与语法
一个最简 SVG 文档如下:
xml
<svg xmlns="http://www.w3.org/2000/svg" width="100" height="100">
<circle cx="50" cy="50" r="40" fill="blue" />
</svg>
<svg>是根元素,必须声明命名空间xmlns="http://www.w3.org/2000/svg"(在 HTML5 中可省略)。- 内部可包含各种图形元素(如
<circle>、<rect>、<path>等)。 - 所有坐标和尺寸默认以"用户单位"(user units)表示,通常等同于 CSS 像素。
注意 :在 HTML 文档中直接嵌入 SVG 时,浏览器会自动处理命名空间,无需显式声明
xmlns。
三、SVG 在前端中的使用方式
根据项目需求和性能考量,SVG 有多种引入方式,各有优劣。
3.1 内联 SVG(Inline SVG)
直接将 SVG 代码嵌入 HTML 中:
html
<svg width="24" height="24" viewBox="0 0 24 24">
<path d="M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z" />
</svg>
✅ 优点:
- 零额外 HTTP 请求
- 可通过 CSS/JS 完全控制内部元素
- 支持主题切换(如动态修改
fill颜色) - 可添加 ARIA 属性提升可访问性
❌ 缺点:
- 增加 HTML 体积
- 不利于缓存复用(除非配合构建工具提取)
适用场景:网站 Logo、关键图标、交互式插图。
3.2 外部文件引用(<img> 标签)
html
<img src="logo.svg" alt="Company Logo" width="200" height="60">
✅ 优点:
- 浏览器自动缓存
- 使用简单,兼容性好
- 安全(隔离 DOM,防止 XSS)
❌ 缺点:
- 无法通过 CSS 修改内部样式 (如
fill颜色) - 无法绑定事件或执行脚本
- 无法利用 SVG 的语义能力(如
<title>)
适用场景:静态装饰图、非交互性插图。
3.3 <object> 或 <iframe> 引入
html
<object data="chart.svg" type="image/svg+xml" width="500" height="300"></object>
✅ 优点:
- 可通过 JS 访问内部 SVG DOM(需跨域策略允许)
- 支持脚本和动画
❌ 缺点:
- 使用复杂,需处理加载状态
- 兼容性略逊于
<img> - 无法继承父页面 CSS(作用域隔离)
适用场景:独立的 SVG 应用(如 D3.js 图表导出为 SVG 文件后嵌入)。
3.4 CSS 背景图
css
.icon-bg {
background: url('icon.svg') no-repeat center;
width: 24px;
height: 24px;
}
⚠️ 注意 :同 <img> 方式,无法修改内部颜色。若需变色,可考虑:
- 使用 CSS
filter(如filter: invert(1) hue-rotate(180deg),但精度有限) - 提供多色版本 SVG 文件
3.5 SVG Sprite(雪碧图)
这是现代前端图标系统的最佳实践。
步骤 1:定义符号库(通常放在 <body> 顶部或通过构建工具注入)
html
<svg style="display: none;" aria-hidden="true">
<symbol id="icon-home" viewBox="0 0 24 24">
<path d="M10 20v-6h4v6h5v-8h3L12 3 2 12h3v8z"/>
</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"/>
</symbol>
</svg>
步骤 2:使用图标
html
<svg class="icon" aria-labelledby="home-title">
<title id="home-title">首页</title>
<use href="#icon-home"></use>
</svg>
✅ 优势:
- 单次 HTTP 请求加载全部图标
- 支持 CSS 样式覆盖(如
.icon { fill: currentColor; }实现文字色同步) - 支持无障碍(通过
<title>和aria-labelledby) - 易于主题化和国际化
工具推荐 :svg-sprite、svgo、Vite/Rollup/Webpack 插件。
四、SVG 核心属性
SVG 属性按功能可分为以下几类。所有属性均区分大小写,且多数支持 CSS 动画。
4.1 通用属性(适用于所有 SVG 元素)
| 属性 | 类型 | 说明 |
|---|---|---|
id |
string | 唯一标识符,用于 <use> 引用或 JS/CSS 选择 |
class |
string | CSS 类名 |
style |
string | 内联样式(不推荐,优先用外部 CSS) |
transform |
string | 变换函数,如 translate(10,20) rotate(45) scale(1.5) |
4.2 几何与位置属性(按元素类型)
<rect>(矩形)
| 属性 | 必需 | 默认值 | 说明 |
|---|---|---|---|
x |
否 | 0 | 左上角 X 坐标 |
y |
否 | 0 | 左上角 Y 坐标 |
width |
是 | --- | 宽度(必须 >0) |
height |
是 | --- | 高度(必须 >0) |
rx, ry |
否 | 0 | 圆角半径(若只设 rx,则 ry = rx) |
<circle>(圆)
| 属性 | 必需 | 默认值 | 说明 |
|---|---|---|---|
cx |
否 | 0 | 圆心 X |
cy |
否 | 0 | 圆心 Y |
r |
是 | --- | 半径(必须 ≥0) |
<ellipse>(椭圆)
| 属性 | 必需 | 默认值 | 说明 |
|---|---|---|---|
cx, cy |
否 | 0 | 中心点 |
rx |
是 | --- | X 方向半径 |
ry |
是 | --- | Y 方向半径 |
<line>(直线)
| 属性 | 必需 | 说明 |
|---|---|---|
x1, y1 |
是 | 起点 |
x2, y2 |
是 | 终点 |
<polygon> / <polyline>
| 属性 | 说明 |
|---|---|
points |
一系列空格或逗号分隔的坐标对,如 "10,10 20,30 40,10" |
<path>(路径)------ 最强大
| 属性 | 说明 |
|---|---|
d |
路径数据字符串,由命令组成: - M x y:移动到 - L x y:画直线到 - C x1 y1 x2 y2 x y:三次贝塞尔曲线 - Q x1 y1 x y:二次贝塞尔曲线 - A rx ry x-axis-rotation large-arc-flag sweep-flag x y:椭圆弧 - Z:闭合路径 |
提示 :可使用 SVG Path Visualizer 调试路径。
4.3 样式属性(控制外观)
这些属性既可作为 XML 属性,也可通过 CSS 设置(CSS 优先级更高)。
| 属性 | 说明 | 示例值 |
|---|---|---|
fill |
填充颜色或引用(如渐变) | red, #00f, url(#grad1) |
fill-opacity |
填充透明度(0~1) | 0.5 |
stroke |
描边颜色 | black |
stroke-width |
描边宽度(用户单位) | 2 |
stroke-opacity |
描边透明度 | 0.8 |
stroke-linecap |
线端样式 | butt(平)、round(圆)、square(方) |
stroke-linejoin |
线连接样式 | miter(尖角)、round、bevel(斜角) |
stroke-dasharray |
虚线模式(实线, 间隙, ...) | "5,5" 或 "10 5 2 5" |
stroke-dashoffset |
虚线起始偏移 | 10 |
opacity |
整体透明度(影响 fill + stroke) | 0.7 |
✅ 最佳实践:将样式属性移至 CSS,便于维护和主题切换:
css.icon { fill: currentColor; stroke: none; }
4.4 视图与坐标系统属性(<svg> 根元素)
| 属性 | 说明 |
|---|---|
width, height |
渲染尺寸(可带单位:px, em, % 等) |
viewBox |
核心属性 !格式:"min-x min-y width height" 定义内部坐标系,实现响应式缩放 |
preserveAspectRatio |
控制 viewBox 如何适配容器 语法:`[align] [meet |
xmlns |
命名空间(HTML5 中可省略) |
示例:响应式 SVG
html
<svg viewBox="0 0 100 100" width="100%" height="auto">
<circle cx="50" cy="50" r="40" />
</svg>
此 SVG 将按比例缩放以填满父容器宽度,高度自适应。
4.5 文本相关属性(<text>, <tspan>)
| 属性 | 说明 |
|---|---|
x, y |
文本基线起始点(非左上角!) |
dx, dy |
相对于当前位置的偏移 |
text-anchor |
水平对齐:start(默认)、middle、end |
dominant-baseline |
垂直对齐:auto, middle, hanging, alphabetic 等 |
rotate |
单个字符旋转角度(用于 <tspan>) |
font-family, font-size, font-weight |
字体样式(建议用 CSS) |
xml
<text x="50%" y="50%" text-anchor="middle" dominant-baseline="middle">
Centered Text
</text>
4.6 渐变、图案与滤镜引用
渐变定义(置于 <defs> 中)
xml
<defs>
<linearGradient id="grad1" x1="0%" y1="0%" x2="100%" y2="0%">
<stop offset="0%" stop-color="red" />
<stop offset="100%" stop-color="blue" />
</linearGradient>
</defs>
<rect width="100" height="100" fill="url(#grad1)" />
滤镜(如阴影)
xml
<defs>
<filter id="shadow" x="-20%" y="-20%" width="140%" height="140%">
<feDropShadow dx="2" dy="2" stdDeviation="2" flood-color="#000" flood-opacity="0.3" />
</filter>
</defs>
<circle cx="50" cy="50" r="30" filter="url(#shadow)" />
⚠️ 注意:滤镜可能影响性能,慎用于大量元素。
4.7 交互与可访问性属性
虽然 SVG 支持 onclick 等内联事件,但强烈建议使用 JavaScript 事件监听器。
可访问性(Accessibility)
-
使用
<title>和<desc>提供语义描述:xml<svg> <title>公司 Logo</title> <desc>蓝色圆形内含白色字母 A</desc> <circle fill="blue" /> <text>A</text> </svg> -
对图标使用
aria-hidden="true"避免冗余朗读(若已有文字标签):html<button> <svg aria-hidden="true"><use href="#icon-search"/></svg> 搜索 </button>
五、SVG 动画与交互
5.1 CSS 动画(推荐)
css
.spinner {
animation: spin 1s linear infinite;
}
@keyframes spin {
to { transform: rotate(360deg); }
}
✅ 优势:性能好、易维护、可被
prefers-reduced-motion媒体查询控制。
5.2 JavaScript 操作
js
const svgEl = document.querySelector('#my-circle');
svgEl.addEventListener('click', () => {
svgEl.setAttribute('fill', 'green');
// 或使用 class 切换
svgEl.classList.toggle('active');
});
5.3 SMIL 动画(已弃用)
xml
<circle r="10">
<animate attributeName="r" values="10;20;10" dur="2s" repeatCount="indefinite" />
</circle>
❌ 警告 :Chrome 自 2015 年起已移除 SMIL 支持,不应在生产环境使用。
六、最佳实践
6.1 文件精简
使用 SVGO 工具自动移除:
- 注释、元数据
- 未使用的
<defs> - 冗余小数(如
10.000→10) - 默认属性值
bash
npx svgo icon.svg --pretty
6.2 避免过度复杂
- 单个 SVG 元素数建议 < 1000
- 复杂插图(如地图)考虑 Canvas 或 WebGL
6.3 响应式设计
始终设置 viewBox,并谨慎使用 width/height:
html
<!-- 推荐 -->
<svg viewBox="0 0 24 24" width="24" height="24">
<!-- 或完全响应式 -->
<svg viewBox="0 0 24 24" style="width: 100%; height: auto;">
6.4 主题与颜色管理
利用 currentColor 实现图标颜色继承文本色:
css
.icon { fill: currentColor; }
.dark-mode .icon { fill: white; }
6.5 可访问性保障
- 为交互元素添加
role="button"、tabindex - 提供
<title>或aria-label - 避免仅靠颜色传递信息
6.6 降级方案(Fallback)
针对 IE8 等旧浏览器:
html
<svg class="icon">
<use href="#icon-home"></use>
</svg>
<!--[if IE]>
<img src="icon-home.png" alt="Home" class="icon-fallback">
<![endif]-->
或使用 Modernizr 检测 SVG 支持。
七、典型应用场景对照表
| 场景 | 推荐方案 | 理由 |
|---|---|---|
| 网站 Logo | 内联 SVG | 清晰、可 SEO、可交互 |
| 图标系统 | SVG Sprite (<symbol> + <use>) |
高性能、易维护、支持主题 |
| 数据可视化 | D3.js + 内联 SVG | 精细控制、动态更新 |
| 加载动画 | 内联 SVG + CSS 动画 | 轻量、流畅、可中断 |
| 背景装饰 | <img> 或 CSS background |
简单、安全、缓存友好 |
| 打印内容 | 内联 SVG | 保证打印清晰度 |
八、常见问题解答
Q1:SVG 和 PNG/JPEG 有什么区别?什么时候该用 SVG?
答:
- SVG 是矢量图 ,由数学公式描述,无限缩放不失真;PNG/JPEG 是位图,由像素点构成,放大后会模糊或出现锯齿。
- 适用场景对比 :
- ✅ 用 SVG:图标、Logo、简单插图、图表、需要动态变色或动画的图形。
- ✅ 用 PNG/JPEG:照片、复杂绘画、带有丰富色彩渐变或噪点的图像。
一般规则:若图像由线条、色块、文字构成,优先选 SVG;若为真实照片或艺术渲染图,选位图。
Q2:为什么我用 CSS 修改不了 <img src="icon.svg"> 里的颜色?
答 :
当 SVG 通过 <img>、CSS background-image 或 <iframe> 引入时,它被视为外部资源 ,其内部 DOM 与主文档隔离,因此无法通过外部 CSS 修改 fill、stroke 等属性。
解决方案:
- 改用 内联 SVG 或 SVG Sprite(推荐);
- 若必须用
<img>,可准备多个颜色版本的 SVG 文件; - 使用 CSS
filter进行有限的颜色变换(如filter: brightness(0) saturate(100%) invert(1)可转黑白再着色),但精度和可控性较差。
Q3:viewBox 到底是什么?为什么它这么重要?
答 :
viewBox 是 SVG 实现响应式和缩放的核心属性。其格式为:
xml
viewBox="min-x min-y width height"
- 它定义了 SVG 内部的逻辑坐标系。
- 浏览器会将这个逻辑区域映射 到
<svg>元素的实际显示区域(由width/height或 CSS 决定)。 - 例如:
viewBox="0 0 24 24"表示图形绘制在 24×24 的逻辑网格中,无论你将其显示为 24px 还是 240px,图形都会等比缩放。
没有
viewBox的 SVG 无法正确响应式缩放,可能导致裁剪或变形。
Q4:如何让 SVG 图标跟随文字颜色变化?
答 :
使用 CSS 的 currentColor 关键字:
css
.icon {
fill: currentColor; /* 继承父元素的 color 值 */
stroke: none;
}
html
<span style="color: blue;">
<svg class="icon" viewBox="0 0 24 24">
<use href="#icon-star"></use>
</svg>
收藏
</span>
这样,图标颜色会自动与文字颜色保持一致,非常适合主题切换。
Q5:SVG 支持 SEO 吗?搜索引擎能"看到" SVG 内容吗?
答:
-
内联 SVG :是的。搜索引擎可以解析其中的文本、
<title>、<desc>等内容,有助于 SEO。 -
外部 SVG 文件(如
<img src="x.svg">) :通常不会被索引,因为搜索引擎将其视为普通图片资源。 -
建议 :对关键内容(如 Logo、品牌标识)使用内联 SVG,并添加语义标签:
xml<svg> <title>Acme 公司标志</title> <path ... /> </svg>
Q6:SVG 有安全风险吗?会被 XSS 攻击吗?
答:
- 内联 SVG :如果 SVG 内容来自用户输入且未经转义,可能存在 XSS 风险 (因 SVG 可包含
<script>、onload等)。 - 外部 SVG(
<img>) :安全,浏览器会将其作为纯图像处理,禁用脚本和事件。 - 防护建议 :
- 永远不要直接渲染未经净化的用户上传 SVG;
- 使用 DOMPurify 等库清理 SVG 内容;
- 对不可信来源的 SVG,优先使用
<img>引入。
Q7:如何优化 SVG 文件体积?
答:
- 手动精简 :删除注释、
<metadata>、未使用的<defs>; - 使用工具 :
- 构建集成:在 Vite、Webpack、Rollup 中配置 SVG 优化插件;
- 避免冗余精度 :将
10.000000简化为10; - 合并路径:使用矢量软件(如 Illustrator、Figma)导出时选择"合并路径"。
Q8:SVG 支持打印吗?打印效果如何?
答 :
支持,且效果极佳 。
由于 SVG 是矢量格式,打印时会以打印机的最高分辨率渲染,边缘锐利、无锯齿 ,远优于位图。
建议:在需要高质量打印的场景(如发票、报告、PDF 导出)中,优先使用内联 SVG。
九、拓展:SVG 路径测量与动态绘制核心 API
在 SVG 交互动画开发中,获取图形路径的几何信息是实现高级效果(如手绘动画、进度轨迹、元素沿路径运动)的关键。SVG DOM 原生提供了一组强大且高效的 JavaScript 方法,无需依赖第三方库即可完成精确控制。
9.1 核心方法概览
以下方法适用于所有可度量的 SVG 图形元素(包括 <path>、<line>、<polyline>、<polygon>、<rect>、<circle>、<ellipse>):
| 方法 | 说明 |
|---|---|
element.getTotalLength() |
返回路径的总长度(单位:用户单位) |
element.getPointAtLength(distance) |
返回距离路径起点 distance 处的 {x, y} 坐标对象 |
element.getTangentAtLength(distance) |
返回该点处的切线向量(用于对齐旋转方向) |
⚠️ 注意:非路径类元素(如
<g>、<text>、<image>)不支持这些方法。
9.2 典型应用:描边绘制动画(Stroke Drawing)
这是 getTotalLength() 最广泛的应用场景。通过结合 stroke-dasharray 与 stroke-dashoffset,可实现"路径逐步绘制"的视觉效果。
实现步骤:
- 获取路径总长度
L = path.getTotalLength() - 设置
stroke-dasharray: L L→ 实线与间隙均为全长,路径被完全隐藏 - 设置
stroke-dashoffset: L→ 初始偏移使路径不可见 - 动画将
stroke-dashoffset从L变为0,路径逐渐显现
代码示例:
html
<svg viewBox="0 0 200 100">
<path id="drawPath" d="M20,50 Q80,10 140,50 T260,50"
fill="none" stroke="#007acc" stroke-width="3" />
</svg>
<script>
const path = document.getElementById('drawPath');
const totalLength = path.getTotalLength();
// 隐藏初始路径
path.style.strokeDasharray = `${totalLength} ${totalLength}`;
path.style.strokeDashoffset = totalLength;
// 触发动画
path.animate(
{ strokeDashoffset: 0 },
{ duration: 1500, easing: 'ease-out', fill: 'forwards' }
);
</script>
✅ 最佳实践:
- 在
DOMContentLoaded或组件挂载后调用getTotalLength(),确保元素已渲染- 缓存长度值,避免重复计算
- 结合
@media (prefers-reduced-motion: reduce)提供无障碍降级
9.3 高级应用:元素沿路径运动
利用 getPointAtLength() 和 getTangentAtLength(),可让任意元素(如图标、文字、小球)精准沿 SVG 路径移动并自动对齐方向,常用于数据可视化(如地图轨迹、流程图节点动画)。
js
function moveAlongPath(element, path, progress) {
const length = path.getTotalLength();
const point = path.getPointAtLength(progress * length);
const tangent = path.getTangentAtLength(progress * length);
const angle = Math.atan2(tangent.y, tangent.x) * 180 / Math.PI;
element.style.transform = `translate(${point.x}px, ${point.y}px) rotate(${angle}deg)`;
}