在前六篇文章中,我们学习了如何绘制各种 SVG 图形——矩形、圆形、路径,以及如何用 <g> 分组和 <use> 复用。但你有没有注意到一个反复出现的概念:transform 属性?

从第 2 篇的 SVGGraphicsElement 到第 6 篇的<g> 分组,transform 几乎无处不在。我们用它平移、旋转、缩放图形,却很少深究它的本质:

transform 的背后,究竟是什么?

答案是——矩阵。

你可能会想:“矩阵?那不是大学线性代数里才学的东西吗?前端开发也要懂这个?”

好消息是:你不需要成为数学专家。SVG 已经帮你封装好了所有复杂的计算。你只需要理解矩阵的基本思想——它本质上是一个“坐标转换器”——就能掌握 SVG 变换的全部奥秘。

而在 JavaScript 中操作这个“坐标转换器”的工具,就是 SVGMatrix(以及它的现代替代方案 DOMMatrix)。

本文将带你从零开始,理解矩阵变换的本质,掌握 SVGMatrix 的核心方法,并最终用矩阵实现一个拖拽 + 旋转 + 缩放的综合交互 Demo。

一、为什么需要矩阵?

先来看一个场景。假设你在画布上有一个矩形:

<rect x="50" y="50" width="100" height="80" fill="blue" />

现在你想让它:

  1. 向右移动 30 像素。

  2. 旋转 45 度。

  3. 放大 1.5 倍。

用 transform 属性可以这样写:

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

如果变换更复杂——比如有 10 个图形需要应用不同的变换组合,或者需要在 JavaScript 中动态计算变换后的坐标——手动拼写 transform 字符串就变得非常痛苦。

矩阵的威力就在于:它能把任意多个变换(平移、旋转、缩放、斜切)统一成一个数学对象,然后通过一次运算完成所有坐标转换。

核心认知:矩阵不是“另一种变换方式”,而是所有变换的通用语言。translate、rotate、scale 都只是矩阵的“语法糖”。

二、2D 变换矩阵:六个数字的奥秘

2.1 矩阵的数学形式

SVG 的 2D 变换矩阵是一个 3×3 矩阵,但实际只有 6 个有效参数:

[ a  c  e ]
[ b  d  f ]
[ 0  0  1 ]

这六个参数 a、b、c、d、e、f 分别控制不同的变换效果:

参数

作用

对应变换

a

X 轴缩放

scale(sx, sy) 中的 sx

b

Y 轴倾斜(影响 X)

skewY 或旋转的 sin 分量

c

X 轴倾斜(影响 Y)

skewX 或旋转的 sin 分量

d

Y 轴缩放

scale(sx, sy) 中的 sy

e

X 轴平移

translate(tx, ty)中的 tx

f

Y 轴平移

translate(tx, ty)中的 ty

2.2 常见变换的矩阵表示

变换

矩阵参数

效果

平移 (tx, ty)

matrix(1, 0, 0, 1, tx, ty)

移动图形

缩放 (sx, sy)

matrix(sx, 0, 0, sy, 0, 0)

放大/缩小

旋转 θ 度

matrix(cosθ, sinθ, -sinθ, cosθ, 0, 0)

绕原点旋转

X 轴斜切 θ 度

matrix(1, 0, tanθ, 1, 0, 0)

水平倾斜

Y 轴斜切 θ 度

matrix(1, tanθ, 0, 1, 0, 0)

垂直倾斜

2.3 图形化记忆矩阵

知识铺垫:坐标系由二样东西(原点的"位置" + 坐标轴的"方向")定义:

一个 2D 坐标系需要定义:

  • 原点在哪里​ → 一个点​ (0,0)。

  • x 轴朝哪走​ → 一个向量​ (1,0)(从原点出发,向右走 1 单位)。

  • y 轴朝哪走​ → 一个向量​ (0,1)(从原点出发,向上走 1 单位)。

💡点只有位置,向量(x / y 轴基向量)才有方向和长度。x 轴的"方向"就是用向量 (1,0) 来表示的。

核心口诀:

竖着看三列,每一列就是一个"基向量的去向"

[ a  c  e ]   第1列(a,b) → 原来的 x 轴 (1,0) 变成了 (a,b)
[ b  d  f ]   第2列(c,d) → 原来的 y 轴 (0,1) 变成了 (c,d)
[ 0  0  1 ]   第3列(e,f) → 原来的原点 (0,0) 搬到了 (e,f)

说明
原 x 轴方向向量 (1,0)  →  第1列 (a, b)   ← 线性变换部分
原 y 轴方向向量 (0,1)  →  第2列 (c, d)   ← 线性变换部分
原 原点位置     (0,0)  →  第3列 (e, f)   ← 平移部分

简单记忆
第1列 (a, b) → 新 x 轴去哪    
第2列 (c, d) → 新 y 轴去哪    
第3列 (e, f) → 原点去哪       
                               
反过来推:                       
看到矩阵 → 画三个箭头 → 完事!  

逐列拆解教学

第1列 (a, b) = 新 x 轴

想象坐标系里原来指向右方的 x 轴箭头 (1, 0),经过变换后,它的箭头尖端落在了 (a, b)​ 这个位置。

  • a​ → 箭头在水平方向有多长(x 缩放)

  • b​ → 箭头在垂直方向偏了多少(x 轴倾斜程度)

比如第1列是 (2, 0.5),意思就是:新的 x 轴箭头指向右上方,水平拉长 2 倍,同时向上偏了 0.5。

第2列 (c, d) = 新 y 轴

同理,原来指向正上方的 y 轴箭头 (0, 1),变换后尖端落在 (c, d)。

  • d​ → 箭头在垂直方向有多长(y 缩放)

  • c​ → 箭头在水平方向偏了多少(y 轴倾斜程度)

比如第2列是 (1, 1.5),意思就是:新的 y 轴箭头指向右上方,垂直拉长 1.5 倍,同时向右偏了 1。

第3列 (e, f) = 新原点

原来坐标系的原点 (0, 0),变换后搬到了 (e, f)。

  • e​ → 向右移多少

  • f​ → 向上移多少

这一列最好记,就是 translate(e, f)。

实战演示

以 matrix(2, 0.5, 1, 1.5, 2, 1) 为例,脑海里的步骤:

步骤

看哪一列

得到什么

图形效果

1

第1列 (2, 0.5)

新 x 轴指向右上方

x 方向拉长 2 倍,同时向上倾斜

2

第2列 (1, 1.5)

新 y 轴指向右上方

y 方向拉长 1.5 倍,同时向右倾斜

3

第3列 (2, 1)

原点搬到 (2, 1)

整体向右 2、向上 1

下面的图分别展示了:基础变换示例、单列含义高亮、以及完整矩形变换对比、分步拆解 + 公式验证 + 速记卡片,可以结合图片加深理解 。

基础变换示例

单列含义高亮

矩形变换对比

分步拆解 + 公式验证 + 速记卡片

2.4 在 transform 属性中使用矩阵

你可以直接在 transform 属性中使用 matrix() 函数:

<!-- 等价于 translate(30, 20) rotate(45) -->
<rect x="50" y="50" width="100" height="80" 
      transform="matrix(0.707, 0.707, -0.707, 0.707, 30, 20)" fill="blue" />

但手动计算这些数值太麻烦了——这正是 SVGMatrix / DOMMatrix 要解决的问题。

三、SVGMatrix:SVG 的矩阵接口

SVGMatrix 是 SVG 1.1 中定义的矩阵接口,用于在 JavaScript 中表示和操作变换矩阵。

3.1 如何创建 SVGMatrix

SVGMatrix  没有公共构造函数。你需要通过 SVGSVGElement 的 createSVGMatrix() 方法来创建:

const svg = document.querySelector('svg');
const matrix = svg.createSVGMatrix();
console.log(matrix);  // 单位矩阵: { a: 1, b: 0, c: 0, d: 1, e: 0, f: 0 }

创建出来的是一个单位矩阵(Identity Matrix)——相当于“什么都不做”的变换。

