首页
/ d3 转场定时机制详解:transition.delay、duration、ease 与 easeVarying 的完整用法

d3 转场定时机制详解:transition.delay、duration、ease 与 easeVarying 的完整用法

2026-09-06 14:00:48作者:牧宁李

在 D3(Data-Driven Documents)中,transition 负责把选区从当前状态平滑地过渡到目标状态,而 transition 的定时机制 决定了这场"过渡"何时开始、持续多久、以什么节奏推进。本文基于当前仓库(d3 v7.9.0)的官方文档 docs/d3-transition/timing.md,系统讲解 delaydurationeaseeaseVarying 四个 API 的取值规则、默认值与函数式配置方式,并结合仓库源码结构剖析其底层约定,帮助你写出从"逐个错开启动"到"按数据驱动缓动"的精细动画。

一、三个可配置维度与默认值

一次 D3 transition 的时间行为由三个维度共同决定:

维度 API 含义 默认值
延迟 transition.delay(value) 每个元素启动前的等待时间(毫秒) 0
时长 transition.duration(value) 每个元素动画本身的持续时间(毫秒) 250
缓动 transition.ease(value) / transition.easeVarying(factory) 将归一化时间 t 映射为缓动后时间 tʹ 的函数 d3.easeCubic

官方文档在 timing.md 开篇即点明了定时机制的实战价值:逐元素延迟(per-element delay)可以错开元素的过渡节奏,例如在排序柱状图中让每一根柱子按索引依次移动,显著改善视觉感知(文档引用了可视化领域的经典研究《Animated Transitions in Statistical Data Graphics》作为依据)。

一个直观的组合示例:

selection
  .transition()
  .delay(250)        // 整体先等 250ms
  .duration(750)     // 每根柱子动画持续 750ms
  .ease(d3.easeCubic) // 默认就是 easeCubic,这里显式写出
  .attr("transform", (d, i) => `translate(0,${y(d.value)})`);

二、transition.delay(value)

基本用法

为选区中的每个元素设置过渡延迟,单位为毫秒:

transition.delay(250);

value 既可以是常量,也可以是函数:

// 函数形式:延迟随数据索引递增,形成"级联启动"效果
transition.delay((d, i) => i * 10);

函数形式求值约定

若 value 为函数,它会**立即(immediately)**按顺序对每个选中的元素求值,求值时的约定与 d3-selection 的访问器函数完全一致:

  • 依次传入当前数据(d)、当前索引(i)、当前分组(nodes);
  • this 指向当前 DOM 元素;
  • 函数的返回值即该元素的延迟。

这意味着函数里可以直接读 this 上的状态(例如 this.getAttribute('x'))或按数据字段计算,而不必依赖外部闭包变量。

取值(getter)与默认值

  • 不传参数调用时,返回第一个非空元素的当前延迟值:
transition.delay() // 250

文档特别提醒:这一 getter 只在"你确定 transition 恰好只包含一个元素"时才真正有用——多元素选区读到的只是第一个元素值。

  • 若不指定延迟,默认值为 0

错开(staggering)的两种进阶写法

  1. 按索引错开:把延迟设为索引的倍数是最方便的写法,如上面的 i * 10,100 个元素会在 1000ms 内依次启动;
  2. 按数据错开,或排序后再按索引错开:延迟可以写成数据的任意函数;官方文档还建议在计算索引型延迟之前,先对选区排序(参见 selection.sort 的文档),这样"视觉上从上到下依次移动"的效果才能与数据顺序一致。
// 先排序,再错开:让柱子从最长到最短依次就位
selection
  .sort((a, b) => b.value - a.value)
  .transition()
  .delay((d, i) => i * 15)
  .duration(400)
  .attr("height", (d) => y(d.value));

三、transition.duration(value)

基本用法

为每个元素设置过渡时长,单位为毫秒:

transition.duration(750);

delay 一样,value 可以是常量或函数。函数形式同样是立即对每个选中的元素按顺序求值,传入 dinodesthis 为当前 DOM 元素,返回值即该元素的时长:

// 数据越大的柱子过渡越久,形成"重量感"
selection.transition()
  .duration((d) => 250 + d.value * 2);

默认值与 getter 行为:

  • 不指定时长时,默认 250ms(这是 d3-transition 的内置默认时长);
  • 不传参调用时,返回第一个非空元素的当前时长:
transition.duration() // 750

同样注意:getter 仅在 transition 恰好只含一个元素时才有普遍意义。

duration 与 delay 的协作

两个参数都是逐元素独立的,因此总动画窗口可以按元素估算为 delay(i) + duration(i)。实践中常把 delay 用作"启动编排"、duration 用作"单元素节奏",例如入场动画 delay(i * 30).duration(300),离场动画则常用 duration(200).remove()remove() 的用法见 transition 修改类 API 文档)。

四、transition.ease(value)

缓动函数的契约

ease(value) 为所有选中元素指定同一个缓动函数:

transition.ease(d3.easeCubic);

