前置知识: SVG

SVG 变换 transform

6 min中级

translate、rotate、scale、skew、matrix 与 transform-origin 变换组合。


前置知识:用户坐标系与 viewBox(《SVG 坐标系统与 viewBox》,003-SVGCoordinateSystemViewBox);本文的矩阵运算只需要高中水平的二元一次方程。

学习目标:掌握五种变换函数的参数含义与坐标效果;理解”变换列表从右往左作用于坐标”这一核心规则;会用 rotate(angle cx cy) 与 transform-box/transform-origin 两种方式指定旋转中心;能识别绕原点旋转、描边被缩放两大经典陷阱。


前置知识:用户坐标系与 viewBox(《SVG 坐标系统与 viewBox》,003-SVGCoordinateSystemViewBox);本文的矩阵运算只需要高中水平的二元一次方程。

学习目标:掌握五种变换函数的参数含义与坐标效果;理解”变换列表从右往左作用于坐标”这一核心规则;会用 rotate(angle cx cy) 与 transform-box/transform-origin 两种方式指定旋转中心;能识别绕原点旋转、描边被缩放两大经典陷阱。

1. transform 属性

transform 对元素或分组应用几何变换,不影响后续元素的坐标系(除非在 <g> 上)。

<svg viewBox="0 0 200 200">
  <rect x="50" y="50" width="50" height="50" fill="#4f5bd5" />
  <rect x="50" y="50" width="50" height="50" fill="#d63031" transform="translate(60, 0)" />
</svg>

2. 基本变换函数

2.1 translate 平移

<rect transform="translate(50, 30)" />
<!-- 或单轴 -->
<rect transform="translate(50, 0)" />
参数说明
txX 方向偏移
tyY 方向偏移(可省略,默认 0)

2.2 rotate 旋转

<rect transform="rotate(45)" />
<!-- 围绕指定点旋转 -->
<rect transform="rotate(45 100 100)" />
参数说明
angle旋转角度(度)
cx, cy旋转中心(可省略,默认 0,0)

单参数 rotate(45) 围绕原点 (0,0) 旋转,通常不是想要的效果。常用 rotate(angle cx cy) 围绕元素中心旋转。

方向提醒:SVG 的 y 轴向下,因此正角度是顺时针旋转(这与数学课本里 y 轴向上的坐标系正好相反,但与屏幕直觉一致)。

2.3 scale 缩放

<rect transform="scale(1.5)" />
<!-- 双轴独立 -->
<rect transform="scale(1.5, 0.5)" />
参数说明
sxX 方向缩放比
syY 方向缩放比(可省略,默认等于 sx)

scale 会同时缩放 stroke-width。若需保持描边不变,使用 vector-effect="non-scaling-stroke"。

2.4 skew 倾斜

<rect transform="skewX(30)" /> <rect transform="skewY(15)" />
函数说明
skewX(angle)沿 X 轴倾斜
skewY(angle)沿 Y 轴倾斜

倾斜的本质是”y 坐标(或 x 坐标)越大,错切得越多”,常用于斜体文字、等轴测图形。

3. matrix 矩阵

所有变换本质都是 2D 仿射矩阵:matrix(a, b, c, d, e, f)。

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

变换公式:

  • x' = a*x + c*y + e
  • y' = b*x + d*y + f

3.1 各变换对应的矩阵

变换matrix 参数
translate(tx, ty)matrix(1 0 0 1 tx ty)
rotate(θ)matrix(cosθ sinθ -sinθ cosθ 0 0)
scale(s)matrix(s 0 0 s 0 0)
skewX(θ)matrix(1 0 tanθ 1 0 0)
skewY(θ)matrix(1 tanθ 0 1 0 0)

3.2 示例

<rect transform="matrix(1 0 0 1 50 30)" />
<!-- 等价于 translate(50, 30) -->

<rect transform="matrix(0.707 0.707 -0.707 0.707 0 0)" />
<!-- 等价于 rotate(45) -->

4. 变换组合

多个变换用空格分隔,构成变换列表。矩阵层面它们按书写顺序依次左乘:transform="A B C" 等价于矩阵 A × B × C,作用到具体坐标点时是”从右往左”依次生效(最右边的变换最先接触元素自己的坐标),这一点与 CSS transform 一致。