3.2 核心属性

SVGMatrix 有六个可读写的属性:

const m = svg.createSVGMatrix();
m.a = 2;   // X 轴放大 2 倍
m.d = 2;   // Y 轴放大 2 倍
m.e = 50;  // X 轴平移 50
m.f = 30;  // Y 轴平移 30
3.3 核心方法

SVGMatrix 提供了一系列方法来组合变换。所有这些方法都返回一个新的 SVGMatrix对象,不会修改原矩阵:

方法

说明

multiply(matrix)

矩阵乘法(后乘),返回新矩阵

inverse()

返回逆矩阵

translate(x, y)

后乘平移变换

scale(scale)

后乘均匀缩放

scaleNonUniform(sx, sy)

后乘非均匀缩放

rotate(angle)

后乘旋转变换(角度制)

rotateFromVector(x, y)

后乘由向量 (x,y) 方向决定的旋转

flipX()

后乘沿 X 轴翻转 [-1, 0, 0, 1, 0, 0]

flipY()

后乘沿 Y 轴翻转 [1, 0, 0, -1, 0, 0]

skewX(angle)

后乘 X 轴斜切

skewY(angle)

后乘 Y 轴斜切

重要概念:后乘(Post-multiply)
  • “后乘”意味着新变换会附加在现有变换之后。如果你有一个矩阵 M,然后调用 M.translate(10, 20),得到的新矩阵等价于 M × Translate(10, 20)。

  • 这和你写在 transform 属性中的顺序是一致的: 越靠后的变换,在矩阵乘法中越靠左。

3.4 链式调用示例
const svg = document.querySelector('svg');
const m = svg.createSVGMatrix();

// 构建一个复合变换:先缩放 2 倍,再旋转 45 度,最后平移 (50, 30)
const result = m
  .scale(2)             // 先缩放
  .rotate(45)           // 再旋转
  .translate(50, 30);   // 最后平移

console.log(result.a, result.b, result.c, result.d, result.e, result.f);

四、DOMMatrix:SVGMatrix 的现代替代

4.1 为什么要用 DOMMatrix?

SVGMatrix 接口在 SVG 2 中已被废弃,被更通用的 DOMMatrix 和 DOMMatrixReadOnly 接口取代。

DOMMatrix 的优势在于:

  1. 不仅限于 SVG——它同时用于 Canvas 2D、CSS Transforms 和 WebGL。

  2. 支持 3D 变换——4×4 矩阵,而 SVGMatrix 只支持 2D

  3. 有公共构造函数——可以直接 new DOMMatrix()。

  4. 在 Web Worker 中可用.

  5. 提供可变版本——DOMMatrix 的方法(如 multiplySelf)会修改自身,而 DOMMatrixReadOnly 的方法返回新对象。

重要提示:WebKitCSSMatrix 在现代浏览器中是 DOMMatrix 的别名;而 SVGMatrix 在 Chrome 150+ 中已经是一个独立的接口对象,不再是 DOMMatrix 的别名。

如果你在做跨浏览器、跨版本的矩阵兼容处理,不要依赖​ SVGMatrix === DOMMatrix 或跨类型的 instanceof 判断。推荐做法:

// 推荐:统一使用 DOMMatrix/DOMMatrixReadOnly
const m = new DOMMatrix();

// SVG 场景下,用 createSVGMatrix() 创建,并按 SVGMatrix 接口使用
const svg = document.querySelector('svg');
const sm = svg.createSVGMatrix();  // 返回的是 SVGMatrix 实例

// 避免:依赖跨类型判断
matrix instanceof SVGMatrix   // 在 Chrome 150+ 上不可靠
SVGMatrix === DOMMatrix       // 在 Chrome 150+ 上为 false
4.2 创建 DOMMatrix
// 方式一:构造函数——创建单位矩阵
const m1 = new DOMMatrix();

// 方式二:从数组创建
const m2 = new DOMMatrix([2, 0, 0, 2, 50, 30]);

// 方式三:从 CSS 变换字符串创建
const m3 = new DOMMatrix('translate(50px, 30px) scale(2) rotate(45deg)');

