5月27日 15:38

SVG 的坐标系统和 viewBox 变换原理是什么?

SVG 坐标系的基本模型

SVG 使用笛卡尔坐标系,但和数学课本上的不同:原点 (0,0) 在画布左上角,x 轴向右为正,y 轴向下为正。这个设定和浏览器渲染一致,也意味着"向上移动"对应的 y 值是负数。

理解 SVG 坐标系要抓住两个层次:

  • 视口(viewport):由 <svg>widthheight 决定,是 SVG 在页面上占据的实际像素区域。width="200" height="200" 就是 200x200 像素的画布。
  • 用户坐标系(user coordinate system):SVG 内部绘图使用的逻辑坐标空间。默认一个用户单位等于一个像素,但 viewBox 会改变这个映射关系。

单位方面,SVG 支持 pxemremcmmm% 等,无单位数字默认等同于 px。实际开发中绝大多数场景用无单位数字就够了。

viewBox 做了什么?

viewBox 是 SVG 里最关键的属性之一,它定义内部逻辑坐标系的范围,再将该范围映射到视口上。语法为 viewBox="min-x min-y width height"

svg
<svg viewBox="0 0 100 100" width="200" height="200"> <circle cx="50" cy="50" r="40" fill="red" /> </svg>

这里 viewBox="0 0 100 100" 声明逻辑坐标系为 100x100,视口为 200x200 像素。浏览器计算缩放比:水平 200/100=2,垂直 200/100=2,所以逻辑坐标中 1 个单位等于 2 个像素。圆心在逻辑坐标 (50,50),实际渲染在视口的 (100,100) 像素位置。

viewBox 的三个核心作用:

  1. 解耦绘图尺寸与渲染尺寸:图标设计常用 viewBox="0 0 24 24",因为 24 的网格便于对齐和计算,实际显示大小由外部 CSS 控制。
  2. 实现响应式缩放:设置 width="100%" 配合 viewBox,SVG 自动适配容器大小,内部坐标无需改动。
  3. 控制可视区域:调整 min-xmin-y 可以平移可视区域,类似"镜头移动"。viewBox="-50 -50 200 200" 相当于将坐标系原点向右下偏移 50 个单位,让你看到原点左上方的内容。

preserveAspectRatio 如何处理宽高比不一致?

当 viewBox 的宽高比和视口不一致时,preserveAspectRatio 决定 SVG 内容如何适配视口。

语法为 preserveAspectRatio="align meetOrSlice"

  • align:对齐方式,由 x 方向(xMin / xMid / xMax)和 y 方向(YMin / YMid / YMax)组合,共 9 种。
  • meetOrSlice:缩放策略,meet 保持比例完整显示(可能留白),slice 保持比例填充区域(可能裁切)。
svg
<!-- 居中完整显示,保持比例(默认值) --> <svg viewBox="0 0 100 100" preserveAspectRatio="xMidYMid meet" width="300" height="200"> <rect x="0" y="0" width="100" height="100" fill="blue" /> </svg> <!-- 居中裁切填充,保持比例 --> <svg viewBox="0 0 100 100" preserveAspectRatio="xMidYMid slice" width="300" height="200"> <rect x="0" y="0" width="100" height="100" fill="blue" /> </svg> <!-- 拉伸变形,忽略原始比例 --> <svg viewBox="0 0 100 100" preserveAspectRatio="none" width="300" height="200"> <rect x="0" y="0" width="100" height="100" fill="blue" /> </svg>

日常开发中 xMidYMid meet 是最常用的默认配置。none 一般避免使用,除非确实需要拉伸效果(比如背景图案)。一个典型的 slice 场景是全屏背景图:你希望图片铺满容器,宁可裁切也不留白。

SVG transform 有哪些变换类型?

SVG 的 transform 属性支持以下变换函数:

translate — 平移

将元素沿 x 和 y 方向移动指定距离。只写一个值时 y 方向默认为 0。