value 必须是函数(不能是字符串)。缓动函数在动画的每一帧被调用一次,约定如下:

  • 入参是归一化时间 t,取值范围 [0, 1];
  • 返回缓动后的时间 ,通常也在 [0, 1] 内;
  • 一个好的缓动函数应满足 t = 0 时返回 0,t = 1 时返回 1。

若不指定,默认使用 d3.easeCubic(即 easeCubicInOut,对称三次缓动)。getter 行为:

transition.ease() // d3.easeCubic

同样,不传参返回的是第一个非空元素的当前缓动函数,仅在单元素 transition 中才有确定意义。

内置缓动函数家族

d3-ease 提供了一族以"指数"为参数的缓动函数,d3.easePolyIn 通过 .exponent(e) 可生成不同"陡峭度"的曲线(默认 e = 3,等价于三次缓动);easeCubiceaseCubicInOut 的别名。完整的函数列表、公式与曲线示例见 d3-ease 文档。本仓库的 docs/components/ExampleEase.vue 就是用 d3.ticks(0, 1, 500) 生成 500 个采样点、再对每个缓动函数求值绘制曲线,直观展示了 easePolyIn.exponent(e) 随指数变化的形态——这正是理解"缓动函数 = 把线性时间重排成非线性时间"的最好方式。

从源码结构看,缓动函数只负责"时间的重排",真正的属性插值(数值、颜色、路径等)由 transition 的 tween 机制配合 d3-interpolate 完成(参见 transition 修改类 API),两者组合起来才构成完整的逐帧动画。

五、transition.easeVarying(factory)

当"同一个缓动函数不适合所有元素"时,easeVarying 提供了按元素生成不同缓动函数的工厂机制:

transition.easeVarying((d) => d3.easePolyIn.exponent(d.exponent));

ease 的区别在于:

维度 ease(value) easeVarying(factory)
参数类型 必须是缓动函数 必须是返回缓动函数的工厂
作用范围 所有选中元素共用一个函数 每个节点调用一次工厂,各自得到一个函数
求值时机 设置时一次 对选区的每个节点求值
求值约定 传入 dinodesthis 为当前 DOM 元素

上例中,每个数据对象自带 exponent 字段,于是每根柱子都拥有"陡峭度"不同的 easePolyIn 曲线——这是纯数据驱动的差异化动画。例如指数越大,加速越猛,适合表现"数据量越大的元素冲得越快"这类叙事。

由于工厂函数拥有与 d3 访问器完全一致的求值约定,你也可以根据 d 的类别、i 的奇偶或 this 上的几何位置来挑选缓动函数,实现成组的、方向感的或空间感知的节奏差异。

六、仓库源码层面的印证

  • 本仓库是 d3 v7 的聚合入口包。src/index.js 第 29 行 export * from "d3-transition"; 说明 transition 的全部 API(含 delay/duration/ease/easeVarying)以 re-export 方式从独立的 d3-transition 包进入 d3 命名空间;
  • package.json"d3-transition": "^3.0.1""d3-ease": "^3.0.1" 声明了当前版本所依赖的两个子包版本。timing.md 文档中标注的 Source 文件(src/transition/delay.jssrc/transition/duration.jssrc/transition/ease.jssrc/transition/easeVarying.js)位于 d3-transition 独立仓库中,本仓库的 docs/d3-transition/timing.md 即其官方文档镜像,可作为 API 语义的权威依据;
  • 文档构建流程由 package.jsondocs:dev / docs:build 脚本驱动(VitePress),因此 docs 下的 Markdown 与 d3 官方文档站保持同一套源文件,API 描述可直接当作当前仓库行为的规范来看待;
  • 测试入口 test/d3-test.jstest/docs-test.js 分别负责库行为与文档构建校验,yarn test(即 mocha 'test/**/*-test.js' && eslint src test)是验证本仓库改动的基础命令。

七、实用速查与易错点

  1. 单位delayduration 均为毫秒,不要与归一化的 0–1 时间混淆;
  2. 默认值:delay 0ms、duration 250ms、ease easeCubic——不写定时代码也能得到一个 250ms 的默认过渡;
  3. 函数求值是立即的:delay/duration/easeVarying 的函数在设置时就被逐元素求值并固化,而不是每帧求值;每帧被调用的只有缓动函数本身;
  4. getter 的适用边界transition.delay() 等 getter 只报告第一个非空元素的值,多元素选区不要依赖它做断言;
  5. ease 必须是函数:传字符串会静默失效,需要 d3.easeCubicInOut 这类函数引用;
  6. 排序先于索引错开:希望"按视觉位置依次启动"时,先 sort 再计算 i * step,否则索引顺序与视觉顺序可能不一致;
  7. 需要逐帧逻辑时用 tween:timing 只控制"时间轴",若需要逐帧自定义属性更新,应转到 tween/attrTween 等 API;需要等待动画结束再执行后续动作时,用 control-flow 中的 on/end

八、相关文档索引

掌握 delay/duration/ease/easeVarying 四个 API 及其"常量或函数"的取值约定,就掌握了 D3 转场时间轴的全部控制面:从统一的 250ms 默认过渡,到按索引级联、按数据差异化缓动的精细编排,都是这四行 API 的直接组合。

登录后查看全文
热门项目推荐
相关项目推荐