// 方式四:从现有的 SVGSVGElement 创建(兼容旧代码)
const svg = document.querySelector('svg');
const m4 = svg.createSVGMatrix(); 
4.3 DOMMatrix 的核心方法

DOMMatrix 的方法分为两类:

可变方法(修改自身) :

方法

说明

multiplySelf(matrix)

后乘矩阵,修改自身

preMultiplySelf(matrix)

前乘矩阵,修改自身

invertSelf()

求逆矩阵,修改自身

translateSelf(tx, ty)

后乘平移,修改自身

scaleSelf(sx, sy)

后乘缩放,修改自身

rotateSelf(angle)

后乘旋转,修改自身

不可变方法(返回新对象) :

这些方法继承自 DOMMatrixReadOnly,如 translate()、scale()、rotate()、inverse() 等,返回新的 DOMMatrix 对象,不修改原矩阵。

4.4 实战对比:SVGMatrix vs DOMMatrix
// 旧方式(SVGMatrix)——已废弃
const svg = document.querySelector('svg');
const oldMatrix = svg.createSVGMatrix();
const newMatrix = oldMatrix.translate(50, 30).scale(2);  // 返回新对象,不修改原对象

// 新方式(DOMMatrix)——推荐
const matrix = new DOMMatrix();
matrix.translateSelf(50, 30);  // 修改自身
matrix.scaleSelf(2);            // 修改自身
console.log(matrix.e, matrix.f);  // 50, 30

// 或者链式调用(不可变版本)
const result = new DOMMatrix()
  .translate(50, 30)
  .scale(2);
// result 是新对象,原矩阵不变

五、getCTM() 与 getScreenCTM():获取元素的当前变换矩阵

理解了矩阵的基础之后,我们来学习两个最实用的方法——它们能帮你获取任意 SVG 元素的当前变换矩阵。

这两个方法都定义在 SVGGraphicsElement 接口上。

5.1 getCTM():获取到视口的变换矩阵

getCTM() 返回从当前元素的用户坐标系到最近的视口坐标系的变换矩阵。

简单说:它告诉你这个元素在 SVG 画布中的实际位置和变换状态(考虑了所有祖先元素的 transform)。

<svg viewBox="0 0 500 400">
  <g transform="translate(100, 50)">
    <rect id="box" x="50" y="50" width="80" height="60" transform="rotate(30)" />
  </g>
</svg>

const rect = document.getElementById('box');
const ctm = rect.getCTM();
console.log(`元素在视口中的变换矩阵: a=${ctm.a}, b=${ctm.b}, c=${ctm.c}, d=${ctm.d}, e=${ctm.e}, f=${ctm.f}`);

ctm 包含了从 <g> 的 translate(100, 50) 和 <rect> 自身的 rotate(30) 的复合效果。

5.2 getScreenCTM():获取到屏幕的变换矩阵

getScreenCTM() 返回从当前元素的用户坐标系到屏幕坐标系的变换矩阵。

它和 getCTM() 的区别在于:getScreenCTM() 考虑了 SVG 在整个页面中的位置(包括 CSS 布局的影响),而 getCTM() 只考虑 SVG 内部的坐标系。

const rect = document.getElementById('box');
const screenCTM = rect.getScreenCTM();
console.log(`元素在屏幕上的实际位置: (${screenCTM.e}, ${screenCTM.f})`);
5.3 用矩阵转换坐标

获取到矩阵之后,你可以用它来转换坐标——这是实现拖拽、碰撞检测等交互的核心技术。

const rect = document.getElementById('box');
const ctm = rect.getCTM();

// 创建一个点(在元素的局部坐标系中)
const localPoint = new DOMPoint(10, 10);

// 将局部坐标转换为视口坐标
const viewportPoint = localPoint.matrixTransform(ctm);
console.log(`局部点 (10, 10) 在视口中的位置: (${viewportPoint.x}, ${viewportPoint.y})`);