svg
<rect x="10" y="10" width="50" height="50" fill="red" transform="translate(100, 80)" />

注意:SVG 的 translate 是相对于 SVG 画布当前坐标系原点,而非元素自身位置。CSS 的 translate 则是相对于元素自身的,这点容易混淆。

rotate — 旋转

rotate(angle, cx, cy) 指定旋转角度(度)和旋转中心坐标。省略旋转中心时默认围绕当前坐标系原点 (0,0) 旋转。

svg
<!-- 围绕元素中心旋转 45 度 --> <rect x="0" y="0" width="50" height="50" fill="blue" transform="rotate(45, 25, 25)" /> <!-- 围绕坐标系原点旋转 30 度 --> <rect x="100" y="100" width="50" height="50" fill="green" transform="rotate(30)" />

这是 SVG transform 和 CSS transform 的一个重要差异:CSS 默认以元素中心为旋转原点(transform-origin: 50% 50%),SVG 默认以坐标系原点。在 SVG 中想让元素绕自身中心旋转,必须手动指定 cx, cy,或者用 translate + rotate + translate 三步模拟。

scale — 缩放

scale(sx, sy) 分别指定水平和垂直方向的缩放倍数。只写一个值时 sy 等于 sx

svg
<!-- 等比放大 2 倍 --> <rect x="50" y="50" width="30" height="30" fill="green" transform="scale(2)" /> <!-- 水平放大 1.5 倍,垂直缩小到 0.5 倍 --> <rect x="50" y="50" width="30" height="30" fill="yellow" transform="scale(1.5, 0.5)" />

关键细节:scale 以坐标系原点为缩放中心,同时会改变元素的坐标位置。transform="scale(2)" 会让位于 (50,50) 的元素实际渲染到 (100,100)。想让元素在原位放大,需要先 translate 到原点,再 scale,再 translate 回去。

skewX / skewY — 倾斜

沿 x 轴或 y 轴方向倾斜指定角度。

svg
<rect x="50" y="50" width="50" height="50" fill="purple" transform="skewX(30)" /> <rect x="50" y="50" width="50" height="50" fill="orange" transform="skewY(20)" />

倾斜在日常开发中用得较少,但在制作平行四边形、梯形等几何效果时会派上用场。

matrix — 矩阵变换

所有变换最终都归结为矩阵运算。matrix(a, b, c, d, e, f) 对应变换矩阵:

shell
| a c e | | b d f | | 0 0 1 |

其中 ad 控制缩放,bc 控制倾斜,ef 控制平移。其他变换函数本质上是 matrix 的语法糖:

  • translate(tx, ty) = matrix(1, 0, 0, 1, tx, ty)
  • scale(s) = matrix(s, 0, 0, s, 0, 0)
  • rotate(a) = matrix(cos(a), sin(a), -sin(a), cos(a), 0, 0)

直接用 matrix 的场景不多,但在需要高性能批量变换(如 Canvas 导出 SVG、复杂动画插值)时更高效。

组合变换

多个变换函数写在同一个 transform 属性中,按从右到左的顺序依次应用(矩阵乘法的右乘规则):

svg
<rect x="0" y="0" width="50" height="50" fill="red" transform="translate(100, 100) rotate(45) scale(1.5)" />

变换顺序非常重要:translate -> rotate -> scalerotate -> translate -> scale 的结果完全不同。每次变换都在修改当前坐标系,后续变换基于已修改的坐标系执行。实践中推荐"先缩放、再旋转、最后平移"的顺序(SRT),这样平移的方向不受旋转影响,缩放也不影响平移距离。

SVG transform 和 CSS transform 有什么区别?

这是面试中的高频混淆点:

对比项SVG transformCSS transform
变换原点默认为当前坐标系原点 (0,0)默认为元素中心 (50% 50%)
语法属性写在元素上:transform="rotate(45)"样式写在 CSS 中:transform: rotate(45deg)
单位rotate 不需要单位rotate 必须带 deg/rad 等单位
坐标系相对于 SVG 当前用户坐标系相对于元素自身的包含块
transform-origin不支持(需手动 translate 模拟)支持 transform-origin 属性