一个不易失真的类比:变换列表像给元素层层套坐标系,每往右写一个函数,就往里再套一层,因此最右边的函数离元素最近、最先作用到它自身的坐标。

<!-- 先 translate 再 rotate(对点而言) -->
<rect transform="translate(100, 100) rotate(45)" />

<!-- 先 rotate 再 translate -->
<rect transform="rotate(45) translate(100, 0)" />

4.1 顺序影响结果

<svg viewBox="0 0 200 200">
  <!-- 原始矩形 -->
  <rect x="0" y="0" width="50" height="50" fill="#4f5bd5" />

  <!-- 先平移到 (100,100) 再绕原点旋转 45°:矩形被甩到远处 -->
  <rect
    x="0"
    y="0"
    width="50"
    height="50"
    fill="#d63031"
    transform="rotate(45) translate(100,100)"
  />

  <!-- 先绕原点旋转 45° 再平移到 (100,100):矩形在 (100,100) 处旋转 -->
  <rect
    x="0"
    y="0"
    width="50"
    height="50"
    fill="#00b894"
    transform="translate(100,100) rotate(45)"
  />
</svg>

建议:旋转用 rotate(angle cx cy) 形式,避免顺序歧义。

5. transform-origin

SVG 2 引入 transform-origin,类似 CSS 的同名属性。

<rect
  x="50"
  y="50"
  width="50"
  height="50"
  transform="rotate(45)"
  transform-origin="75px 75px"
  style="transform-box: fill-box"
/>

5.1 transform-box

值含义
view-box以 SVG viewBox 为参考(默认)
fill-box以元素边界框为参考
<style>
  .spin {
    transform-box: fill-box;
    transform-origin: center;
    animation: spin 2s linear infinite;
  }
  @keyframes spin {
    to {
      transform: rotate(360deg);
    }
  }
</style>
<rect class="spin" x="50" y="50" width="100" height="100" fill="#4f5bd5" />

transform-box: fill-box + transform-origin: center 是 SVG 元素围绕自身中心旋转的标准模式。

6. CSS transform 与 SVG transform

CSS transform 也可作用于 SVG 元素,但需注意坐标系差异。

6.1 CSS 方式

.logo {
  transform: rotate(45deg);
  transform-origin: 50% 50%;
  transform-box: fill-box;
}
<svg viewBox="0 0 200 200">
  <rect class="logo" x="50" y="50" width="100" height="100" fill="#4f5bd5" />
</svg>

6.2 两者区别

维度SVG transform 属性CSS transform
语法rotate(45 100 100)rotate(45deg) + transform-origin
单位无单位(默认度/像素)需 deg、px
动画SMIL <animateTransform>CSS @keyframes
性能略优(直接矩阵)现代浏览器已优化
浏览器支持全部SVG 2 后完整支持

7. animateTransform 变换动画

SMIL 提供 <animateTransform> 专门用于变换动画。它的定义如今在独立的 SVG Animations 规范中(SVG 2 本体不强制要求实现),社区方向是 CSS 动画与 Web Animations API;不过所有主流浏览器至今仍完整支持,独立 .svg 文件(无法挂外部 CSS 的场景)里它仍是零依赖动画的首选,详见《SVG 动画基础》(013-SVGAnimationBasics)。

<svg viewBox="0 0 200 200">
  <rect x="75" y="75" width="50" height="50" fill="#4f5bd5">
    <animateTransform
      attributeName="transform"
      type="rotate"
      from="0 100 100"
      to="360 100 100"
      dur="4s"
      repeatCount="indefinite"
    />
  </rect>
</svg>

7.1 多变换叠加 additive

<g>
  <animateTransform
    attributeName="transform"
    type="translate"
    values="0 0; 100 0; 0 0"
    dur="4s"
    repeatCount="indefinite"
  />
  <animateTransform
    attributeName="transform"
    type="rotate"
    values="0; 360"
    dur="2s"
    repeatCount="indefinite"
    additive="sum"
  />
  <rect x="-25" y="-25" width="50" height="50" fill="#4f5bd5" />
</g>

additive="sum" 让多个 animateTransform 叠加,否则后一个会覆盖前一个。

8. 嵌套变换

<g> 上的 transform 会作用于所有子元素,形成坐标系嵌套。

