在前六篇文章中,我们学习了如何绘制各种 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" />现在你想让它:
向右移动 30 像素。
旋转 45 度。
放大 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 轴平移 303.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 的优势在于:
不仅限于 SVG——它同时用于 Canvas 2D、CSS Transforms 和 WebGL。
支持 3D 变换——4×4 矩阵,而 SVGMatrix 只支持 2D
有公共构造函数——可以直接 new DOMMatrix()。
在 Web Worker 中可用.
提供可变版本——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+ 上为 false4.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 列表中的单个变换 |
矩阵乘法 | “后乘”意味着新变换附加在现有变换之后 |
三个核心认知:
矩阵是变换的通用语言——平移、旋转、缩放、斜切,本质上都是矩阵运算。理解矩阵,就理解了所有变换。
DOMMatrix 是 SVGMatrix 的现代替代——新代码应使用 DOMMatrix,它更强大、更通用、更易用。
getCTM() 和 getScreenCTM() 是坐标转换的利器——它们帮你获取元素的“真实位置”,是实现复杂交互(拖拽、碰撞检测、坐标映射)的基础。
下篇预告
在下一篇文章中,我们将深入探索SVG 数据类型的另一个重要成员—— 列表类型。你将学会:
SVGStringList:操作 class 等字符串列表。
SVGNumberList:操作 <polygon> 的 points 属性。
SVGLengthList:操作多个长度值。
动态管理图形顶点,实现“编辑多边形”的交互功能。
互动话题:尝试用 DOMMatrix 实现一个“绕任意点旋转”的函数——输入角度和旋转中心坐标,输出变换后的矩阵。欢迎在评论区分享你的实现思路!