news 2026/9/7 7:51:59

D3 v7 过渡(Transition)高级控制流详解:生命周期、interrupt、end 与事件监听

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
D3 v7 过渡(Transition)高级控制流详解:生命周期、interrupt、end 与事件监听

D3 v7 过渡(Transition)高级控制流详解:生命周期、interrupt、end 与事件监听

【免费下载链接】d3Bring data to life with SVG, Canvas and HTML. :bar_chart::chart_with_upwards_trend::tada:项目地址: https://gitcode.com/GitHub_Trending/d3/d3

本文以 d3-transition 控制流文档 为主体,系统讲解 D3 过渡从创建、调度、启动、插值到结束的完整生命周期,以及selection.interruptinterrupttransition.end()transition.on等高级控制流 API 的语义、边界与报错时机,并辅以transition.eachtransition.call等工具方法与仓库依赖结构的源码级佐证。读完后,你将能够准确控制多个过渡之间的打断与同步、通过 Promise 等待过渡完成、注册start/end/interrupt/cancel四类过渡事件,并理解 D3 为何要按帧批量执行 tween 以提升性能。

一、先理解前提:过渡是选择(selection)的“动画版”

在 D3 中,过渡是一种与 selection 类似、但用于把 DOM 从当前状态平滑插值到目标状态的接口,典型入口是selection.transition()(详见 元素选择文档 与 d3-transition 总览):

d3.select("body") .transition() .style("background-color", "red");

过渡创建后,可以用 delay、duration、attr、style 等方法进行配置。本文聚焦的是“高级用法”——控制流(control flow):过渡何时开始、如何被打断、如何等待结束、事件何时触发。这也是官方文档 docs/d3-transition/control-flow.md 开篇的定位:“For advanced usage, transitions provide methods for custom control flow.”

二、过渡的一生(The Life of a Transition)

这是控制流文档的核心章节,理解它才能正确理解后面所有 API 的可用窗口。整个生命周期可以划分为五个阶段,每个阶段对“还能改什么”有严格限制:

2.1 创建后:配置窗口(同步求值与延迟求值)

