SVG 的坐标系统和 viewBox 变换原理是什么?
SVG 坐标系的基本模型
SVG 使用笛卡尔坐标系,但和数学课本上的不同:原点 (0,0) 在画布左上角,x 轴向右为正,y 轴向下为正。这个设定和浏览器渲染一致,也意味着"向上移动"对应的 y 值是负数。
理解 SVG 坐标系要抓住两个层次:
- 视口(viewport):由
<svg>的width和height决定,是 SVG 在页面上占据的实际像素区域。width="200" height="200"就是 200x200 像素的画布。 - 用户坐标系(user coordinate system):SVG 内部绘图使用的逻辑坐标空间。默认一个用户单位等于一个像素,但
viewBox会改变这个映射关系。
单位方面,SVG 支持 px、em、rem、cm、mm、% 等,无单位数字默认等同于 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 的三个核心作用:
- 解耦绘图尺寸与渲染尺寸:图标设计常用
viewBox="0 0 24 24",因为 24 的网格便于对齐和计算,实际显示大小由外部 CSS 控制。 - 实现响应式缩放:设置
width="100%"配合 viewBox,SVG 自动适配容器大小,内部坐标无需改动。 - 控制可视区域:调整
min-x和min-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 |
其中 a 和 d 控制缩放,b 和 c 控制倾斜,e 和 f 控制平移。其他变换函数本质上是 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 -> scale 和 rotate -> translate -> scale 的结果完全不同。每次变换都在修改当前坐标系,后续变换基于已修改的坐标系执行。实践中推荐"先缩放、再旋转、最后平移"的顺序(SRT),这样平移的方向不受旋转影响,缩放也不影响平移距离。
SVG transform 和 CSS transform 有什么区别?
这是面试中的高频混淆点:
| 对比项 | SVG transform | CSS 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 坐标变换在动画场景有几个容易踩的坑:
- CSS 动画和 SVG 属性动画的坐标系不同:用 CSS
@keyframes做 transform 动画时,变换原点遵循 CSS 规则;用 SVG 的<animateTransform>时遵循 SVG 规则。两者混用会导致意外行为。 - transform 不可叠加:SVG 的
<animateTransform>的additive属性默认为replace,多个动画会互相覆盖。需要additive="sum"才能叠加。 - 缩放动画的坐标偏移:对带有
x、y属性的元素做 scale 动画,元素会向原点方向移动(因为坐标值也被缩放了)。解决方案是用<g>包裹,对<g>做动画。
实际开发中的核心要点
- 始终设置 viewBox:让 SVG 具备响应式能力,避免硬编码固定尺寸。图标用
viewBox="0 0 24 24"或viewBox="0 0 16 16",通过 CSS 控制实际显示大小。 - 用 viewBox 而非 width/height 控制缩放:设置
width="100%"或不设宽高,通过 CSS 控制尺寸,viewBox 负责逻辑坐标映射。 - 注意 transform 顺序:不同顺序产生不同结果,推荐 SRT 顺序(Scale -> Rotate -> Translate)。
- 用
<g>组织和变换元素组:减少重复代码,提高可维护性,也便于动画控制。 - SVG 旋转需指定中心点:CSS 中可以
transform-origin: center,SVG 中必须手动写rotate(angle, cx, cy)或用 translate 模拟。 - 避免在 SVG 内部使用百分比坐标:百分比在 SVG 中的计算规则复杂,优先使用 viewBox 内的绝对坐标值。
- CSS transform 和 SVG 属性 transform 不要混用:在同一元素上同时设置两者,行为在不同浏览器中可能不一致。选一种方式贯彻到底。