<h1>开发者必备 SVG 手册:从入门到 Path 实战,图标、动画、自适应一次搞定</h1>
- 引言
SVG 绝对是浏览器里最有意思的技术之一!用它能实现超多酷炫效果,也是前端开发工具箱里绝对关键的一环。
先给大家快速看看前端开发用 SVG 做过的一些东西: (原文此处有示例图,实际场景可替换为自己的 SVG 作品展示)
不过 SVG 也挺让人望而生畏的——这玩意儿水很深,很容易看得眼花缭乱。
所以这篇文章里,我想把最核心的基础知识讲清楚,帮大家打个扎实的底子。既能让你明白 SVG 到底酷在哪,还能教你几个马上就能用的小技巧~ ✨
适用人群
本文不需要你有任何 SVG 基础,但默认你懂点前端基础知识(HTML/CSS/JS)就行。
- 初识 SVG
SVG(”可缩放矢量图形”,Scalable Vector Graphics)本质上是一种图像格式,就像 .jpg
或 .gif
一样。我们可以像用其他图片那样,把它塞进 <img>
标签里:
<img
alt="返回首页"
src="https://my.oschina.net/images/home.svg"
/>
这方法能跑通,但根本没体现出 SVG 的厉害之处。真正的”魔法”,要从内联 SVG 说起。
像 .jpg
这类图片格式都是二进制的——你用文本编辑器打开,看到的全是乱码。但 SVG 不一样,它用 XML 语法定义,跟 HTML 特别像!它不是给每个像素指定 RGB 颜色,而是包含了”绘制图像所需的一系列指令”。
更神奇的是,我们能直接把 SVG 源码丢进 HTML 文档里:
代码示例
<!-- index.html -->
<div class="wrapper">
<p>快看这个 SVG:</p>
<svg width="100" height="100">
<!-- 圆形:填充色 hotpink,半径 30,圆心在 (50,50) -->
<circle
fill="hotpink"
r="30"
cx="50"
cy="50"
/>
</svg>
</div>
<style>/* styles.css */
.wrapper {
padding: 20px;
font-family: "Microsoft YaHei", sans-serif;
}
</style>
HTML 给的”基础组件”都是跟文档相关的——段落、标题、列表,跟 Word 里的功能差不多。SVG 其实是一个道理,只不过它的”基础组件”全是用来画图的,比如 <circle>
(圆形)、<polygon>
(多边形)、<path>
(路径)这些。
最酷的是:SVG 是 DOM 里的”一等公民”!我们能用 CSS 和 JS 选择、修改 SVG 节点,就像操作 HTML 元素一样。
再看这个例子,鼠标 hover 时圆形会变大、下移:
<!-- index.html -->
<style>/* styles.css */
circle {
fill: hotpink;
/* 过渡动画:半径 400ms,y 坐标 600ms */
transition: r 400ms, cy 600ms;
}
/* 鼠标悬浮/聚焦时,半径变大到 50,y 坐标移到 100 */
button:hover circle,
button:focus-visible circle {
r: 50px;
cy: 100px;
}
button {
border: none;
background: transparent;
cursor: pointer;
}
</style>
<button>
<svg width="100" height="100">
<circle r="30" cx="50" cy="50" />
</svg>
</button>
很多 SVG 属性(比如圆形的填充色 fill
、半径 r
)其实也能当 CSS 属性用。这意味着我们能用 CSS 改它们,甚至加过渡动画!🤯
这才是 SVG 的威力所在——它就像一个”平行世界的 HTML”,专注于画图而非文档,而且我们能用已有的 CSS/JS 技能让它动起来。
手写 SVG
可能有点反直觉,但我经常在代码编辑器里写 SVG,而不是用 Illustrator 或 Figma 这类设计工具。
设计工具确实能导出 SVG,但它往往会把所有内容揉进一个 <path>
标签里。这样一来,想给单个元素做动画就难多了——而在我看来,这正是 SVG 最酷的地方。
当然也看场景:如果图形复杂度太高,用专业工具还是更实际。但像我之前展示的那些效果,手写代码反而更简单。
- 基本图形
就像刚才看到的,SVG 有自己的”绘图基础组件”。不是 <div>
或 <button>
,而是 <circle>
、<polygon>
这类图形。咱们一个个说。
3.1 直线(Lines)
最简单的图形大概就是 <line>
了:
<svg width="280" height="280">
<!-- 直线:起点 (80,80),终点 (200,200),描边色 oklch(0.9 0.3 164),描边宽 5 -->
<line
x1="80"
y1="80"
x2="200"
y2="200"
stroke="oklch(0.9 0.3 164)"
stroke-width="5"
/>
</svg>
属性说明:
x1
/y1
:直线的起点坐标x2
/y2
:直线的终点坐标
这事儿看着简单,但在 HTML 里还真不好实现——HTML 里画对角线,得弄个细长的 DOM 元素再旋转,要是想精确控制起点终点,还得算数学题。
但 SVG 里画直线就很简单:指定起点和终点,直线就出来了!
3.2 矩形(Rectangles)
矩形用 <rect>
标签,通过左上角坐标 x
/y
定位,再用 width
/height
控制大小:
<svg width="300" height="300">
<rect
x="60" <!-- 左上角 x 坐标 -->
y="100" <!-- 左上角 y 坐标 -->
width="180" <!-- 宽度 -->
height="100"<!-- 高度 -->
fill="none" <!-- 不填充颜色 -->
stroke="oklch(0.9 0.3 164)" <!-- 描边色 -->
stroke-width="5" <!-- 描边宽 -->
/>
</svg>
跟 HTML <div>
的区别
乍一看像带边框的 <div>
,但有两个核心区别:
- SVG 描边是画在”路径正中间”的,不是内侧或外侧——而且这没法配置(所有图形都这样)。
- 如果把
width
或height
设为 0,矩形会直接消失!SVG 规范里把这种情况叫”退化图形”(听着有点狠)——像矩形这种二维图形,一旦只剩一个维度,就会被判定为”无效”,不渲染。
以前不同浏览器表现还不一致,有的会渲染”退化图形”,有的不会。好在现在所有现代浏览器都遵循规范了。
圆角矩形
还能用 rx
/ry
给矩形加圆角,类似 CSS 的 border-radius
:
<svg width="340" height="340">
<rect
x="80"
y="100"
width="500"
height="500"
rx="100" <!-- 水平圆角半径 -->
ry="50" <!-- 垂直圆角半径 -->
stroke="green"
stroke-width="5"
fill="none"
/>
</svg>
3.3 圆形(Circles)
圆形用 <circle>
标签,大小由半径 r
控制,位置由圆心坐标 cx
/cy
控制:
<svg width="280" height="280">
<circle
cx="140" <!-- 圆心 x 坐标 -->
cy="140" <!-- 圆心 y 坐标 -->
r="70" <!-- 半径 -->
fill="none"
stroke="oklch(0.9 0.3 164)"
stroke-width="5"
/>
</svg>
跟矩形一样:如果把 r
设为 0,圆形也会消失。
3.4 椭圆(Ellipses)
椭圆 <ellipse>
其实就是”可拉伸的圆形”——水平半径和垂直半径可以设不同值,用来画椭圆:
<svg width="300" height="300">
<ellipse
cx="150" <!-- 圆心 x 坐标 -->
cy="150" <!-- 圆心 y 坐标 -->
rx="75" <!-- 水平半径 -->
ry="50" <!-- 垂直半径 -->
fill="none"
stroke="oklch(0.9 0.3 164)"
stroke-width="5"
/>
</svg>
3.5 多边形(Polygons)
<polygon>
能画多角形,比如五边形、星形这些:
<svg width="300" height="300">
<polygon
points="
60,100 <!-- 点 1 -->
100,180 <!-- 点 2 -->
140,140 <!-- 点 3 -->
180,180 <!-- 点 4 -->
220,100 <!-- 点 5 -->
"
fill="none"
stroke="oklch(0.9 0.3 164)"
stroke-width="3"
/>
</svg>
关键属性 points
points
里填一系列 X/Y 坐标,浏览器会把这些点连起来,最后还会把”最后一个点”和”第一个点”连起来,形成闭合图形。
我刚开始学的时候还懵过:我以为”多边形”特指三角形、正方形、六边形这种”对称图形”(规范里叫”正多边形”),但其实正多边形只是多边形的一种。
画正多边形(需要 JS)
想画正多边形得用三角函数,这里给个可直接运行的示例(国内 Playground 可用,需引入 Lodash):
<!-- index.html -->
<svg class="parent-svg" width="400" height="400">
<polygon class="mister-polygon" fill="none" stroke="green" stroke-width="3" />
</svg>
<!-- 引入 Lodash(国内 CDN) -->
<script src="https://cdn.jsdelivr.net/npm/lodash@4.17.21/lodash.min.js"></script>
<script src="./index.js"></script>
// index.js
const svg = document.querySelector('.parent-svg');
const polygon = document.querySelector('.mister-polygon');
// 可修改:边数、半径
const numOfSides = 8; // 8 边形
const radius = 80; // 外接圆半径
function drawPolygon() {
const svgWidth = Number(svg.getAttribute('width'));
const svgHeight = Number(svg.getAttribute('height'));
const cx = svgWidth / 2; // SVG 中心 x 坐标
const cy = svgHeight / 2; // SVG 中心 y 坐标
// 生成每个顶点的坐标
const points = _.range(numOfSides).map((index) => {
// 旋转偏移:让偶数边图形(如六边形、八边形)"边朝上",而非"角朝上"
const rotationOffset = numOfSides % 2 === 0 ? Math.PI / numOfSides : 0;
// 计算当前顶点的角度
const angle = (index * 2 * Math.PI) / numOfSides - Math.PI / 2 + rotationOffset;
// 极坐标转直角坐标
const x = cx + radius * Math.cos(angle);
const y = cy + radius * Math.sin(angle);
return `${x},${y}`;
});
// 设置多边形的顶点
polygon.setAttribute('points', points.join(' '));
}
drawPolygon();
3.6 路径(Paths)——SVG 的”万能画笔”
前面聊的 circle、rect 都是”基础款”图形,但如果想画更复杂的东西——比如自定义图标、logo、甚至手写字体,就得靠 SVG 里的”万能选手”:<path>
元素了。
<path>
是 SVG 中最灵活、也最强大的图形元素,没有之一。它不像其他形状有固定的”轮廓”,而是靠一串”绘图指令”控制——就像给画笔写了个”行动脚本”,让它按步骤画出你想要的任何形状。
3.6.1 先搞懂:<path>
怎么工作?
<path>
的核心是 d
属性(”d” 代表 “data” 或 “drawing”),所有绘图指令都写在 d
里。这些指令由”字母+数字”组成,比如 M10 20 L30 40
,就像在告诉画笔:”先挪到 (10,20),再画直线到 (30,40)”。
有个关键点要记:指令字母分大小写——
- 大写字母(如
M
、L
、C
):用「绝对坐标」(相对于 SVG 画布的原点 (0,0)); - 小写字母(如
m
、l
、c
):用「相对坐标」(相对于当前画笔的位置)。
刚开始建议先用大写字母,不容易搞混,熟悉后再用小写简化代码。
3.6.2 常用绘图指令:从简单到复杂
咱们按”难度梯度”拆解常用指令,每个指令配小示例,直接复制到 CodeSandbox 就能跑~
(1)基础指令:移动与直线(画折线、多边形)
先掌握最基础的 3 个指令,就能画各种直线组成的图形:
| 指令 | 含义 | 语法示例 | 说明 | | —- | —————— | ————————– | —————————————- | | M
| 移动画笔(不画线) | M x y
或 M x1 y1 x2 y2
| 把画笔挪到指定位置,准备开始画 | | L
| 画直线 | L x y
或 L x1 y1 x2 y2
| 从当前位置画直线到指定位置 | | Z
| 闭合路径(收尾) | Z
(无参数) | 从当前位置画直线回路径起点,形成闭合图形 |
示例:用 M
/L
/Z
画一个三角形
<svg width="300" height="300" viewBox="0 0 300 300">
<!-- path 画三角形:移动到(150,50) → 直线到(250,200) → 直线到(50,200) → 闭合 -->
<path
d="M 150 50 L 250 200 L 50 200 Z"
fill="none"
stroke="hotpink"
stroke-width="3"
/>
</svg>
效果:一个粉色描边的三角形,Z
指令自动把最后一个点(50,200)连回起点(150,50),不用手动写 L 150 50
。
(2)简化直线指令:H
(水平直线)和 V
(垂直直线)
如果要画水平或垂直直线,不用写 L
,用 H
(Horizontal)和 V
(Vertical)更方便:
| 指令 | 含义 | 语法示例 | 说明 | | —- | ———- | ——– | ————————————— | | H
| 画水平直线 | H x
| 从当前位置画水平直线到 x 坐标(y 不变) | | V
| 画垂直直线 | V y
| 从当前位置画垂直直线到 y 坐标(x 不变) |
示例:用 H
/V
画一个矩形(替代 <rect>
)
<svg width="300" height="300" viewBox="0 0 300 300">
<!-- 移动到(50,50) → 水平右移到250 → 垂直下移到200 → 水平左移到50 → 闭合 -->
<path
d="M 50 50 H 250 V 200 H 50 Z"
fill="none"
stroke="oklch(0.9 0.3 164)"
stroke-width="3"
/>
</svg>
效果和 <rect x="50" y="50" width="200" height="150">
一样,但 path
更灵活——比如想在矩形右边加个小凸起,直接加指令就行。
(3)进阶指令:曲线(画平滑图形)
基础指令只能画直线,想画圆、椭圆、波浪线、爱心这些带弧度的图形,就得用曲线指令。最常用的是「贝塞尔曲线」指令,咱们重点讲两个:C
(三次贝塞尔曲线)和 Q
(二次贝塞尔曲线)。
① 二次贝塞尔曲线 Q
:简单曲线(一个控制点)
二次贝塞尔曲线需要 1 个”控制点”和 1 个”终点”,控制点决定曲线的”弯曲方向”和”程度”,就像用手指按住绳子中间掰弯它。
| 指令 | 含义 | 语法示例 | 说明 | | —- | ————– | ————- | ———————————- | | Q
| 二次贝塞尔曲线 | Q cx cy x y
| cx,cy = 控制点坐标;x,y = 终点坐标 |
示例:用 Q
画一个笑脸的嘴巴
<svg width="200" height="200" viewBox="0 0 200 200">
<!-- 笑脸的眼睛(两个小圆) -->
<circle cx="70" cy="80" r="10" fill="black" />
<circle cx="130" cy="80" r="10" fill="black" />
<!-- 嘴巴(二次贝塞尔曲线):起点(60,120) → 控制点(100,150) → 终点(140,120) -->
<path
d="M 60 120 Q 100 150 140 120"
fill="none"
stroke="black"
stroke-width="5"
/>
</svg>
试着改控制点 100 150
为 100 100
,会发现嘴巴从”微笑”变成”撇嘴”——控制点越远,曲线越弯曲。
② 三次贝塞尔曲线 C
:复杂曲线(两个控制点)
三次贝塞尔曲线需要 2 个”控制点”和 1 个”终点”,能画出更复杂的弧度,比如 S 形、波浪线,常用于 logo 或自定义图形。
| 指令 | 含义 | 语法示例 | 说明 | | —- | ————– | ———————– | ———————————————————- | | C
| 三次贝塞尔曲线 | C cx1 cy1 cx2 cy2 x y
| cx1,cy1 = 第一个控制点;cx2,cy2 = 第二个控制点;x,y = 终点 |
示例:用 C
画一个爱心
爱心是经典的 path
案例,用两个三次贝塞尔曲线就能实现:
<svg width="200" height="200" viewBox="0 0 200 200">
<path
d="M 100 40 C 140 0 180 60 100 120 C 20 60 60 0 100 40 Z"
fill="hotpink"
stroke="none"
/>
<!-- 拆解指令:
1. M 100 40 → 起点(爱心顶部)
2. C 140 0 180 60 100 120 → 右半颗心(两个控制点:(140,0) 拉右上,(180,60) 拉右下,终点(100,120)是底部)
3. C 20 60 60 0 100 40 → 左半颗心(控制点:(20,60) 拉左下,(60,0) 拉左上,终点回到起点)
4. Z → 闭合路径(其实这里C的终点已经是起点,Z可省略,但加上更规范)
-->
</svg>
复制到 CodeSandbox 就能看到粉色爱心,改控制点的坐标(比如把 140 0
改成 150 10
),爱心的形状会跟着变,大家可以多试几次感受规律。
(4)实用技巧:简化曲线指令(S
和 T
)
如果连续画多条贝塞尔曲线,比如画波浪线,每次都写全 C
或 Q
会很麻烦。SVG 提供了 S
(三次贝塞尔曲线简化版)和 T
(二次贝塞尔曲线简化版),能自动继承上一个控制点的”对称点”,少写一半参数。
比如用 S
画连续的 S 形曲线:
<svg width="300" height="150" viewBox="0 0 300 150">
<!-- 第一条C曲线:M 20 75 C 50 25 100 125 150 75
第二条S曲线:S 200 25 280 75(自动继承上一个控制点(100,125)的对称点(200,25)) -->
<path
d="M 20 75 C 50 25 100 125 150 75 S 200 25 280 75"
fill="none"
stroke="oklch(0.9 0.3 164)"
stroke-width="3"
/>
</svg>
效果是一条平滑的 S 形曲线,S
指令只需要写”第二个控制点”和”终点”,比连续写 C
简洁多了。
3.6.3 <path>
的实战:动画与优化
<path>
不仅能画复杂图形,还能结合之前讲的”动画描边”,实现更酷的效果。另外,咱们也聊聊 path
代码的优化技巧——毕竟手写 path
容易乱,工具导出的 path
又可能冗余。
(1)实战:让 <path>
自绘制(爱心动画)
延续之前”SVG 自绘制”的思路,给爱心 path
加个”逐渐画出来”的动画,核心还是用 stroke-dasharray
、stroke-dashoffset
和 getTotalLength()
:
<!-- index.html -->
<svg width="200" height="200" viewBox="0 0 200 200">
<path
id="heart-path"
d="M 100 40 C 140 0 180 60 100 120 C 20 60 60 0 100 40 Z"
fill="none"
stroke="hotpink"
stroke-width="3"
/>
</svg>
<script>
// 1. 获取path元素
const heartPath = document.getElementById('heart-path');
// 2. 计算path的总长度(关键:getTotalLength() 对path同样有效)
const pathLength = heartPath.getTotalLength();
// 3. 初始设置:让虚线"藏起来"
heartPath.style.strokeDasharray = `${pathLength}, 10000`; // 实线=总长,空白=很大
heartPath.style.strokeDashoffset = pathLength; // 偏移=总长,实线偏移到看不见
// 4. 页面加载后触发动画:实线逐渐显示
window.addEventListener('load', () => {
heartPath.style.transition = 'stroke-dashoffset 2000ms ease-in-out';
heartPath.style.strokeDashoffset = 0; // 偏移归0,画出爱心
});
</script>
复制到 CodeSandbox 运行,会看到粉色的爱心”一笔画”出来,和之前多边形的自绘制逻辑完全一致——这就是 SVG 动画的通用性!
(2)优化技巧:让 <path>
代码更易读
不管是手写还是工具导出的 path
,d
属性里的数字堆在一起都像”乱码”。其实我们可以给 d
加格式,就像之前优化 polygon
的 points
一样:
<!-- 优化前:一堆数字挤在一起,难读 -->
<path d="M100 40 C140 0 180 60 100 120 C20 60 60 0 100 40 Z" />
<!-- 优化后:按指令换行,加注释,一目了然 -->
<path d="
M 100 40 <!-- 起点:爱心顶部 -->
C 140 0 180 60 <!-- 右半心:控制点1(140,0),控制点2(180,60) -->
100 120 <!-- 右半心终点:爱心底部 -->
C 20 60 60 0 <!-- 左半心:控制点1(20,60),控制点2(60,0) -->
100 40 <!-- 左半心终点:回到起点 -->
Z <!-- 闭合路径 -->
" />
SVG 会忽略 d
里的空格和换行,所以放心加格式——队友和未来的你会感谢这份清晰!
(3)工具导出 path
的注意事项
如果用 Figma/Illustrator 画复杂图形(比如 logo),导出 SVG 时会自动生成 path
,但可能有冗余代码(比如多余的节点、重复指令),建议做两步优化:
- 简化路径 :Figma 里选图形 → 右键 → “简化路径”(Reduce Points),减少节点数量,让
d
更短; - 清理代码 :用在线工具(比如 SVGOMG:https://svgomg.net/,国内可访问)去除冗余属性(如
fill-opacity="1"
这种默认值),让代码更干净。
3.6.4 <path>
的使用场景总结
现在你应该明白为什么 <path>
是 SVG 的”万能画笔”了吧?它的核心优势是”无所不能”,适合这些场景:
- 自定义图标(比如 App 里的特殊按钮、导航图标);
- 复杂图形(比如 logo、插画、手写字体);
- 动态生成的图形(比如数据可视化里的折线图、雷达图,用 JS 动态拼接
d
指令)。
虽然 path
的指令看起来多,但常用的也就 M
/L
/Z
/Q
/C
这几个,多画几个小例子(比如箭头、星星、咖啡杯),很快就能上手~
SVG 代码格式化
不管是 <polygon>
的 points
还是 <path>
的 d
,多余的空格和换行在 SVG 规范里都是”可选的”。以前这么写是为了优化文件大小,但现在服务器都用 gzip 压缩了,多几个符号对体积影响微乎其微。
所以强烈建议大家给 SVG 加格式!用户看不出差别,但同事(还有未来的你)会感谢你写的”可读 SVG”。
- 可缩放 SVG
之前咱们用的都是”绝对坐标”——SVG 必须是固定大小,否则就会出问题。比如这个例子:
<!-- 固定 300x220 的 SVG,圆形在中心 -->
<svg width="300" height="220">
<circle
cx="150"
cy="110"
r="60"
stroke="#FFD700"
stroke-width="10"
fill="none"
/>
</svg>
如果把 SVG 宽度改小,圆形不会缩小,反而会被截断——这跟普通图片不一样(jpg 会跟着容器缩放)。
笨办法:用 JS 动态计算
有一种解法是”根据容器宽度动态改所有属性”——比如默认宽度 300px,要是容器只有 150px,就把 cx
、r
这些值都乘以 0.5。但这太麻烦了,哪怕一个简单图形都要写一堆计算逻辑。
好办法:用 viewBox
属性
这才是 SVG 缩放的精髓!给 SVG 加个 viewBox
,就能定义”内部坐标系”——图形不再用 DOM 的像素值,而是用这个内部坐标系:
<!-- 容器宽度可改,但图形会自动缩放 -->
<svg
width="300" <!-- 外部容器宽度(可改) -->
viewBox="0 0 300 220" <!-- 内部坐标系:x0,y0 到 x300,y220 -->
>
<circle
cx="150" <!-- 内部坐标:中心在 (150,110) -->
cy="110"
r="60"
stroke="#FFD700"
stroke-width="10"
fill="none"
/>
</svg>
viewBox
怎么理解?
viewBox
接受 4 个值,可拆成两组来看:
- 前两个值(x, y):控制”视野起点”——相当于在 SVG 的”无限画布”上移动视野,看不同区域。
- 后两个值(width, height):控制”视野大小”——相当于”缩放级别”,不改变 SVG 外部尺寸,只改变内部图形的显示比例。
举个例子:
- 如果 SVG 外部尺寸是 300×300,
viewBox="0 0 300 300"
:内部坐标和像素 1:1,图形正常显示。 - 如果
viewBox="0 0 150 150"
:视野只看内部 150×150 的区域,相当于把图形放大 2 倍(因为外部还是 300×300)。
这就像浏览器的缩放功能(Ctrl +):窗口大小没变,但内容放大了。
为什么要用 viewBox
?
实际开发里,我们一般把 viewBox
设为固定值——这样不管 SVG 外部尺寸怎么变,显示的内容始终一致。比如同一个图标,在按钮上用 24×24,在导航栏用 48×48,只要 viewBox
相同,图形就不会变形。
而且矢量图的优势就是”无限缩放不失真”——位图(jpg/png)放大后会出像素块,但 SVG 是数学指令,放大 10 倍、100 倍都依然清晰!
- 表现属性
SVG 图形可以用 fill
填色,用 stroke
画边框,也可以两者都用。fill
很好理解,咱们重点说 stroke
——它有点像 HTML 的 border
,但功能强多了。
先看个示例,切换不同选项看看 stroke
能玩出什么花样:
<svg viewBox="0 0 200 200">
<circle
cx="100"
cy="100"
r="50"
fill="none"
stroke="hsl(45deg 100% 50%)" <!-- 描边色:金色 -->
stroke-width="6px" <!-- 描边宽 -->
stroke-dasharray="20, 14" <!-- 虚线:20px 实线 + 14px 空白 -->
stroke-linecap="butt" <!-- 端点样式:无(默认) -->
/>
</svg>
常用 stroke
属性说明
| 属性名 | 作用 | | —————— | ———————————————————— | | stroke
| 描边颜色,默认透明(transparent) | | stroke-width
| 描边宽度,单位像素 | | stroke-dasharray
| 虚线样式:传多个值,分别代表”实线长度”和”空白长度”,循环重复。比如 10,20
是”10px 实线+20px 空白”,5,3,2
是”5px+3px空白+2px+3px空白” | | stroke-linecap
| 描边端点样式:butt
(无,默认)、round
(圆形端点)、square
(方形端点) |
这些属性既可以写在 SVG 标签里( inline 属性),也可以用 CSS 控制——比如把 stroke-width="5"
改成 style="stroke-width: 5px;"
。
- 动画描边
既然 stroke
相关属性能当 CSS 属性用,那肯定能加动画!
比如给描边加过渡效果,鼠标 hover 时变色、变宽:
circle {
stroke: hsl(45deg 100% 50%);
stroke-width: 6px;
/* 过渡动画:所有 stroke 属性都加动画 */
transition: stroke 1200ms, stroke-width 900ms, stroke-dasharray 1500ms;
}
circle:hover {
stroke: hsl(164deg 100% 40%); /* hover 时变绿色 */
stroke-width: 8px;
stroke-dasharray: 10, 5; /* hover 时虚线更密集 */
}
6.1 用 stroke-dashoffset
做动画
stroke-dashoffset
是个超有用的属性——它能让虚线”偏移滑动”。比如让虚线绕着图形跑:
<svg viewBox="0 0 200 200">
<rect
x="50"
y="50"
width="100"
height="100"
fill="none"
stroke="oklch(0.9 0.25 164)"
stroke-width="5"
stroke-dasharray="10, 10" <!-- 10px 实线 + 10px 空白 -->
stroke-dashoffset="0" <!-- 初始偏移 0 -->
style="animation: casinoLights 400ms linear infinite;"
/>
</svg>
<style>
/* 动画:让虚线偏移滑动 */
@keyframes casinoLights {
from { stroke-dashoffset: 0; }
to { stroke-dashoffset: 20; } /* 偏移量 = 实线+空白(10+10),无缝循环 */
}
</style>
关键技巧:无缝循环
要让动画不”跳帧”,stroke-dashoffset
的目标值要等于”实线长度 + 空白长度”(比如上面的 10+10=20)。
6.2 模拟”SVG 自绘制”效果
这是最经典的 SVG 动画之一——让图形像”被画出来”一样逐渐显示。原理很简单:
- 让
stroke-dasharray
的”实线长度”等于图形的”路径总长”,”空白长度”设得很大(比如 10000)。 - 初始时让
stroke-dashoffset
等于”路径总长”(虚线偏移到看不见)。 - 动画时把
stroke-dashoffset
减到 0,实线逐渐显示出来。
示例代码(以多边形为例,path
动画见 3.6.3 节):
<svg viewBox="0 0 280 320">
<!-- 这里用三角形举例,实际可替换成任意图形 -->
<polygon
id="triangle"
points="140,20 260,220 20,220"
fill="none"
stroke="oklch(0.9 0.3 164)"
stroke-width="3"
/>
</svg>
<script>
// 1. 获取图形元素
const triangle = document.getElementById('triangle');
// 2. 计算图形的路径总长(关键方法:getTotalLength())
const pathLength = triangle.getTotalLength();
// 3. 设置虚线:实线长度 = 路径总长,空白长度 = 10000(足够大)
triangle.style.strokeDasharray = `${pathLength}, 10000`;
// 4. 初始偏移 = 路径总长(虚线看不见)
triangle.style.strokeDashoffset = pathLength;
// 5. 触发动画(比如页面加载后)
setTimeout(() => {
triangle.style.transition = 'stroke-dashoffset 3000ms';
triangle.style.strokeDashoffset = 0; // 偏移到 0,图形逐渐显示
}, 500);
</script>
简化方案:用 pathLength
属性
如果不想用 JS 计算路径总长,可以给 SVG 标签加 pathLength
属性,手动定义”路径总长”(比如设为 100):
<svg viewBox="0 0 280 320">
<polygon
points="140,20 260,220 20,220"
fill="none"
stroke="oklch(0.9 0.3 164)"
stroke-width="3"
pathLength="100" <!-- 手动定义路径总长为 100 -->
style="
stroke-dasharray: 100, 10000;
stroke-dashoffset: 100;
transition: stroke-dashoffset 3000ms;
"
/>
</svg>
<script>
// 直接改偏移量即可,不用计算真实长度
setTimeout(() => {
document.querySelector('polygon').style.strokeDashoffset = 0;
}, 500);
</script>
这个方法更简单,尤其适合动态生成的图形——但本质是”让浏览器假装路径总长是 100″,实际长度没变,只是计算简化了。
- SVG 的强大之处
这篇文章帮大家把 SVG 的核心基础讲透了:从简单的 line、rect,到万能的 path 元素,再到可缩放、表现属性和动画描边,足够支撑你做很多实际需求。但 SVG 的玩法远不止这些——比如滤镜(Filter)、蒙版(Mask)、渐变(Gradient),还有结合 JS 做数据可视化(比如用 D3.js 生成动态图表),这些都是更进阶的方向。
我目前正在做一门关于”趣味动画”的综合课程,SVG 是课程的核心内容。我做前端快 20 年了,在动画上踩过很多坑,也总结了不少技巧,希望能在课程里把这些”秘籍”都分享给大家!
这门课计划几个月后开启”抢先体验”,想了解更新的话,可以在这里订阅(原文此处有订阅链接,国内可替换为公众号/课程平台链接):
最后:动手试试!
SVG 最忌讳”只看不动手”——建议你现在就打开 CodeSandbox,试着用今天学的知识画一个小作品:比如用 path
画个咖啡杯,加个 viewBox
让它自适应,再给杯柄加个描边动画。哪怕图形很简单,亲手实现后你对 SVG 的理解会完全不一样~
</div>