后台收到不少留言问我:SVG 到底有什么好?说实话,作为一个写了好几年前端、平时也免不了画图做方案的开发者,SVG 是我最常用又最容易被低估的工具。很多人一听到“SVG 图片”,脑子里就是“不就是矢量图嘛,放大不糊”。但真正用起来,SVG 远不止是“不糊的位图”。它本质是一段可编程的 XML 文本,放到浏览器里会被解析成 DOM 节点,可以直接用 CSS 控制样式、用 JavaScript 操作结构、用 SMIL 做动画,甚至在服务端生成、在移动端转成 Kotlin 的 ImageVector。这篇文章就把 SVG 的语法、代码、实战场景一次性理清楚,尽量让新手看完能上手,老手也能找到几个平时容易忽略的点。
1. 先想清楚:SVG 到底是什么,凭什么比位图强
1.1 剥开 SVG 的外壳:一段有结构的文本
SVG 的全称是 Scalable Vector Graphics,可缩放矢量图形。标准由 W3C 维护,和 HTML、CSS 同属一个生态体系。它的核心思想不是记录“每个像素的颜色”,而是记录“图形的几何描述”:这个圆圆心在哪、半径多少,这条线从哪个坐标画到哪个坐标,这块区域填充什么颜色。
所以一张 SVG 图片本质上就是一个 XML 文档。比如一个最简单的红色圆:
<svg width="100" height="100" xmlns="http://www.w3.org/2000/svg"> <circle cx="50" cy="50" r="40" fill="red" /> </svg>这段代码放到任意支持 SVG 的浏览器里就能渲染出一个红色圆。也正因为它是文本而不是二进制,SVG 可以被人直接阅读和修改,可以被版本管理工具追踪 diff,可以被脚本动态生成。这一点是 PNG/JPG 永远做不到的:你没法用一个正则去改 JPG 里的某个圆,但你可以很轻松地改 SVG 里的 circle 标签。
这个特性带来几个直接好处:SEO 友好(内容可以被搜索引擎读取)、压缩率高(gzip 之后文本体积很小)、无障碍友好(可以加 title 和 desc 标签描述内容)。很多图标字体、地图标注、数据图表背后其实都是 SVG 在支撑。
1.2 选型对比:SVG 与 Canvas、PNG 的分工
很多人上来就问“SVG 和 Canvas 哪个好”,这个问题本身就有问题。它俩不是替代关系,而是拿着不同工具的工种。
Canvas 是画布式渲染,你拿到一块矩形区域,用 JavaScript 在上面画点、线、面,画完就画完了,内部不会保留图形对象。它适合高频率重绘的场景,比如游戏、粒子特效、视频处理,几千上万帧画面要跑满 60fps,Canvas 的“一次性绘制”模型有明显优势。代价是“无状态”:你想让某个刚才画过的圆变颜色,你得重绘整个画面,哪怕只改了一个元素。
SVG 是保留型的 DOM 结构,每个图形都是一个节点,你能随时拿到它、改它、删除它,浏览器会自动帮你重绘受影响区域。它天然适合“图形数量中等但交互性强”的场景:鼠标 hover 变色、点击选中等,逻辑写起来非常直白,不用自己维护一份数据再整体重绘。
再看 PNG/JPG 这类位图,优势是照片级表现了:细腻的渐变、真实的噪点、复杂的纹理,这些用 SVG 表达成本极高。但是位图放大到超过原始尺寸就会模糊,而且体积随尺寸线性增长。一张 1920×1080 的截图可能几 MB,而同样的 UI 界面用 SVG 描述可能只要几十 KB。
我的建议是直接看场景选型,不需要纠结谁强谁弱:
| 需求 | 推荐方案 | 原因 |
|---|---|---|
| 图标、Logo、简单插画 | SVG | 体积小、清晰度高、可配色可动画 |
| 数据图表、图表交互 | SVG | 节点可绑定事件、易于局部更新 |
| 地图与导览系统 | SVG | 区域可划分、可点击、可缩 |
| 照片、复杂纹理 | 位图 | 渲染 fidelity 和编写成本综合考虑 |
| 大规模粒子、游戏 | Canvas/WebGL | 重绘性能 |
2. 手写第一张 SVG:基础语法与坐标体系
2.1 坐标系与 viewBox:理解“缩放”的本质
SVG 的坐标系和数学里的平面直角坐标系长得很像,但注意一个区别:y 轴方向是向下的。左上角是 (0,0),往右 x 增大,往下 y 增大。这点和 Canvas、DOM 的坐标体系一致,但和很多人中学数学里的坐标系相反,初次接触的人容易把圆画到了屏幕外面,这不是 bug,是坐标系方向问题。
viewBox 是 SVG 里最重要的属性之一,它的格式是四个数字:
viewBox="minX minY width height"前两个数字是可视区域左上角的逻辑坐标,后两个数字是可视区域的逻辑宽高。配合 width 和 height 属性,viewBox 决定的是“怎么把逻辑坐标映射到实际渲染尺寸”。
我用一个例子说明。下面这个 SVG 实际尺寸是 200×200,但 viewBox 是 0 0 100 100,意味着逻辑上 100 个单位的图形会被拉大到 200 像素宽:
<svg width="200" height="200" viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg"> <circle cx="50" cy="50" r="40" fill="#4A90D9" /> </svg>这时候你改变 width 和 height,图形会跟着等比伸缩,但内容比例不变。这就是 SVG “响应式”的基础。反过来,如果你不写 viewBox 只写 width/height,那 width/height 就是画布的真实大小,里面的坐标直接按像素算,放大时就会失真——虽然 SVG 是矢量格式,但如果你用绝对像素硬撑,还是会糊。
实战经验:做图标的时候,我习惯统一设viewBox="0 0 24 24"或0 0 48 48,这样放到不同尺寸环境下只需要设置 width 和 height,里面的坐标不用动。团队协作时也统一一个 scope,避免一人一套坐标。
2.2 基础图元速查:从矩形到多边形
SVG 提供了很多开箱即用的基本图形标签,我把高频的几个列出来,每个配一个最小可运行示例。
矩形 rect:
<rect x="10" y="10" width="80" height="50" rx="8" fill="#F5A623" />x/y 是左上角坐标,width/height 是宽高,rx/ry 是圆角半径。做按钮背景、卡片圆角底的时候非常方便。
圆形 circle:
<circle cx="50" cy="50" r="30" fill="#7ED321" />cx/cy 是圆心坐标,r 是半径。做头像、状态点、气泡坐标点都用它。
椭圆 ellipse:
<ellipse cx="50" cy="50" rx="40" ry="20" fill="#9013FE" />rx 是水平半径,ry 是垂直半径。比 circle 多了一个轴上的控制。
线条 line:
<line x1="0" y1="0" x2="100" y2="80" stroke="#000" stroke-width="2" />注意线没有 fill,只有 stroke,stroke-width 定义线宽。很多新手只写 stroke 忘写 stroke-width,结果线粗得离谱或者细到看不见。
折线 polyline:
<polyline points="0,0 50,20 100,80 150,40" fill="none" stroke="#34B8D1" />points 是一串坐标对,能画折线图、走势图。
多边形 polygon:
<polygon points="50,10 90,90 10,90" fill="#F8E71C" stroke="#333" stroke-width="1" />polygon 和 polyline 的区别在于 polygon 会自动闭合最后一条边,适合画三角形、六边形、不规则区域。
组标签 g 很重要:
<g fill="none" stroke="#333"> <circle cx="30" cy="30" r="20" /> <circle cx="70" cy="30" r="20" /> </g>g 的作用是分组:对一组元素统一设置样式、统一做 transform 变换,还能配合 defs 做复用。想对一个区块里的所有子元素做位移、旋转,直接对 g 下手,别一个元素一个元素地改。
2.3 path 路径:SVG 的“万能画笔”
前几个标签都是快捷方式,真正让 SVG 表达力达到顶峰的是 path。它用一系列命令描述画笔轨迹:移动到某个点、画直线、画曲线、画弧线,最后闭合。只要你能想象出来的图形,理论上 path 都能画。
path 的 d 属性是关键,里面是一连串命令加坐标:
M 10 10 // move to,把画笔移动到 (10,10) L 50 10 // line to,画直线到 (50,10) C 70 10 90 50 100 80 // cubic bezier 三次贝塞尔曲线 A 20 20 0 0 1 120 60 // arc 圆弧 Z // close path,闭合当前路径,回到起点大小写有严格含义:大写命令是“绝对坐标”,小写命令是“相对坐标”。比如l 10 0表示相对当前点向右移动 10 个单位。做动画和动态生成的时候,相对坐标有时候更好用,因为你不用关心起点在哪,只要关心偏移量。
一个常见的心形路径示例:
<path d="M 10,30 A 20,20 0 0,1 50,30 A 20,20 0 0,1 90,30 Q 90,60 50,90 Q 10,60 10,30 Z" fill="#FF6B6B" />这里 A 画圆弧,Q 画二次贝塞尔曲线,配合 Z 闭合。虽然看起来像咒语,但拆开看就是:“左半沿圆弧上来,再沿圆弧下去,中间用两条贝塞尔曲线收尾”。理解了每个字母的含义,path 就不再神秘。
我的建议是:不是所有图形都要硬写 path,能用 rect/circle 用的就别自找麻烦。path 适合的是图标里那些不规则轮廓、字体图标转换、地图区块轮廓这些场景。平时做图,直接去 Figma 或 Illustrator 拖出来然后复制 SVG 路径,比手写快得多。
3. 让图形活起来:动画与交互代码实操
3.1 CSS 动画:最省力的动效方案
SVG 节点是 DOM 的一部分,所以 CSS 可以作用于 SVG 元素。这意味着你不需要引入任何动画库,直接用 CSS 就能做很多效果。
举个例子,做一个加载转圈的图标:
<svg viewBox="0 0 100 100" class="spinner"> <circle cx="50" cy="50" r="40" fill="none" stroke="#ccc" stroke-width="8" /> <circle cx="50" cy="50" r="40" fill="none" stroke="#333" stroke-width="8" stroke-dasharray="251" stroke-dashoffset="251" class="spinner-circle" /> </svg>.spinner { width: 60px; height: 60px; animation: spin 1.2s linear infinite; } .spinner-circle { animation: dash 1.2s ease-in-out infinite; } @keyframes spin { to { transform: rotate(360deg); } } @keyframes dash { 0% { stroke-dashoffset: 251; } 50% { stroke-dashoffset: 62.5; transform: rotate(135deg); } 100% { stroke-dashoffset: 251; transform: rotate(450deg); } }这里的核心技巧是 stroke-dasharray 和 stroke-dashoffset 组合。stroke-dasharray 把描边切成一段实线和一段空白,stroke-dashoffset 控制这一段的起始位置偏移。把这个原理用在 path 上,就能实现流行的“描边生长动画”:先算出路径总长度,然后让 dashoffset 从路径长度逐渐变到 0,看起来就像画笔在逐渐画出轮廓。
获取路径长度的代码很简洁:
const path = document.querySelector('#somePath'); const length = path.getTotalLength(); console.log(length);用这个 length 值去设置 stroke-dasharray 和 stroke-dashoffset 就能精准控制动画进度。这个技巧在页面加载动画、Logo 出场效果、导览系统里高亮某条路线时特别实用。
3.2 SMIL 动画:纯 SVG 里的时间轴
CSS 动画虽然强,但有个天然短板:它更适合做“循环重复”的效果,对于“按时间顺序执行一系列动作并且互相联动”稍微麻烦。SVG 原生自带一套 SMIL 动画标签,可以在 SVG 内部定义一个完整的时间轴。
最常用的是 animate 和 animateTransform:
<svg viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg"> <rect x="10" y="10" width="20" height="20" fill="#4A90D9"> <animate attributeName="x" values="10; 70; 10" dur="3s" repeatCount="indefinite" /> </rect> </svg>attributeName 指定要变化的属性,values 是动画关键帧的取值序列,dur 是周期时长,repeatCount 是重复次数。这个方块会在 3 秒内从 x=10 移动到 x=70 再回到 10,循环往复。
如果是旋转、缩放、平移这类 transform 变化,要使用 animateTransform:
<circle cx="50" cy="50" r="15" fill="#FF6B6B"> <animateTransform attributeName="transform" type="rotate" from="0 50 50" to="360 50 50" dur="2s" repeatCount="indefinite" /> </circle>from 和 to 里,rotate 的语法是“角度 圆心x 圆心y”,表示绕哪个点转。这里表示绕 (50,50) 这个点旋转 360 度。
SMIL 对事件支持也不错,可以配合 begin="click" 让动画在用户点击时启动。现在主流浏览器支持情况已经很好,除了一些老掉牙的移动端浏览器内核,基本放心用。
3.3 JS 交互:让 SVG 响应点击与数据
SVG 节点就是 DOM 节点,你可以用 querySelector 或者事件委托绑定点击、悬浮、拖拽。
一个很常见的场景:点击某个图形区块,把它高亮,同时让其他区块变灰。比如一个简单的国家地图分布:
<g id="region-north" class="region">const regions = document.querySelectorAll('.region'); regions.forEach(region => { region.addEventListener('click', function () { regions.forEach(r => r.classList.remove('active')); this.classList.add('active'); }); });.region { fill: #ccc; cursor: pointer; transition: fill 0.2s; } .region:hover { fill: #a0c4ff; } .region.active { fill: #4A90D9; }因为 SVG 是 DOM,CSS 的伪类 hover、active、transition 都能直接用,不用额外写 mouseenter/mouseleave。这也是我在交互场景里优先选 SVG 而不是 Canvas 的最重要原因之一:省了非常多事件管理代码。
数据驱动场景同样方便。比如后端返回一组坐标点,你动态生成 circle 塞进 SVG:
const points = [ { x: 20, y: 30, value: 50 }, { x: 60, y: 50, value: 80 }, { x: 90, y: 20, value: 30 } ]; const svg = document.querySelector('#chart'); points.forEach(p => { const circle = document.createElementNS('http://www.w3.org/2000/svg', 'circle'); circle.setAttribute('cx', p.x); circle.setAttribute('cy', p.y); circle.setAttribute('r', p.value / 10); circle.setAttribute('class', 'data-point'); svg.appendChild(circle); });注意这里用的是 document.createElementNS 而不是 document.createElement。普通 createElement 创建出来的是 HTML 元素,在 SVG 命名空间里渲染不出来。这是新手最容易踩的坑之一。
3.4 进阶:动画风格与 Live2D 的参照
有时候看到“svg live2d”这种关键词会误以为 SVG 能直接做 Live2D 那种立体可动的角色。严格来说 Live2D 是纹理网格变形技术,和 SVG 路线不同。但 SVG 完全可以通过对多个图层分别变形、位移、旋转,实现类似“关节带动”的伪 2D 动画效果:头部一个 g,躯干一个 g,四肢各自一个 g,用 JS 或者 SMIL 让它们分别动。这样做低精度、扁平风格的“轻量 Live2D”在网页上完全可行,而且文件比视频和位图序列小得多。
4. 场景实战:从图标系统到室内导览
4.1 图标系统:告别图片雪碧图的年代
早年间做网页图标用 CSS Sprite:把所有图标拼到一张 PNG 上,再用 background-position 定位。问题是切图麻烦、换色麻烦,放大还糊。后来图标字体流行过一阵,比如 Font Awesome,但字体图标在渲染细节、多色图标上天生受限。
SVG 图标系统把这些痛点基本全解决了。最基础的用法是 SVG Sprite:把所有图标合成一个 SVG 文件,每个图标用 symbol 包裹,配合 use 引用。
<svg xmlns="http://www.w3.org/2000/svg" style="display:none"> <symbol id="icon-home" viewBox="0 0 24 24"> <path d="M12 3 L22 12 L19 12 L19 21 L14 21 L14 14 L10 14 L10 21 L5 21 L5 12 L2 12 Z" /> </symbol> <symbol id="icon-user" viewBox="0 0 24 24"> <path d="..." /> </symbol> </svg>使用时:
<svg class="icon" width="24" height="24"> <use href="#icon-home" /> </svg>视觉上是正常的 24×24 图标,底层还是一片可引用资源。改颜色只需要写一条 CSS:
.icon { fill: currentColor; }这样图标的颜色自动跟随文字颜色,主题切换的时候一套代码全变。整个 Sprite 文件可以统一做 buffer 缓存,加载一次,到处使用。现在很多组件库的图标方案,底层也是这一套。
在实际工作中,我更推荐在构建阶段把 SVG 图标直接编译成组件而不是运行时 use,因为运行时引用会多一次网络依赖。但小型项目或者非工程化页面,SVG Sprite 方案依然是性价比最高的。
4.2 数据可视化:图表里的 SVG 身影
只要打开过 ECharts、D3.js 的渲染面板,你就会发现 SVG 在其中占了大半壁江山。折线图、柱状图、热力图、地图,只要图形数量在几千以内,SVG 都能舒服地拿下。
为什么可视化库偏爱 SVG?原因是交互和状态更新:图表里最常见的操作是移动端横滑数据点、鼠标悬浮显示 tooltip、点击某个柱子下钻。这些操作本质上是“把某个节点的样式改一改,顺便在附近插入一个提示框”。DOM 模型做这件事就是天生顺手,CSS 可以控制样式,事件能直接绑定到元素上,不需要去 Canvas 上做像素级别的命中检测。
D3.js 的核心哲学也是“数据驱动 DOM”:你把数据映射成 SVG 属性,剩下的交给浏览器渲染。比如画一个最简单的柱状图,数据数组 [4, 8, 10] 映射成三个 rect:
const data = [4, 8, 10]; const chart = d3.select('#chart'); chart.selectAll('rect') .data(data) .enter() .append('rect') .attr('x', (d, i) => i * 40) .attr('y', d => 100 - d * 8) .attr('width', 30) .attr('height', d => d * 8) .attr('fill', '#4A90D9');这段代码语义清晰:页面上有几个数据就画几个矩形,位置和高度由数据算出来。数据更新时,enter、update、exit 三段逻辑分别处理新增、更新、删除。这比手动管一堆 Canvas 绘制指令简单得多。
另外,很多量化分析、Python 数据项目里提到的 “python量化交易策略代码” 通常也只是产出数据,真正要把策略图展示成网页报告,还是会把数据转成 SVG 结构的图表。包括 XGBoost、LSTM 模型的训练结果分析、特征重要度可视化,最终落到前端也是 SVG 图表居多。所以做算法的人建议也了解一下 SVG 的结构,否则调报表样式时无从下手。
4.3 室内导览与地图标注:SVG 的隐藏主场
“svg室内导览系统”这个关键词背后是一个特别典型的 SVG 应用场景:商场导航、展览馆导览、停车场找车位。
为什么这类系统特别适合 SVG?
第一个理由是区域划分。室内地图的每个商铺、每个展位都是不规则多边形,在 SVG 里就是一个 polygon 或者 path。给每个区域加一个><g id="map-layer"> <polygon points="...">// 监听鼠标滚轮,缩放 map-layer 的 scale 值 const layer = document.querySelector('#map-layer'); let scale = 1; map.addEventListener('wheel', e => { scale += e.deltaY > 0 ? -0.05 : 0.05; scale = Math.max(0.5, Math.min(3, scale)); layer.setAttribute('transform', `scale(${scale})`); });
缩放时如果用 scale 直接作用于整个地图层,注意点击事件坐标的地图映射。如果你同时用 CSS transform 作过渡,还要小心事件区域漂移问题。比较稳妥的做法是记录当前视图中心,缩放时以鼠标位置为锚点调整平移量。细节不少,但整体代码量相比 Canvas 方案还是小了一个量级。
第三个理由是路线高亮。导览系统经常要画“从 A 到 B 的最优路线”,用 SVG 完全是动画主场:把路线画成 path,然后用之前说的 stroke-dasharray 做流动描边,视觉上是路线“生长”出来的,非常直观。
4.4 一个完整案例:鹈鹕骑自行车动画怎么拆
我见过有人聊“generate an svg of a pelican riding a bicycle”,听起来像段子,但它其实是一个很好的 SVG 动画教学练手题目。我们把它拆开看:鹈鹕、自行车、轮子转动、脚蹬踩踏,这些都是可以拆成独立 g 层的动效组件。
先画自行车:车架可以用几个 line,轮子用 circle,注意给两个轮子分别设 id,转动的时候让它们绕自身圆心旋转:
<circle id="wheel-front" cx="150" cy="180" r="40" fill="none" stroke="#333" stroke-width="4" /> <circle id="wheel-back" cx="50" cy="180" r="40" fill="none" stroke="#333" stroke-width="4" />轮子转动动画用 SMIL 的 animateTransform,把圆心坐标写进去,让轮子持续旋转:
<animateTransform xlink:href="#wheel-front" attributeName="transform" type="rotate" from="0 150 180" to="360 150 180" dur="1s" repeatCount="indefinite" />然后画鹈鹕:身体用椭圆,嘴巴用长长的 path,翅膀用一个 g 包住,让翅膀做小幅度的上下摆动。脚蹬可以用一个旋转的 group,随着轮子转动产生联动。整体控制好每个部件的 transform-origin,动画就能自然起来。
这个小项目做完,你会发现 SVG 动画的核心根本不是背 API,而是拆层:把复杂动作拆成多个 g,每个 g 负责一个独立的运动,再用时间轴把它们串起来。这个思路不仅适用于鹈鹕自行车,任何角色动画都可以照搬。
另外,这种“svg提示词”类需求通常是想借助 AI 直接生成 SVG 代码,但 AI 生成的 SVG 普遍存在一个问题:所有节点都堆在一个 g 里,线条路径也经常冗余,后续想单独做某个部件动画非常困难。所以我拿到 AI 生成的 SVG 第一件事就是“拆”:分析结构,把能动的部件单独拎出来,加上 id,再写动画。不要指望一口气生成完美动画。
5. 性能优化与踩坑实录
5.1 渲染性能:别把 SVG 当普通标签疯狂刷
SVG 是 DOM,所以它继承 DOM 的性能特征:节点多了,操作多了,一样会卡。一个 2000 个 circle 的 SVG,反复修改坐标,重排重绘成本不低。如果单帧要更新几百个节点,任督二脉就堵了。
几个实用的优化手段:
减少 DOM 节点数量。能用 path 合并的图形不要拆成满屏 rect/circle。一个 path 有几十个指令依然是一个节点,渲染压力远小于 50 个 circle 组成的效果。图标导出时,尽量开启“合并路径”之类的优化选项。
低频更新尽量不要操作样式属性,直接改 class。比如高亮某个区域,设置 fill 属性改颜色只是轻量操作,但如果同时改多个属性,改 class 让浏览器一次应用样式更高效率。
高频更新尽量减小重绘区域。SVG 里如果只动一个小部件,把它单独放到一个最底层的 g 里,避免和其他静态大区域搅在一起,某些性能敏感的浏览器能减少重新栅格化的影响。
给初始 hidden 的复杂 SVG 设置 display:none 而不是 visibility:hidden。display:none 时浏览器不参与布局和渲染,visibility 还会保留渲染空间。大量初始化场景,优先用 display:none。
5.2 兼容性与工程化问题
SVG 的兼容历史比较曲折,早期 IE 对 SVG 支持极差,连基本的 viewBox 都有 bug。现在的现代浏览器基本完全支持,但如果你还维护着老系统,有几个点还是要留意:
老 Edge 和低版本 Android WebView 对部分滤镜 filter 效果支持不一致,特别是 feDropShadow、feGaussianBlur 这类效果,渲染可能有偏差。遇到这类场景,我建议加一个 fallback:先检测是否支持滤镜,不支持就退化为实色边框,别让页面显示成一团黑。
第二是外部引用资源跨域问题。如果你在 SVG 里使用<image href="另一个域的图片">,或者 CSS 里引用了外部字体,浏览器默认有很多安全限制。不是所有系统都有心思处理跨域,所以团队内部约定:SVG 里尽量内联资源,或者走同域 CDN。
第三是字体图标替代后遗留问题。老项目从字体图标迁移到 SVG 时,经常有人遗漏标题、aria-label 之类的可访问性属性,导致屏幕阅读器读取时没有语义。SVG 图标建议统一加<title>,复杂图形再加<desc>。这块不只是“体验”,项目过审或无障碍检查时也是硬指标。
5.3 工具链:怎么调试、怎么导出、怎么转格式
调试 SVG 最直接的方式是用浏览器 DevTools 的 Elements 面板。你可以像查看普通 DOM 一样查看 SVG 结构、临时修改属性、断点调试事件。Chrome 的 Styles 面板还能直接调整 fill、stroke 等 SVG 属性并在页面实时生效。这比写死代码再刷新页面调试效率高太多。
导出方面,Figma、Sketch、Illustrator 都支持直接复制 SVG 代码,但复制出来的代码往往带了一堆冗余的元数据和分组嵌套。我习惯先用 SVGO 做压缩:去除 XML 声明、合并路径、精简坐标。命令行可以这样使用:
npx svgo -f ./svgs -o ./min在实际项目中,建议把 SVGO 直接集成到构建流程里,对所有导入的 svg 文件自动压缩。团队里经常有人把手画的十几个路径忘压了,几 MB 的 SVG 就这样进了仓库,页面加载时长肉眼可见地增加。
至于格式转换,“svg → compose imagevector kotlin”这个需求在 Android 端很常见。官方工具 Android Studio 自带 Vector Asset 支持导入 SVG 并自动生成 ImageVector 的 Kotlin 代码。操作路径:右键 res 目录,New -> Vector Asset,选择本地 SVG 文件,Android Studio 会转换出对应的 xml 资源。这样你在 Android 里就能享受到 SVG 的缩放不糊、可以动态改变颜色这些好特性。
另一个实用技巧是 SVG 转 base64 内联到 CSS 或 HTML 里。小图标(几 KB 以内)不想多一次网络请求的话,直接转成 data URI 塞进 CSS 背景图:
.icon { background-image: url("data:image/svg+xml,%3Csvg xmlns='...' viewBox='0 0 24 24'%3E%3Cpath d='...'/%3E%3C/svg%3E"); }注意 URL 里需要把特殊字符转码,比如<对应%3C,>对应%3E,空格可以用%20。这种内联方式的优势是小、快、不存在请求阻塞,劣势是难以复用。只适合那种只用一次的小图标。
5.4 常见问题速查表
我这几年经手了不少项目,把高频踩的坑归一下,做成速查表,给你省点时间:
| 现象 | 可能原因 | 解决方案 |
|---|---|---|
| 代码写了但浏览器渲染空白 | 少了 xmlns 命名空间 | 根 svg 加上xmlns="http://www.w3.org/2000/svg" |
| JS 创建图形不显示 | 用了 createElement 而非 createElementNS | 改用document.createElementNS('http://www.w3.org/2000/svg', 'circle') |
| 放大图变模糊 | 没写 viewBox,直接按像素硬撑 | 设置 viewBox,让坐标和实际渲染尺寸分离 |
| 动画转得位置不对 | transform 中心点不对 | 给 animateTransform 的 rotate/scale 明确圆心坐标 |
| 描边动画不流畅 | 没设置 stroke-dasharray 或者数值比路径长度小 | 先用 getTotalLength() 拿到总长度再设 array 和 offset |
| hover 无效 | 元素 fill 被上级 g 覆盖 | 明确给目标元素的指定样式,或提升 CSS 优先级 |
| 在老手机WebView渲染错乱 | 部分 SMIL 动画不支持 | 平滑降级为 CSS 动画或 JS 动画方案 |
| SVG 文件超大 | 路径未压缩、节点冗余 | 用 SVGO 压缩,开启资源合并 |
这里面比较常见的是第一和第二个:SVG 的命名空间问题。很多人一上来就直接写createElement('circle'),然后浏览器怎么都不显示,卡了半天。实际上 SVG 元素和 HTML 元素分属两个命名空间,写错一个地方,后面全部白搭。任何要动态创建 SVG 图形的代码,记得命名空间字符串写对。
第三个 viewBox 的问题平时不太容易暴露,直到你把一张小图标放大做 banner 背景,才发现边缘全糊。这不是 SVG 的锅,是你没把坐标系和真实尺寸解耦。加一个 viewBox 就能解决。
说实话,把这些坑踩过一遍之后,SVG 在我这里基本就是“表象复杂、内核简单”的工具:语法标签就那么十来个,真正的难点是理解坐标系和路径描述。只要你把这两块啃下来,剩下的都是平时用得多手就熟的问题。
如果你正在学或者正在做,我的建议是给自己准备一个小的“SVG 工具箱”:SVGO 做压缩、一个能预览 SVG 的本地 HTML 模板、自己积累的常用 path 片段。遇到新需求优先在现有片段上改,不要每次从零画。长期做下来,效率能上去不少。这个工具,谁用谁知道。