<svg viewBox="0 0 400 200">
  <g transform="translate(100, 100)">
    <!-- 子坐标系原点平移到 (100,100) -->
    <g transform="rotate(45)">
      <!-- 再旋转 45° -->
      <rect x="-25" y="-25" width="50" height="50" fill="#4f5bd5" />
    </g>
    <circle cx="0" cy="0" r="5" fill="#d63031" />
  </g>
</svg>

嵌套变换矩阵会相乘,最终变换是父子变换的复合。

9. 实战:地球绕太阳

<svg viewBox="0 0 400 400">
  <!-- 太阳 -->
  <circle cx="200" cy="200" r="40" fill="#f9a825" />
  <!-- 地球轨道 -->
  <circle cx="200" cy="200" r="120" fill="none" stroke="#ccc" stroke-dasharray="4 4" />
  <!-- 地球绕太阳公转 -->
  <g>
    <animateTransform
      attributeName="transform"
      type="rotate"
      from="0 200 200"
      to="360 200 200"
      dur="8s"
      repeatCount="indefinite"
    />
    <!-- 平移到轨道位置 -->
    <g transform="translate(320, 200)">
      <!-- 地球自转:给球面加一个"斑点"才能看出转动 -->
      <g>
        <animateTransform
          attributeName="transform"
          type="rotate"
          from="0"
          to="360"
          dur="2s"
          repeatCount="indefinite"
        />
        <circle cx="0" cy="0" r="15" fill="#4f5bd5" />
        <circle cx="6" cy="-6" r="3.5" fill="#2d4a9e" />
      </g>
    </g>
  </g>
</svg>

结构:

  • 外层 <g> 绕太阳中心 (200,200) 旋转 → 实现公转
  • 中层 <g> translate 到轨道位置 → 地球位置
  • 内层 <g> 绕自身原点 (0,0) 旋转 → 自转;注意纯色圆看不出旋转,必须给球面加一个不对称的标记(此处用一个小斑点)

这是”嵌套坐标系”最典型的应用:每层 <g> 只负责一件事,组合起来就是复杂运动。

10. 常见陷阱

10.1 rotate 默认绕原点

<!-- 错误:矩形被甩到画布外 -->
<rect x="50" y="50" width="100" height="100" transform="rotate(45)" />

<!-- 正确:围绕矩形中心旋转 -->
<rect x="50" y="50" width="100" height="100" transform="rotate(45 100 100)" />

10.2 scale 缩放描边

<!-- 描边被放大到 6px -->
<rect stroke-width="2" transform="scale(3)" />

<!-- 保持描边不变 -->
<rect stroke-width="2" transform="scale(3)" vector-effect="non-scaling-stroke" />

10.3 transform 与 viewBox 重复缩放

<!-- viewBox 已缩放 2 倍,transform scale(2) 会再缩 2 倍 -->
<svg viewBox="0 0 100 100" width="200" height="200">
  <rect width="50" height="50" transform="scale(2)" />
  <!-- 实际显示 200×200 -->
</svg>

小结

初学者要点:

  • rotate / scale 默认以原点 (0,0) 为基准,而元素往往不在原点——要么用 rotate(角度 cx cy) 三参数形式,要么先把元素画在原点再 translate 到位。
  • 变换列表”从右往左”作用到元素坐标,顺序不同结果不同;拿不准时写一个测试用例对照。
  • SVG 的 y 轴向下,正角度是顺时针;scale 会等比放大 stroke-width,需要线宽恒定时用 vector-effect="non-scaling-stroke"。

进阶注意:

  • transform-box: fill-box + transform-origin: center 是 CSS 驱动 SVG 元素自转的标准组合;不设 transform-box 时默认参考 viewBox,origin: center 会绕整个画布中心转,这是 CSS 动画 SVG 时最常见的”转错轴”原因。
  • transform 属性(SVG 2 起是 CSS transform 属性的表现属性形式)与 CSS 写法最终走同一条渲染管线,性能差异可忽略;选型依据是场景——独立 .svg 文件只能用属性/SMIL,内联 SVG 则两者皆可。
  • 复合运动(公转 + 自转)优先用嵌套 <g> 分层表达,每层一个 transform,比手算复合矩阵可读得多。