SVG 坐标系实战:viewBox、viewport 和图标主题色的完整解法

· 约 4 分钟 🖋 SVG 编辑预览

SVG 是矢量图,理论上无限缩放不失真——但实践中,“图标放大后被裁了”、“换了尺寸比例变了”、“颜色改不了”是三个最常见的坑。根源都在坐标系没搞清楚。

两个坐标系:viewport 和 viewBox

<svg width="100" height="100" viewBox="0 0 24 24">
  <circle cx="12" cy="12" r="10"/>
</svg>

viewportwidth="100" height="100"):SVG 元素在页面上占多少像素。类比相框尺寸。

viewBoxviewBox="0 0 24 24"):内容使用的坐标系。0 0 24 24 表示坐标从 (0,0) 到 (24,24),这块区域会被映射到 viewport。

缩放比例 = viewport ÷ viewBox:

  • x 方向:100 ÷ 24 ≈ 4.17
  • y 方向:100 ÷ 24 ≈ 4.17

所以内容坐标里的圆心 (12,12) 渲染在屏幕上是 (50,50)——正中间。半径 10 渲染成像素半径 41.7px。

viewBox 四个参数

viewBox="min-x min-y width height"
参数含义常见用法
min-x视野左上角 x 坐标通常 0,平移内容时改
min-y视野左上角 y 坐标通常 0
width视野宽度(内容坐标单位)图标的设计稿宽度,如 24
height视野高度图标的设计稿高度,如 24

平移技巧viewBox="-4 -4 32 32" 把视野扩大四圈,相当于给 24×24 的图标加了 4px 内边距——描边不再被裁剪。

preserveAspectRatio 决定宽高比不匹配时的行为

默认值 xMidYMid meet 是最常见的”安全”模式:

meet   = 保持比例缩放,全部内容可见,可能有空白边
slice  = 保持比例缩放,填满 viewport,可能裁边
none   = 不保持比例,直接拉伸
<!-- 充满容器,裁边(适合背景装饰) -->
<svg preserveAspectRatio="xMidYMid slice" viewBox="0 0 24 24">

<!-- 拉伸(适合需要自适应宽高的图表) -->
<svg preserveAspectRatio="none" viewBox="0 0 400 300">

currentColor:图标颜色随 CSS 继承

内联 SVG 的颜色问题标准解法:

第一步:把所有硬编码颜色改成 currentColor

<!-- 改前 -->
<path fill="#333333" d="..."/>

<!-- 改后 -->
<path fill="currentColor" d="..."/>

第二步:用 CSS color 属性控制颜色

.icon {
  color: var(--text-primary);  /* 主题色 token */
}
.icon-danger {
  color: #e53e3e;
}

原理currentColor 是 CSS 关键字,它的值等于当前元素(或继承到的)color 属性值。SVG 属性 fillstrokestop-color 都支持 currentColor

多色图标的处理

<svg style="color: #333; --icon-accent: #4CAF50">
  <path fill="currentColor" d="..."/>       <!-- 主色:跟随 color -->
  <circle fill="var(--icon-accent)" r="4"/> <!-- 强调色:独立变量 -->
</svg>

Figma 导出 SVG 的清理

Figma 导出的原始 SVG:

<svg width="24" height="24" viewBox="0 0 24 24" fill="none"
     xmlns="http://www.w3.org/2000/svg">
  <g id="icon / home" data-name="icon / home">
    <path id="Vector" d="M10 20v-6h4v6h5v-8h3L12 3 2 12h3v8z"
          fill="#333333" fill-rule="evenodd"/>
  </g>
</svg>

清理后:

<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
  <path d="M10 20v-6h4v6h5v-8h3L12 3 2 12h3v8z"
        fill="currentColor" fill-rule="evenodd"/>
</svg>

去掉:iddata-name、固定 width/height、多余的 <g> 层级,fill 颜色改 currentColor

SVG 路径 d 属性快速读法

<path d="M10 20v-6h4v6h5v-8h3L12 3 2 12h3v8z"/> 里的字母:

命令含义大写小写
MMove to绝对坐标相对坐标
LLine to绝对相对
H水平线绝对 x相对 x
V垂直线绝对 y相对 y
C三次贝塞尔绝对相对
A弧线绝对相对
Z闭合路径

不需要背,在工具里点击路径节点可视化编辑,比手改 d 字符串直观得多。

