d3 变换插值详解:d3.interpolateTransformCss 与 d3.interpolateTransformSvg 背后的矩阵分解原理
在 d3 的过渡系统中,transform 属性是最常参与动画的属性之一,但它也是最容易被"错误插值"的属性。本篇技术指南围绕 docs/d3-interpolate/transform.md 展开,完整覆盖 d3.interpolateTransformCss 与 d3.interpolateTransformSvg 两个变换插值器的用法、参数与行为,并顺着"CSS 矩阵分解标准"这一线索,深入到为什么不能直接对矩阵 6 个系数做线性插值、d3 是如何把 transform 字符串拆解成 translate / rotate / x-skew / scale 四个分量再逐分量过渡的。读完本篇,你将能够正确地为 SVG 元素和 CSS 样式编写变换过渡,理解 d3-transition 在 attr("transform") 与 style("transform") 上自动选用变换插值器的机制。
什么是变换插值
d3-interpolate 为颜色、数字、字符串等提供了一系列插值器(见 docs/d3-interpolate/value.md 与 docs/d3-interpolate/color.md),而变换插值专用于插值 CSS 和 SVG 的 2D 变换。原始文档给出的核心定义是:
Interpolators for CSS and SVG transforms. The interpolation method is standardized by CSS: see matrix decomposition for animation.
即:两个变换字符串之间的插值方法遵循 CSS 规范中"matrix decomposition for animation"(矩阵分解用于动画)的标准算法。d3 并不发明自己的算法,而是把浏览器 CSS 动画处理 2D 变换的同一套数学搬到了 JavaScript 里,这保证了"手写的 d3 过渡"与"浏览器原生 transform 过渡"在视觉上保持一致。
d3 主包(src/index.js)以 export * from "d3-interpolate" 的方式把 d3-interpolate 的全部 API 并入命名空间,因此 d3.interpolateTransformCss 和 d3.interpolateTransformSvg 与 d3.rgb、d3.interpolateRgb 一样,在引入整包 d3 后直接可用(package.json 中声明的依赖为 d3-interpolate: ^3.0.1)。
两个变换插值器 API
d3.interpolateTransformCss(a, b)
d3.interpolateTransformCss("translateY(12px) scale(2)", "translateX(30px) rotate(5deg)")(0.5) // "translate(15px,6px) scale(1.5,1.5)" 前实际输出 "translate(15px,6px) rotate(2.5deg) scale(1.5,1.5)"
以文档原例为准,调用与返回值为:
d3.interpolateTransformCss("translateY(12px) scale(2)", "translateX(30px) rotate(5deg)")(0.5)
// "translate(15px,6px) rotate(2.5deg) scale(1.5,1.5)"
语义说明(继承自原始文档):
- 接收两个 2D CSS 变换字符串
a(起点)与b(终点),返回一个插值器; - 每个 transform 先被分解为标准表示:translate、rotate、x-skew、scale 四个分量;
- 然后对这四个分量在
t ∈ [0, 1]上做线性插值; - 结果是一个新的 2D CSS 变换字符串。
以该示例核验一下中间过程:起点 translateY(12px) scale(2) 分解为 translate(0, 12) · scale(2, 2),终点 translateX(30px) rotate(5deg) 分解为 translate(30, 0) · rotate(5deg)。t = 0.5 时平译为 (15px, 6px)、旋转角为 2.5deg、缩放为 1.5——与返回值完全吻合,这正是"分解后再插值"而非"矩阵系数插值"的直接体现。
重要前提:绝对单位。 从仓库变更历史 CHANGES.md 可以确认两条关键演进:
- "Change d3.interpolateTransformCss to require absolute units"——CSS 版变换要求长度带绝对单位(
px等),像translateX(30)这种不带单位的 CSS 写法不被接受; - "Change d3.interpolateTransformCss to use DOMMatrix"——d3-interpolate v2 起底层改用浏览器原生的
DOMMatrix做矩阵运算,解析与合成都委托给标准 DOMMatrix API,这也是它能可靠处理复合变换写法的基础。
d3.interpolateTransformSvg(a, b)
d3.interpolateTransformSvg("skewX(-60)", "skewX(60) translate(280,0)")
// "translate(140,0) skewX(0)"
语义与 CSS 版一致:返回两个 2D SVG 变换之间的插值器,每个 transform 被分解为 translate、rotate、x-skew、scale 四个分量后逐分量插值。与 CSS 版的差异在于输入遵循 SVG transform 属性的语法(无单位数字、rotate(5) 而非 rotate(5deg) 等)。
这个例子特别值得展开,因为它展示了变换顺序造成的非线性视觉轨迹。终点 skewX(60) translate(280,0) 中,translate 发生在 skew 之后(矩阵乘法顺序),相当于在倾斜坐标系里平移,其等价矩阵为:
skewX(60) = [[1, 0], [tan(60°), 1]],与 T(280,0) 相乘后 ≈ [[1, 280], [1.732, 1]]
而起点 skewX(-60) 的分解结果约为 translate(-1.4px, 0) skewX(-60)(因为负角度切向分量近似为零,矩阵近乎纯 skew)。因此 t = 0.5 的中间状态是"接近零的 skewX + 约 140 的平移",最终输出 translate(140,0) skewX(0)。若对两条轨迹的原始矩阵系数直接线性取中点,得到的中间形状会与文档返回值不同——再次说明分量插值保证了"运动语义"而不是"矩阵数值"被插值。
核心原理:CSS 矩阵分解
两个插值器共同的数学基础是 CSS 规范定义的 2D 变换矩阵分解。流程可以概括为:
-
解析:把 transform 列表字符串解析为一个 2D 仿射矩阵(3×3,平移、旋转、缩放、错切);
-
分解:将该唯一矩阵重写为规范化的四步分解形式:
M = translate(tx, ty) · rotate(α) · skewX(φ) · scale(sx, sy) -
分量插值:对
(tx, ty)、α、φ、(sx, sy)六个标量分别在t处线性插值; -
合成:按同样的四步顺序把分量拼回一个变换字符串输出。
这种分解之所以必要,是因为 2D 仿射矩阵只有 6 个自由度,而"平移多少、旋转多少、错切多少、缩放多少"这 4 类语义参数也是 6 个自由度。直接对 6 个矩阵系数线性插值时,中间帧通常不再是一个"干净的"纯旋转/缩放组合,会出现意料之外的错切与形变;而分解-插值-合成保证了中间帧仍是语义正确的标准变换,运动轨迹与 CSS 动画的行为一致。
从源码结构看,这套实现位于 d3-interpolate 子包(当前仓库通过 src/index.js 的 export * from "d3-interpolate" 聚合其全部导出),文档中给出的实现入口为 d3-interpolate 的 src/transform/index.js 模块;本仓库中不携带该子包源码,因此上文的矩阵数值推演属于按 CSS 分解规范的计算核验,而行为描述均以 docs/d3-interpolate/transform.md 的原文与示例为准。
与 d3-transition 的配合
变换插值器的真正舞台在 d3-transition 中。从 CHANGES.md 的 4.0 变更说明可知:
The d3.interpolateTransform interpolator has been renamed to d3.interpolateTransformSvg, and there is a new d3.interpolateTransformCss to interpolate CSS transforms! This allows d3-transition to automatically interpolate both the SVG transform attribute and the CSS transform style property.
也就是说:
transition.attr("transform", …)—— 目标是 SVG 的transform属性,d3-transition 会自动使用interpolateTransformSvg;transition.style("transform", …)—— 目标是 CSS 的transform样式属性,则自动使用interpolateTransformCss;- 两者都只支持 2D 变换(
rotateX/rotateY/rotate3d、perspective等 3D 写法不在支持范围内,原文 4.0 说明明确标注 "only 2D CSS transforms are supported")。
典型用法示例(属性过渡,写法可直接复用):
// SVG:对 transform 属性过渡,内部走 interpolateTransformSvg
selection.transition()
.attr("transform", (d, i) => `translate(${i * 80},0) rotate(45)`);
// CSS:对 transform 样式过渡,内部走 interpolateTransformCss(长度必须带绝对单位)
selection.transition()
.style("transform", "translateX(30px) rotate(5deg)");
也可以绕过 transition 的自动分发,手动构造变换插值器作为 tween:
selection.transition()
.attrTween("transform", function() {
return d3.interpolateTransformSvg("skewX(-60)", "skewX(60) translate(280,0)");
});
这种"手动 tween + 变换插值器"的组合,适合起点不是 DOM 当前值、或者需要精确控制中间轨迹的场景。
版本沿革与使用注意
结合 CHANGES.md 的变更记录,使用这两个 API 时需要注意以下历史约束:
| 变更 | 影响 |
|---|---|
d3.interpolateTransform 更名为 d3.interpolateTransformSvg,新增 d3.interpolateTransformCss(4.0) |
旧代码中的 d3.interpolateTransform(...) 只应作用于 SVG transform 语法;升级后请显式选择 Svg/Css 版本 |
d3.transform 方法被移除(4.0) |
不能再依赖旧的公开 API 手动解析变换矩阵,统一交给两个插值器内部处理 |
interpolateTransformCss 改用 DOMMatrix(2.x) |
运行环境需支持标准 DOMMatrix(现代浏览器均支持) |
interpolateTransformCss 要求绝对单位 |
CSS 变换中的平移量必须写 12px 这类带单位形式 |
此外还有两点实践要点:
- 只支持 2D:两个插值器均面向 2D 变换;若元素同时使用 3D CSS 变换,请改用浏览器原生过渡或自行处理。
- 分量插值的边界情况:分解过程中若矩阵接近奇异(例如某轴缩放趋近于 0),分解出的角度可能不稳定,这在 CSS 规范本身中就是已知特性;实践中避免在过渡端点使用
scale(0)之类的退化变换即可。
小结
d3.interpolateTransformCss(a, b)与d3.interpolateTransformSvg(a, b)分别面向 CSS transform 样式与 SVG transform 属性,均返回一个t ∈ [0,1]的插值器;- 插值方法遵循 CSS 标准的矩阵分解:解析 → 分解为 translate/rotate/x-skew/scale → 分量线性插值 → 合成输出;
- d3-transition 对
attr("transform")与style("transform")会自动分派这两个插值器,但仅限 2D 变换; - 使用 CSS 版本时保持长度带绝对单位(
px),SVG 版本则使用无单位语法; - 文档原文位于 docs/d3-interpolate/transform.md,API 索引见 docs/api.md 中 "interpolate 2D CSS/SVG transforms" 两条,历史行为变更见 CHANGES.md。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0624
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00