首页
/ d3 变换插值详解:d3.interpolateTransformCss 与 d3.interpolateTransformSvg 背后的矩阵分解原理

d3 变换插值详解:d3.interpolateTransformCss 与 d3.interpolateTransformSvg 背后的矩阵分解原理

2026-09-06 16:01:49作者:钟日瑜

在 d3 的过渡系统中,transform 属性是最常参与动画的属性之一,但它也是最容易被"错误插值"的属性。本篇技术指南围绕 docs/d3-interpolate/transform.md 展开,完整覆盖 d3.interpolateTransformCssd3.interpolateTransformSvg 两个变换插值器的用法、参数与行为,并顺着"CSS 矩阵分解标准"这一线索,深入到为什么不能直接对矩阵 6 个系数做线性插值、d3 是如何把 transform 字符串拆解成 translate / rotate / x-skew / scale 四个分量再逐分量过渡的。读完本篇,你将能够正确地为 SVG 元素和 CSS 样式编写变换过渡,理解 d3-transition 在 attr("transform")style("transform") 上自动选用变换插值器的机制。

什么是变换插值

d3-interpolate 为颜色、数字、字符串等提供了一系列插值器(见 docs/d3-interpolate/value.mddocs/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.interpolateTransformCssd3.interpolateTransformSvgd3.rgbd3.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 可以确认两条关键演进:

  1. "Change d3.interpolateTransformCss to require absolute units"——CSS 版变换要求长度带绝对单位(px 等),像 translateX(30) 这种不带单位的 CSS 写法不被接受;
  2. "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 变换矩阵分解。流程可以概括为:

  1. 解析:把 transform 列表字符串解析为一个 2D 仿射矩阵(3×3,平移、旋转、缩放、错切);

  2. 分解:将该唯一矩阵重写为规范化的四步分解形式:

    M = translate(tx, ty) · rotate(α) · skewX(φ) · scale(sx, sy)
    
  3. 分量插值:对 (tx, ty)αφ(sx, sy) 六个标量分别在 t 处线性插值;

  4. 合成:按同样的四步顺序把分量拼回一个变换字符串输出。

这种分解之所以必要,是因为 2D 仿射矩阵只有 6 个自由度,而"平移多少、旋转多少、错切多少、缩放多少"这 4 类语义参数也是 6 个自由度。直接对 6 个矩阵系数线性插值时,中间帧通常不再是一个"干净的"纯旋转/缩放组合,会出现意料之外的错切与形变;而分解-插值-合成保证了中间帧仍是语义正确的标准变换,运动轨迹与 CSS 动画的行为一致。

从源码结构看,这套实现位于 d3-interpolate 子包(当前仓库通过 src/index.jsexport * from "d3-interpolate" 聚合其全部导出),文档中给出的实现入口为 d3-interpolatesrc/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/rotate3dperspective 等 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 这类带单位形式

此外还有两点实践要点:

  1. 只支持 2D:两个插值器均面向 2D 变换;若元素同时使用 3D CSS 变换,请改用浏览器原生过渡或自行处理。
  2. 分量插值的边界情况:分解过程中若矩阵接近奇异(例如某轴缩放趋近于 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
登录后查看全文
热门项目推荐
相关项目推荐