在工具里做什么

  • 编辑路径节点:拖拽节点微调图标形状
  • 修改颜色属性:直接改 fill/stroke,预览主题色效果
  • 调整 viewBox:增加内边距,解决描边被裁的问题
  • 压缩/清理:去掉 Figma 导出的冗余 id 和 data 属性,减少体积
  • 复制内联代码:直接得到可粘进 HTML 的 <svg>…</svg> 片段

配套工具

  • CSS 渐变 — SVG linearGradient 和 CSS 渐变语法互相参考
  • 颜色工具 — 生成色盘,确定图标配色方案
  • 图片压缩 — SVG 外的其它图片资源压缩
  • 图片转 ASCII — 另一种把图像转换为文字表示的思路

❓ 常见问题

viewBox 和 width/height 有什么区别?

viewport(width/height)是画布尺寸,viewBox 是内容坐标系。比喻:viewport 是相框(物理大小),viewBox 是画布内容的视野范围(内容坐标系)。width="100" height="100" viewBox="0 0 24 24" 的意思是:把 [0,0]~[24,24] 这块内容区域拉伸填满 100×100 像素的画布,缩放比例 = 100/24 ≈ 4.17。如果你只设了 viewBox 没设 width/height,SVG 会尝试用容器宽度自动推算(行为因浏览器和布局上下文而异)。实践规则——图标 SVG:只保留 viewBox,去掉 width/height,用 CSS 控制尺寸;内容 SVG(图表、地图):两个都要设,viewBox 定义内容坐标,width/height 定义输出尺寸。

图标放大后边缘被裁掉,怎么办?

SVG 内容超出 viewBox 范围会被裁剪。常见原因:(1) Figma 导出时有描边(stroke),stroke 默认从路径中心向两边扩展,一半的 stroke 会超出 viewBox 边界被裁;(2) 内容本身绘制到了 (0,0) 边界外;(3) filter(模糊/阴影)渲染区域超出边界。修复:(1) 把 viewBox 四个值各减一个 stroke-width 的一半并增大宽高——如 viewBox="-1 -1 26 26";(2) 在根 <svg>overflow="visible"(但这样内容会溢出容器);(3) 在 Figma 导出时选"Include bounding box"。

怎么用 CSS 控制 SVG 图标的颜色?

把 fill 或 stroke 改成 currentColor,颜色就随 CSS color 属性继承。步骤:(1) 打开 SVG 源码,找到 fill="#333333"fill="black" 这类硬编码颜色;(2) 改成 fill="currentColor";(3) 在 HTML 里 <svg style="color: red"> 或 CSS .icon { color: red } 即可改色。多色图标:如果图标有 2-3 种颜色,可以把次要颜色改成 opacity: 0.3 或用 CSS 变量 var(--icon-accent-color, currentColor),主色用 currentColor。注意:内联 SVG 才能完整被 CSS 控制;<img src="icon.svg">background-image: url() 里的 SVG 不接受外部 CSS。

preserveAspectRatio 是什么,什么时候需要改?

控制当 viewBox 宽高比和 viewport 宽高比不匹配时如何处理,默认是 xMidYMid meet(居中缩放,保持比例,全部内容可见)。三个常用值:(1) xMidYMid meet(默认)—— 等比缩放居中,四边可能有空白;(2) xMidYMid slice —— 等比缩放居中裁切,充满画布,可能裁掉边缘;(3) none —— 不保持比例,拉伸填满,内容会变形。常见场景:背景装饰 SVG 想充满容器 → 用 slice;图表坐标轴 SVG 不想变形但要按容器拉伸 → 用 none(配合 viewBox 里精确的坐标定义)。

Figma 导出的 SVG 为什么那么"脏"?如何清理?

Figma 导出的 SVG 带了很多非必要元数据,常见包括:(1) <title><desc> 标签(可删,不影响渲染);(2) id="..." 属性(Figma 图层名,通常可删);(3) data-name 属性;(4) 大量 group <g> 嵌套;(5) 路径的 style 属性写成内联(style="fill:#fff")而非属性(fill="#fff");(6) 多余的 xmlns:xlink 声明(现代浏览器已弃用)。清理工具:SVGO(命令行)或 SVG 编辑预览工具的"压缩"功能可自动去掉这些。清理后体积通常减少 30%~70%,且 CSS 更容易控制样式。

🖋 打开 SVG 编辑预览 代码↔预览实时同步·点节点改 fill/stroke/transform·SVGO 压缩·导出 data URL/PNG/JSX/Vue·本地处理