在 SVG 2 规范中,SVG 的 transform 属性和 CSS transform 属性正在统一。现代浏览器已支持在 SVG 元素上使用 CSS transform,这意味着你可以用 transform-origin: center 来简化旋转操作。但在需要兼容旧浏览器时,仍要注意区别。

另一个容易忽略的细节:给 SVG 元素加 CSS transform 时,变换原点仍然默认是 (0,0) 而非元素中心,这和普通 HTML 元素不同。需要显式设置 transform-origin 才能改变行为。

嵌套坐标系的运作方式

<g> 元素可以创建局部坐标系,其 transform 属性会影响所有子元素。绘制复杂图形时,把一组元素看作整体进行变换比逐个操作高效得多。

svg
<svg viewBox="0 0 200 200"> <g transform="translate(50, 50)"> <circle cx="0" cy="0" r="20" fill="red" /> <circle cx="50" cy="0" r="20" fill="blue" /> <circle cx="25" cy="43" r="20" fill="green" /> </g> </svg>

<g> 上的 transform="translate(50, 50)" 为所有子元素建立新的局部坐标系,原点偏移到 (50,50)。三个圆的坐标都相对于这个新原点。

嵌套可以多层叠加,外层变换会传递到内层:

svg
<g transform="translate(50, 50)"> <g transform="rotate(30)"> <rect x="0" y="0" width="40" height="40" fill="teal" /> </g> </g>

矩形先在父 <g> 的平移坐标系中旋转 30 度,再随父 <g> 整体平移。这和 transform="translate(50,50) rotate(30)" 写在同一个元素上效果一致。

实际项目中,嵌套坐标系最常见的应用是组件化图形:比如数据可视化中,每个图表模块用一个 <g> 包裹,通过外层 translate 定位,内部元素用相对坐标绘制,互不干扰。

动画中的坐标变换注意事项

SVG 坐标变换在动画场景有几个容易踩的坑:

  1. CSS 动画和 SVG 属性动画的坐标系不同:用 CSS @keyframes 做 transform 动画时,变换原点遵循 CSS 规则;用 SVG 的 <animateTransform> 时遵循 SVG 规则。两者混用会导致意外行为。
  2. transform 不可叠加:SVG 的 <animateTransform>additive 属性默认为 replace,多个动画会互相覆盖。需要 additive="sum" 才能叠加。
  3. 缩放动画的坐标偏移:对带有 xy 属性的元素做 scale 动画,元素会向原点方向移动(因为坐标值也被缩放了)。解决方案是用 <g> 包裹,对 <g> 做动画。

实际开发中的核心要点

  1. 始终设置 viewBox:让 SVG 具备响应式能力,避免硬编码固定尺寸。图标用 viewBox="0 0 24 24"viewBox="0 0 16 16",通过 CSS 控制实际显示大小。
  2. 用 viewBox 而非 width/height 控制缩放:设置 width="100%" 或不设宽高,通过 CSS 控制尺寸,viewBox 负责逻辑坐标映射。
  3. 注意 transform 顺序:不同顺序产生不同结果,推荐 SRT 顺序(Scale -> Rotate -> Translate)。
  4. <g> 组织和变换元素组:减少重复代码,提高可维护性,也便于动画控制。
  5. SVG 旋转需指定中心点:CSS 中可以 transform-origin: center,SVG 中必须手动写 rotate(angle, cx, cy) 或用 translate 模拟。
  6. 避免在 SVG 内部使用百分比坐标:百分比在 SVG 中的计算规则复杂,优先使用 viewBox 内的绝对坐标值。
  7. CSS transform 和 SVG 属性 transform 不要混用:在同一元素上同时设置两者,行为在不同浏览器中可能不一致。选一种方式贯彻到底。
标签:SVG