创建过渡之后(例如通过selection.transition()transition.transition()),可以立即用delaydurationattrstyle等方法配置。这里有一个关键的求值时机区别:

  • 指定目标值的方法(如transition.attr同步求值——调用时立即评估;
  • 需要起始值参与插值的方法(如transition.attrTweentransition.styleTween必须推迟到过渡启动时才能求值,因为起始值(当前 DOM 状态)要等到过渡开始才能确定。

2.2 调度(scheduled):本帧末尾或下一帧

过渡创建后不久——当前帧结束时或下一帧期间——会被调度(scheduled)。从这一刻起:

  • delaystart事件监听器不能再修改
  • 尝试修改会抛出错误,消息为"too late: already scheduled"
  • 若过渡已经结束,则抛出"transition not found"

2.3 启动(start):打断与取消发生在这里

过渡随后启动时,发生三件重要的事:

  1. 打断(interrupt):若同一元素上存在同名的活动过渡,它会被打断,并向已注册的监听器派发interrupt事件。
  2. 取消(cancel):启动过渡会取消同一元素上在它之前创建的同名待处理(pending)过渡。
  3. 派发start事件:向监听器派发。

两个容易踩坑的细节(文档原文明确强调):

  • 打断发生在“启动”时,而不是“创建”时。因此即使是一个零延迟(zero-delay)的过渡,也不会立刻打断活动中的旧过渡——旧过渡会被保留“最后一帧”。如果需要立即打断,必须显式调用selection.interrupt()(见下节)。
  • start事件派发后是最后一次可以修改过渡的时机:运行中的过渡的 timing、tween 和监听器都不能再改,强行修改会抛出"too late: already running"(若已结束则为 "transition not found")。
  • 过渡在启动后立即初始化其 tweens

2.4 运行中:按帧调用 tween,且批量初始化

  • 过渡启动的那一帧,但在所有启动于本帧的过渡都启动之后,过渡第一次调用它的 tweens。
  • 这种批量(batching)初始化 tween的做法通常涉及读取 DOM,D3 将其集中处理,目的是避免 DOM 读与写交叉进行(interleaved DOM reads and writes),从而提升性能
  • 在过渡活跃的每一帧,它以经过 缓动(eased) 的t值(0 到 1 之间)调用 tweens;在单帧内部,tweens按注册顺序依次调用。

2.5 结束(end):最终帧与配置销毁

  • 过渡结束时,用**未经缓动(non-eased)**的t = 1再调用一次 tweens,保证终点值精确落地;
  • 随后派发end事件。这是最后一次可以检查(inspect)过渡的时机:结束之后,过渡从元素上删除,其配置被销毁(在打断或取消时配置同样会被销毁);
  • 在销毁之后再尝试检查该过渡,会抛出"transition not found"

把三个报错信息串起来,就是一张“操作窗口”检查表:

阶段还允许做什么越界操作的结果
创建后、调度前配置 delay/duration/tween/listeners
已调度、未启动不能再改 delay 与start监听器抛 "too late: already scheduled"
运行中不能再改 timing/tweens/listeners抛 "too late: already running"
已销毁不能再检查过渡抛 "transition not found"

三、selection.interrupt(name):显式打断活动过渡

selection.interrupt(name)打断所选元素上指定name的活动过渡,并取消指定name的待处理过渡(如果有);若不指定name,则使用null

文档特别指出一个非递归语义:打断某个元素上的过渡,不会影响任何子孙元素上的过渡。这一点在组合组件中至关重要——例如 轴过渡(axis transition) 并不是单个过渡,而是由轴<g>元素子孙元素上的多个相互独立但同步的过渡组成(刻度线、刻度标签、域路径等)。因此要打断一个轴过渡,必须打断其子孙:

selection.selectAll("*").interrupt();

其中通用选择器*会选中所有子孙元素。如果还想连<g>元素本身一起打断:

selection.interrupt().selectAll("*").interrupt();

四、interrupt(node, name):面向单节点的底层版本

interrupt(node, name)是面向单个 DOM 节点的等价操作:打断指定节点上指定name的活动过渡,并取消该name的待处理过渡(如果有);不指定name时使用null。它与selection.interrupt互为姊妹 API——前者作用于选择集合,后者直接作用于单个节点,适合在非 selection 上下文中(例如原生 DOM 事件处理器里拿到thisevent.currentTarget后)使用。

五、transition.end():用 Promise 等待过渡完成

transition.end()返回一个Promise:当所有被选中的元素都完成过渡时 resolve;如果任一元素的过渡被取消(cancel)或打断(interrupt),该 Promise 会被 reject

这为“过渡完成后做后续动作”提供了基于 Promise 的现代写法(例如在await之后再执行 DOM 移除、或触发下一个流程分支),而不再必须依赖end事件回调。注意它与第二节的呼应:由于 interrupt/cancel 会使 Promise reject,用它串接业务流程时要显式catch或判断场景。

六、transition.on(typenames, listener):四类过渡事件

transition.on(typenames, listener)为每个被选中元素添加或移除指定事件typenameslistener。支持的事件类型共有四种:

  • start—— 过渡启动时;
  • end—— 过渡结束时;
  • interrupt—— 过渡被打断时;
  • cancel—— 过渡被取消时。

需要特别强调(文档原文明确指出):这些不是原生 DOM 事件(与selection.on/selection.dispatch实现的机制不同),而是transition 事件,由过渡机制本身派发。

以下细节直接决定监听器的行为是否符合预期:

  1. 命名空间:类型后可以可选地跟一个点号(.)和一个名字,从而允许同一类型注册多个回调,例如start.foostart.bar
  2. 多个 typenames:用空格分隔,如interrupt endstart.foo start.bar
  3. 回调参数:当过渡事件在某个选中节点上派发时,listener 以该过渡元素为上下文调用,接收当前数据d、当前索引i、当前分组nodesthis为当前 DOM 元素;
  4. 数据与索引的语义:监听器总能拿到元素的最新 datum,但索引是选择(selection)的属性,在监听器被分配时就固定了——要更新索引需重新分配监听器;
  5. 替换与移除:同一元素上若已为相同typename注册过监听器,旧监听器会被移除后再加新的。移除单个监听器传null作为 listener;移除某名字下的所有监听器,传nulltypename.foofoo为该名字);移除所有未命名监听器,typename.
  6. getter 形式:不传 listener 时,返回第一个(非 null)选中元素上该typename当前已分配的监听器(如有);指定多个 typenames 时返回第一个匹配的监听器。

七、过渡上的控制流工具方法:each / call / empty / nodes / node / size

文档后半部分收录了过渡对象上的一组“直通选择”的控制流方法。它们与 d3-selection 同名方法 语义等价,让你无需先transition.selection()拿到选择即可操作。

transition.each(function)

为每个选中元素调用指定函数,传入当前数据d、索引i、分组nodesthis为当前 DOM 元素。可用于为每个元素执行任意代码,特别适合构建同时访问父级与子级数据的上下文。等价于selection.each

transition.call(function, ...arguments)

调用指定函数恰好一次,传入本过渡以及任意可选参数,并返回本过渡以便链式调用——即“手动调用函数的链式包装”。文档给出的可复用配色示例:

function color(transition, fill, stroke) { transition .style("fill", fill) .style("stroke", stroke); }

于是可以这样使用:

d3.selectAll("div").transition().call(color, "red", "blue");

它等价于手动展开:

color(d3.selectAll("div").transition(), "red", "blue");

这种写法让“给一个过渡批量设置若干属性/样式”的公共逻辑可以封装成独立函数并复用,等价于selection.call

empty / nodes / node / size 速查

方法返回等价于
transition.empty()过渡是否不含任何(非 null)元素(布尔)selection.empty
transition.nodes()过渡中所有(非 null)元素组成的数组selection.nodes
transition.node()第一个(非 null)元素;过渡为空时返回nullselection.node
transition.size()过渡中元素总数selection.size

这四个方法与生命周期检查直接相关:例如在start监听器里用transition.node()拿到元素、用transition.nodes()做一次性批量处理,都属于“start 之后仍是合法检查窗口”内的安全操作;而一旦过渡结束,过渡本身已被销毁,只能操作节点而不能再操作过渡。

八、结合本仓库:依赖结构、版本与文档质量如何验证

本仓库是 D3 的顶层发行包,当前版本为7.9.0(见 package.json)。控制流文档所描述的d3-transitionAPI 在本仓库中的落地方式可以从三处确认:

  1. 依赖声明:package.json 的dependencies中声明"d3-transition": "^3.0.1",说明本文所述行为对应 d3-transition 3.x 系列 API;yarn.lock 中将其解析为 d3-transition 3.0.1 的发布产物。
  2. 统一再导出:src/index.js 以export * from "d3-transition";(第 29 行)将该模块的全部公共 API——包括interruptactivetransition构造器及transition.ontransition.end等——并入 D3 顶层命名空间。因此d3.active(node, name)d3.interrupt(node, name)等函数在import * as d3 from "d3"后均可直接调用。
  3. 文档锚点校验:test/docs-test.js 会递归爬取docs/下全部 Markdown,收集标题与{#anchor}形式的显式锚点,并校验所有内部链接指向的锚点真实存在(documentation links point to existing internal anchors)。这意味着 docs/d3-transition/control-flow.md 中如{#selection_interrupt}{#transition_end}等锚点以及指向 timing、selecting、modifying 的链接,在仓库测试层面是被持续保障的。

从源码结构看,本仓库并不内嵌 d3-transition 的实现源码——它作为独立 npm 包通过依赖引入,文档中的 “Source” 指向该独立仓库的src/transition/*.js(如end.json.js)与src/selection/interrupt.jssrc/interrupt.js。因此若需逐行研读控制流实现(例如 tween 批量调度的具体帧循环、too late错误的抛出位置),应以 d3-transition 包为准;本仓库提供的是 API 契约、版本约束与文档锚点保障。

九、适用前提与限制小结

  • 版本前提:本文内容以本仓库 package.json 声明的d3-transition ^3.0.1与 D3 7.9.0 为准;行为描述继承自 docs/d3-transition/control-flow.md,适用于 d3-transition 3.x 文档所对应的过渡机制。
  • 事件语义限制start/end/interrupt/cancel是过渡事件而非原生 DOM 事件,不能通过selection.onselection.dispatch那套 DOM 事件机制触发或监听。
  • 修改窗口限制:三个错误消息("too late: already scheduled"、"too late: already running"、"transition not found")划定了 delay、监听器、timing、tween 各自的可修改边界,任何自动化脚本都应按此窗口安排调用时机。
  • 打断粒度限制interrupt不递归到子孙元素;打断组合组件(如轴)的过渡时,必须显式selectAll("*").interrupt()覆盖其子孙。
  • end()的 reject 语义transition.end()在任一元素被取消或打断时 reject,跨过渡编排时必须处理该分支。

掌握上述生命周期时间线与各 API 的边界,就能在 D3 中可靠地实现过渡的打断、同步、链式编排与 Promise 化收尾——这正是 d3-transition 控制流文档 所定义的“高级用法”的完整闭环。

【免费下载链接】d3Bring data to life with SVG, Canvas and HTML. :bar_chart::chart_with_upwards_trend::tada:项目地址: https://gitcode.com/GitHub_Trending/d3/d3

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/7 7:46:16

VB.NET实现非均匀B样条曲线:Cox-de Boor递推与GDI+交互拖拽实战

简介&#xff1a;一份面向VB.NET开发者与计算机图形学习者的非均匀B样条&#xff08;NURBS&#xff09;曲线实现项目。这份工程以完整Visual Studio项目呈现&#xff0c;包含控制点定义、节点向量与基函数计算、曲线求值及反算等核心模块&#xff0c;并封装为可直接调用的VB类&…

作者头像 李华
网站建设 2026/9/7 7:46:12

DevExpress控件多Sheet导出Excel通用方案封装

简介&#xff1a;这份资源面向使用DevExpress WinForm开展桌面报表开发的.NET程序员&#xff0c;提供一套可直接落地的通用Excel导出方案&#xff0c;针对GridControl自带导出无法输出图片、多表头样式丢失&#xff0c;以及PivotGridControl导出时自动分组导致版式错乱等典型痛…

作者头像 李华
网站建设 2026/9/7 7:43:08

基于STM32与ZAM6228的8通道PT100温度采集系统设计

简介&#xff1a;致远电子ZAM6228八通道PT100温度测量模块的单片机实战程序&#xff0c;面向嵌入式学习者与工业测温项目开发者&#xff0c;解决缺少官方协议文档时如何用STM32单片机的普通IO口模拟IIC时序&#xff0c;完成八路PT100电阻温度数据读取&#xff0c;并驱动OLED显示…

作者头像 李华