// 反向转换:从视口坐标到局部坐标
const inverse = ctm.inverse();
const originalPoint = viewportPoint.matrixTransform(inverse);
console.log(`视口点 (${viewportPoint.x}, ${viewportPoint.y}) 回到局部: (${originalPoint.x}, ${originalPoint.y})`);

六、综合实战:用矩阵实现可拖拽、旋转、缩放的图形

让我们综合运用所学知识,构建一个支持拖拽、旋转和缩放的图形编辑器——所有变换都通过矩阵来管理。

<style>
  body { margin: 0; display: flex; justify-content: center; align-items: center; height: 100vh; background: #f0f2f5; }
  .container { background: white; border-radius: 12px; box-shadow: 0 4px 20px rgba(0,0,0,0.1); padding: 20px; }
  svg { border: 1px solid #ddd; border-radius: 8px; cursor: grab; }
  .controls { display: flex; gap: 20px; margin-top: 16px; flex-wrap: wrap; align-items: center; }
  .controls label { display: flex; align-items: center; gap: 8px; font-size: 14px; }
  .controls input[type="range"] { width: 120px; }
  .controls button { padding: 6px 16px; border: none; border-radius: 6px; background: #4A90D9; color: white; cursor: pointer; }
  .controls button:hover { background: #357ABD; }
  #info { font-size: 13px; color: #666; font-family: monospace; }
</style>

<div class="container">
  <svg id="canvas" viewBox="0 0 800 400" width="800" height="400">
    <!-- 网格背景 -->
    <pattern id="grid" width="20" height="20" patternUnits="userSpaceOnUse">
      <path d="M 20 0 L 0 0 0 20" fill="none" stroke="#eee" stroke-width="0.5" />
    </pattern>
    <rect width="600" height="400" fill="url(#grid)" />

    <!-- 被操控的图形 -->
    <g id="target">
      <rect x="-40" y="-30" width="80" height="60" fill="#4A90D9" rx="4" />
      <circle cx="0" cy="0" r="4" fill="white" />
    </g>

    <!-- 坐标轴指示器 -->
    <line x1="-50" y1="0" x2="50" y2="0" stroke="red" stroke-width="1" stroke-dasharray="4,4" />
    <line x1="0" y1="-40" x2="0" y2="40" stroke="green" stroke-width="1" stroke-dasharray="4,4" />
  </svg>

  <div class="controls">
    <label>旋转 <input type="range" id="rotation" min="-180" max="180" value="0" /></label>
    <label>缩放 <input type="range" id="scale" min="0.2" max="3" step="0.05" value="1" /></label>
    <button id="resetBtn">重置</button>
    <span id="info">矩阵: 单位矩阵</span>
  </div>
</div>

const canvas = document.getElementById('canvas');
const target = document.getElementById('target');
const rotationSlider = document.getElementById('rotation');
const scaleSlider = document.getElementById('scale');
const resetBtn = document.getElementById('resetBtn');
const info = document.getElementById('info');

// ---------- 状态管理 (State Management) ----------
// 将变换的核心属性分离,不混合在一个矩阵中
let state = {
  translateX: 300, // 图形中心X
  translateY: 200, // 图形中心Y
  rotation: 0,     // 旋转角度
  scale: 1,        // 缩放比例
};

// 拖拽相关的临时状态
let dragState = {
  isDragging: false,
  // 拖拽开始时,鼠标在SVG中的坐标
  startX: 0,
  startY: 0,
  // 拖拽开始时,图形的中心坐标
  startTranslateX: 0,
  startTranslateY: 0,
};

// ---------- 统一的变换更新函数 (Single Source of Truth) ----------
function updateTransform() {
  // 1. 从分离的状态构建变换矩阵
  // 顺序至关重要:先平移到中心点,然后围绕该点进行旋转和缩放
  const transformMatrix = new DOMMatrix()
    .translate(state.translateX, state.translateY)
    .rotate(state.rotation)
    .scale(state.scale);

  // 2. 将矩阵应用到SVG元素
  target.setAttribute('transform', transformMatrix.toString());

  // 3. 更新信息显示
  const { a, b, c, d, e, f } = transformMatrix;
  info.textContent = `矩阵: [${a.toFixed(2)}, ${b.toFixed(2)}, ${c.toFixed(2)}, ${d.toFixed(2)}, ${e.toFixed(2)}, ${f.toFixed(2)}]`;
}

七、SVGTransform:变换列表中的单个变换

除了矩阵本身,SVG 还提供了SVGTransform 接口,用于表示 transform 属性列表中的单个变换。

7.1 获取元素的变换列表
const rect = document.querySelector('rect');
const transformList = rect.transform;  // SVGAnimatedTransformList
const baseList = transformList.baseVal;  // SVGTransformList

// 遍历所有变换
for (let i = 0; i < baseList.length; i++) {
  const transform = baseList.getItem(i);
  console.log(`变换类型: ${transform.type}, 矩阵:`, transform.matrix);
}

// ---------- 拖拽功能 (Drag and Drop) ----------

// 获取鼠标在SVG坐标系中的精确位置
function getSVGPoint(event) {
  const point = new DOMPoint(event.clientX, event.clientY);
  return point.matrixTransform(canvas.getScreenCTM().inverse());
}

canvas.addEventListener('mousedown', (event) => {
  const point = getSVGPoint(event);
  // 使用 getBBox 和 isPointInFill/isPointInStroke 是更精确的点击检测方法
  const ctm = target.getCTM();
  const localPoint = point.matrixTransform(ctm.inverse());
  const bbox = target.getBBox();

  if (localPoint.x >= bbox.x && localPoint.x <= bbox.x + bbox.width &&
      localPoint.y >= bbox.y && localPoint.y <= bbox.y + bbox.height) {
    
    dragState.isDragging = true;
    canvas.style.cursor = 'grabbing';

    // 记录拖拽开始时的所有状态
    dragState.startX = point.x;
    dragState.startY = point.y;
    dragState.startTranslateX = state.translateX;
    dragState.startTranslateY = state.translateY;
  }
});

document.addEventListener('mousemove', (event) => {
  if (!dragState.isDragging) return;

  const point = getSVGPoint(event);
  // 计算鼠标从开始拖拽到现在的总位移
  const deltaX = point.x - dragState.startX;
  const deltaY = point.y - dragState.startY;

  // 更新状态:图形的新位置 = 初始位置 + 鼠标总位移
  state.translateX = dragState.startTranslateX + deltaX;
  state.translateY = dragState.startTranslateY + deltaY;

  // 调用统一的更新函数来应用变换
  updateTransform();
});

document.addEventListener('mouseup', () => {
  if (dragState.isDragging) {
    dragState.isDragging = false;
    canvas.style.cursor = 'grab';
  }
});

// ---------- 控件事件绑定 (Control Event Listeners) ----------
rotationSlider.addEventListener('input', () => {
  state.rotation = parseFloat(rotationSlider.value);
  updateTransform(); // 修改状态后,调用统一更新
});

scaleSlider.addEventListener('input', () => {
  state.scale = parseFloat(scaleSlider.value);
  updateTransform(); // 修改状态后,调用统一更新
});

resetBtn.addEventListener('click', () => {
  // 重置状态
  state.translateX = 300;
  state.translateY = 200;
  state.rotation = 0;
  state.scale = 1;

  // 同步滑块UI
  rotationSlider.value = 0;
  scaleSlider.value = 1;

  updateTransform(); // 应用重置后的状态
});

// ---------- 初始化 ----------
updateTransform(); // 页面加载时,根据初始状态进行首次渲染
canvas.style.cursor = 'grab';
7.2 SVGTransform 的类型

SVGTransform 的 type 属性可以是以下常量之一:

常量

值

说明

SVG_TRANSFORM_UNKNOWN

0

新创建未初始化对象的默认值

SVG_TRANSFORM_MATRIX

1

通过一个 3x3 矩阵 (matrix(a,b,c,d,e,f)) 定义

SVG_TRANSFORM_TRANSLATE

2

translate()

SVG_TRANSFORM_SCALE

3

scale()

SVG_TRANSFORM_ROTATE

4

rotate()

SVG_TRANSFORM_SKEWX

5

skewX()

SVG_TRANSFORM_SKEWY

6

skewY()

7.3 setMatrix() 方法

SVGTransform 的 setMatrix() 方法可以将任意矩阵设置为该变换的值:

const rect = document.querySelector('rect');
const transformList = rect.transform.baseVal;

// 清除所有现有变换
while (transformList.length > 0) {
  transformList.removeItem(0);
}

// 创建一个新变换并设置矩阵
const newTransform = canvas.createSVGTransform();
const matrix = new DOMMatrix([2, 0, 0, 2, 50, 30]);
newTransform.setMatrix(matrix);
transformList.appendItem(newTransform);

注意:setMatrix() 会复制矩阵参数的值,之后对原矩阵对象的修改不会影响变换。

八、常见错误与避坑指南

错误一:混淆矩阵乘法的顺序
// 错误:顺序搞反了
const m = new DOMMatrix();
m.translateSelf(50, 30);  // 先平移
m.scaleSelf(2);            // 后缩放
// 实际效果:先缩放再平移(因为 scaleSelf 是后乘)

正确做法:理解“后乘”的含义。如果你想要“先平移再缩放”,应该先调用 scaleSelf 再调用 translateSelf。

错误二:直接修改 SVGMatrix 的属性后期望 transform 自动更新

SVGMatrix / DOMMatrix 只是一个 JavaScript 对象,修改它不会自动更新 DOM 元素的 transform 属性。你需要手动将矩阵值写回到 transform 属性中。

正确做法:修改矩阵后,调用 setAttribute('transform', ...) 或操作 SVGTransformList。

错误三:忘记处理坐标原点

rotate() 和 scale() 默认是绕原点 (0,0)旋转和缩放的。如果你的图形不在原点,需要先平移再变换。

// 错误:直接旋转,图形会飞到别处
const m = new DOMMatrix();
m.rotateSelf(45);

// 正确:先平移到图形中心,再旋转
const m = new DOMMatrix();
m.translateSelf(centerX, centerY);
m.rotateSelf(45);
m.translateSelf(-centerX, -centerY);  // 平移回来
错误四:在旧代码中继续使用 SVGMatrix

SVGMatrix 已被废弃。虽然它在大多数浏览器中仍然可用, 新代码应该使用 DOMMatrix。

九、本章总结

概念

说明

变换矩阵

用 6 个参数 (a, b, c, d, e, f) 统一表示平移、旋转、缩放等所有变换

SVGMatrix

SVG 1.1 的矩阵接口,已废弃,被 DOMMatrix 取代

DOMMatrix

现代矩阵接口,支持 2D/3D,有公共构造函数,在 Web Worker 中可用

getCTM()

获取元素到视口的变换矩阵

getScreenCTM()

获取元素到屏幕的变换矩阵

SVGTransform

表示 transform 列表中的单个变换

矩阵乘法

“后乘”意味着新变换附加在现有变换之后

三个核心认知:

  1. 矩阵是变换的通用语言——平移、旋转、缩放、斜切,本质上都是矩阵运算。理解矩阵,就理解了所有变换。

  2. DOMMatrix 是 SVGMatrix 的现代替代——新代码应使用 DOMMatrix,它更强大、更通用、更易用。

  3. getCTM() 和 getScreenCTM() 是坐标转换的利器——它们帮你获取元素的“真实位置”,是实现复杂交互(拖拽、碰撞检测、坐标映射)的基础。

下篇预告

在下一篇文章中,我们将深入探索SVG 数据类型的另一个重要成员—— 列表类型。你将学会:

  • SVGStringList:操作 class 等字符串列表。

  • SVGNumberList:操作 <polygon> 的 points 属性。

  • SVGLengthList:操作多个长度值。

  • 动态管理图形顶点,实现“编辑多边形”的交互功能。

互动话题:尝试用 DOMMatrix 实现一个“绕任意点旋转”的函数——输入角度和旋转中心坐标,输出变换后的矩阵。欢迎在评论区分享你的实现思路!