d3-transition 完全实战指南:d3 v7 中的过渡、插值器与动画控制流

发布时间:2026/9/7 5:26:34
d3-transition 完全实战指南:d3 v7 中的过渡、插值器与动画控制流 d3-transition 完全实战指南d3 v7 中的过渡、插值器与动画控制流【免费下载链接】d3Bring data to life with SVG, Canvas and HTML. :bar_chart::chart_with_upwards_trend::tada:项目地址: https://gitcode.com/GitHub_Trending/d3/d3d3-transition 是 d3 生态中负责“让数据动起来”的核心模块它提供了一组与 selection 风格一致但面向动画的接口让 DOM 属性、样式、文本从当前状态平滑插值到目标状态而不是瞬间跳变。本文以本仓库文档 docs/d3-transition.md 及其四篇子文档Selecting、Modifying、Timing、Control flow为骨架完整覆盖过渡的创建、时序配置、内容修改方法、插值器选择机制与生命周期控制流并结合本仓库的聚合源码与测试给出可验证的工程事实帮助你掌握 d3 v7 中编写、同步与调试动画的完整技术栈。1. 什么是过渡Transition一句话定义来自 docs/d3-transition.md过渡是一个 selection 风格的接口用于对 DOM 变更进行动画。它不会瞬时应用变更而是在给定持续时间内把 DOM 从当前状态平滑插值interpolate到目标状态。最简用法三步走选择元素 → 调用*selection*.transition→ 声明目标状态d3.select(body) .transition() .style(background-color, red);过渡支持大多数selection 方法如*transition*.attr、*transition*.style分别替代*selection*.attr、*selection*.style但并非全部方法都支持。有一条关键顺序约束必须在过渡开始之前完成元素 append 与 数据绑定过渡启动后再创建新元素就无法纳入本次插值。与之配套文档提供了*transition*.remove操作符用于在过渡结束时便捷地移除元素见 docs/d3-transition/modifying.md 中*transition*.remove一节。2. 计算中间状态内建插值器的自动选择过渡如何算出每一帧的中间状态答案是 d3-interpolate 提供的内建插值器。根据 docs/d3-transition.md 的说明以下类型会被自动检测颜色走 interpolateRgb数字走 interpolateNumber几何变换translate / rotate / scale 等 transform由 transform 插值器 处理内嵌数字的字符串走 interpolateString。这一条对实际开发非常关键——许多 CSS 样式如padding、字体大小和 SVG 路径d属性都是“文本里夹数字”的形态d3-transition 能自动对其中的数值做插值。当自动检测不够用例如需要彩虹色渐变、路径形变、数据驱动插值时使用三类“自定义插值器入口”*transition*.attrTween自定义属性插值*transition*.styleTween自定义样式插值*transition*.tween任意命名 tween可执行副作用。三者的详细签名与示例见下文第 5 节。3. 创建与选择过渡元素Selecting3.1*selection*.transition(*name*)过渡从 selection 派生入口是*selection*.transition(*name*)docs/d3-transition/selecting.md。name可选缺省为null新过渡只与同名的其他过渡互斥——这是 d3-transition 并发模型的基础同名过渡在同一元素上会相互打断/排队不同名过渡可以并行。name还可以直接传入一个过渡实例此时返回的过渡会沿用该实例的 id 与 name。若选中元素上已存在同 id 过渡则对该元素直接返回既有过渡否则返回过渡的时序配置会从每个选中元素的最近祖先上同 id 的既有过渡继承。这个机制用于两类场景跨多个 selection 同步同一个过渡针对特定元素重新选中既有过渡并修改其配置。文档给出的同步示例两个 selection 共用同一节奏const t d3.transition() .duration(750) .ease(d3.easeLinear); d3.selectAll(.apple).transition(t) .style(fill, red); d3.selectAll(.orange).transition(t) .style(fill, orange);注意若指定过渡在节点及其祖先上都找不到例如该过渡已经结束当前版本使用默认时序参数文档明确提示未来版本可能会改为抛出错误。3.2d3.transition(*name*)在文档根元素document.documentElement上创建新过渡等价于d3.selection() .transition(name)同时d3.transition这个构造函数可用于类型判断instanceof d3.transition与扩展 transition 原型。3.3 子选择select / selectAll / selectChild / selectChildren / filter / merge*transition*.select、*transition*.selectAll、*transition*.selectChild、*transition*.selectChildren、*transition*.filter的行为与对应 selection 方法一一对应selector 均支持字符串或函数两种形式函数按顺序对每个选中元素求值收到当前数据*d*、索引*i*、当前组*nodes*this为当前 DOM 元素。新过渡继承本过渡的 id、name 与时序。它们各自等价于“先取*transition*.selection()做子选择再重新建过渡”三步以*transition*.select为例transition .selection() .select(selector) .transition(transition)*transition*.merge(*other*)docs/d3-transition/selecting.md 中*transition*.merge一节返回两个过渡必须同 id合并后的新过渡组数、父节点、name、id 与 this 相同this 中缺失null的元素用other中对应非 null元素补位等价于transition .selection() .merge(other.selection()) .transition(transition)*transition*.selection()则直接返回与该过渡对应的 selection。3.4*transition*.transition()链式排队的核心这是串行动画的关键方法返回一个作用在相同选中元素上的新过渡其启动时间排定为本过渡结束时新过渡的参考时间 本过渡时间 delay duration并继承本过渡的 name、duration 与 easing。文档的“苹果三段变色”示例完整展示了链式排队注意每一段的 delay 相对于其前一段d3.selectAll(.apple) .transition() // First fade to green. .style(fill, green) .transition() // Then red. .style(fill, red) .transition() // Wait one second. Then brown, and remove. .delay(1000) .style(fill, brown) .remove();效果苹果先渐变为绿再变红保持红色一秒后进入最后一段变棕并移除。3.5active(node, name)获取元素上的活跃过渡active(node, name)返回指定节点上指定名称的活跃过渡若无 name 则按 null 处理没有则返回 null。它是构建循环/自重复动画的工具——文档给出的 “disco mode” 完整示例d3.selectAll(circle).transition() .delay((d, i) i * 50) .on(start, function repeat() { d3.active(this) .style(fill, red) .transition() .style(fill, green) .transition() .style(fill, blue) .transition() .on(start, repeat); });思路start事件回调里通过d3.active(this)拿到当前正在运行的过渡在其上继续链式.transition()最后一段的start回调再次注册自身形成无限循环同时用delay((d, i) i * 50)让各圆错峰启动。4. 时序配置Timing过渡的 easing、delay、duration 三者全部可配置。官方文档特别指出利用逐元素 delay错峰stagger元素的重排可显著改善人眼对动画的感知质量。4.1*transition*.delay(*value*)单位为毫秒支持常量或函数两种形式函数立即对每个选中元素求值参数为*d*、*i*、*nodes*this为当前 DOM 元素。未指定时默认为 0。不传参调用返回第一个非 null元素当前的 delay 值仅在过渡恰好含一个元素时才有实用意义transition.delay(250); transition.delay() // 250最典型的错峰写法是把 delay 设为索引的倍数transition.delay((d, i) i * 10);也可以把 delay 计算为数据的函数或在计算索引 delay 前先对 selection 排序selection.sort。4.2*transition*.duration(*value*)单位毫秒同样支持常量或函数。未指定时默认 250ms。不传参调用返回第一个非 null元素当前的 durationtransition.duration(750); transition.duration() // 7504.3*transition*.ease(*value*)指定 easing 函数。value必须是函数动画每一帧都会调用它传入归一化时间*t*范围 [0, 1]返回缓动后的时间*tʹ*通常也在 [0, 1]。良好的缓动函数应满足 t0 时返回 0、t1 时返回 1。未指定时默认为 easeCubic。不传参调用返回第一个非 null元素当前的缓动函数transition.ease(d3.easeCubic); transition.ease() // d3.easeCubic4.4*transition*.easeVarying(*factory*)与ease的区别在于它接受一个工厂函数对每个选中节点求值参数同样是*d*、*i*、*nodes*this为当前 DOM 元素要求其返回一个缓动函数——即不同元素可以使用不同的缓动曲线transition.easeVarying((d) d3.easePolyIn.exponent(d.exponent));5. 修改元素Modifying创建过渡之后见第 3 节使用过渡的转换方法影响文档内容。5.1*transition*.attr(*name*, *value*)为指定属性分配一个属性 tweentween 的起始值是过渡开始时刻该属性的值这保证了从任意当前状态出发的插值。目标value支持常量或函数函数参数与前述一致。目标值为null时属性在过渡开始时被移除否则按以下三步算法自动选择插值器docs/d3-transition/modifying.md 原文算法若value是数字使用 interpolateNumber若value是颜色或可强制转换为颜色的字符串使用 interpolateRgb否则使用 interpolateString。需要其他插值器时用*transition*.attrTween。5.2*transition*.attrTween(*name*, *factory*)factory是“插值器工厂”返回一个 interpolator。过渡开始时factory按序对每个选中元素求值参数*d*、*i*、*nodes*this为当前 DOM 元素返回的插值器在过渡的每一帧被调用传入缓动后的时间*t*通常 [0, 1]其返回值被设为属性值——插值器必须返回字符串。传null移除已赋值的属性 tween不传参返回当前的插值器工厂不存在则为 undefined。文档给出的三个层次递进的示例// 固定从 red 插值到 blue transition.attrTween(fill, () d3.interpolateRgb(red, blue)); // 从当前 fill 插值到 blue等价于 *transition*.attr 的默认行为 transition.attrTween(fill, function() { return d3.interpolateRgb(this.getAttribute(fill), blue); }); // 自定义“彩虹”插值器把归一化时间映射到色相环 transition.attrTween(fill, () (t) hsl(${t * 360},100%,50%));该方法的另一个高价值用途是数据插值data interpolation用 interpolateObject 对两个数据对象插值再把中间数据代入 shape 等生成器计算新的属性值——这是实现平滑形变morphing的常用模式。关于属性移除的时机约定过渡开始时移除用*transition*.attr传 null结束时移除则用*transition*.ondocs/d3-transition/control-flow.md 中*transition*.on监听end事件。5.3*transition*.style与*transition*.styleTween*transition*.style(*name*, *value*, *priority*)与attr的规则几乎一致两点差异tween 的起始值优先取该样式的内联值无内联值时取计算值computed value且可指定 CSS*priority*。目标值为null时在过渡开始移除该样式插值器选择算法同样是三步数字 → interpolateNumber颜色/颜色字符串 → interpolateRgb其余 → interpolateString换用其他插值器用*styleTween*。*transition*.styleTween(*name*, *factory*, *priority*)的工厂契约与attrTween相同返回值以指定priority写入样式值。示例与 attrTween 对称transition.styleTween(fill, () d3.interpolateRgb(red, blue)); transition.styleTween(fill, function() { return d3.interpolateRgb(this.style.fill, blue); }); transition.styleTween(fill, () (t) hsl(${t * 360},100%,50%));5.4*transition*.text与*transition*.textTween*transition*.text(*value*)在过渡开始时把文本内容整体设为目标值支持常量或函数null清空内容。文本默认不做逐帧插值因为逐帧改文本通常并不理想若确实需要“数字滚动”效果用*transition*.textTween或者追加一个替换元素做透明度交叉淡入淡出cross-fade。*transition*.textTween(*factory*)工厂返回的插值器每帧被调用并传入缓动时间*t*返回值用于设置文本必须返回字符串。传null移除既有 text tween不传参返回当前工厂。文档示例整数从 0 滚到 100注意interpolateRound保证每帧都是整数transition.textTween(() d3.interpolateRound(0, 100));5.5*transition*.remove()为每个选中元素登记过渡结束时移除该元素——但前提是此刻该元素没有其他活跃或待启动的过渡若存在其他过渡则不做任何事。这条保护规则避免了“元素正被另一个过渡使用就被删掉”的竞态。5.6 transition.tween(name,value)最底层的通用 tween 注册value必须是“返回函数的函数”。过渡开始时对每个选中元素求值参数*d*、*i*、*nodes*this为当前 DOM 元素得到的内层函数在每一帧被调用并传入缓动时间*t*。传null移除同名 tween。相比attrTween/styleTweentween可以直接写任意副作用不限于设置某个属性/样式。文档的示例——用 tween 复现attr把 fill 插值到 blue 的效果transition.tween(attr.fill, function() { const i d3.interpolateRgb(this.getAttribute(fill), blue); return function(t) { this.setAttribute(fill, i(t)); }; });6. 控制流与过渡的生命周期Control flowdocs/d3-transition/control-flow.md 是理解 d3-transition 并发与错误信息的权威章节。6.1 过渡的一生The life of a transition文档按时间线精确描述了五个阶段逐段理解有助于定位“为什么我改不动过渡”这类问题创建后配置期通过*selection*.transition或*transition*.transition创建过渡后可立即用delay、duration、attr、style等方法配置。注意两类方法的处理时点差异指定目标值的方法如*transition*.attr是同步求值的而需要起始值参与插值的方法如*attrTween*、*styleTween*必须延迟到过渡开始时求值。调度scheduled创建后的不久当前帧末或下一帧期间过渡被调度。从此delay 与start事件监听器不可再修改尝试修改会抛出错误信息 “too late: already scheduled”若过渡已结束则为 “transition not found”。开始start过渡启动时它打断同一元素上同名的活跃过渡如有并向监听器派发interrupt事件。文档特别强调两点打断发生在start 而非创建时——即使是零 delay 过渡也不会立刻打断活跃过渡旧过渡还能拿到最后一帧新过渡同时取消cancel同元素上同名且更早创建的待启动过渡。随后派发start事件。start 是最后一次可修改过渡的时刻运行中的过渡其时序、tween、监听器都不可再改尝试修改抛出 “too late: already running”已结束时为 “transition not found”。过渡在 start 之后立即初始化其 tweens。逐帧运行running在本帧所有开始中的过渡都启动完之后各过渡首次调用自己的 tweens——这种“批量初始化”避免了 DOM 读取与写入交错是性能上的关键设计。此后每个活跃帧过渡以缓动后的*t*0 到 1调用 tweens同一帧内按tween 注册顺序调用。结束end结束时过渡最后再调用一次tweens传入的是未缓动的*t* 1保证精确落在目标值上随后派发end事件。这是最后一次可以检查该过渡的时刻end 之后过渡从元素上删除、配置被销毁interrupt 或 cancel 同样会销毁配置。此时再尝试检查会抛出 “transition not found”。6.2*selection*.interrupt(*name*)与interrupt(*node*, *name*)两者分别作用于 selection 与单个节点打断指定名称的活跃过渡并取消同名待启动过渡未指定 name 时按 null 处理。一个容易踩坑的点文档原文强调打断某个元素上的过渡不会影响其子元素上的过渡。例如 d3-axis 的轴过渡实际由轴 G 元素下多个相互独立但同步的子过渡组成刻度线、刻度标签、domain 路径等。要打断整条轴的过渡必须打断它的后代selection.selectAll(*).interrupt();*是通用选择器选中所有后代元素若还想同时打断 G 元素本身selection.interrupt().selectAll(*).interrupt();6.3*transition*.end()可等待的动画*transition*.end()返回一个promise当所有选中元素都完成过渡时 resolve若任何元素的过渡被取消或打断promise 则 reject。这使得“动画跑完再执行下一步”可以用await表达而不再只能依赖事件回调。6.4*transition*.on(*typenames*, *listener*)过渡事件为每个选中元素添加/移除监听器事件typenames只能是四种字符串之一start— 过渡开始时end— 过渡结束时interrupt— 过渡被打断时cancel— 过渡被取消时。再次对照过渡的一生理解触发时机。务必注意这些是过渡事件不是*selection*.on/*selection*.dispatch实现的原生 DOM 事件。类型名后可选地跟一个英文句点加名称如start.foo、start.bar用于同一事件类型注册多个独立回调多个 typenames 用空格分隔如interrupt end、start.foo start.bar。事件派发时监听器以*d*、*i*、*nodes*为参数、this为当前 DOM 元素被调用监听器总是看到元素的最新数据但索引是选择selection的属性在监听器分配时固定——要更新索引需重新分配监听器。监听器的替换/移除规则同一typename上已有监听器时先移除旧监听器再添加新的传null作为listener移除指定 typename 的监听器传null且 typename 为.foo移除该名称下的所有监听器typename 为.则移除所有无名称监听器。不指定listener时返回第一个非 null选中元素上该 typename 的当前监听器多个 typename 时返回第一个匹配的。6.5 实用工具方法each / call / empty / nodes / node / size以下方法与 selection 的同名方法行为等价作用在过渡上*transition*.each(*function*)对每个选中元素调用函数参数*d*、*i*、*nodes*this为当前 DOM 元素可用于同时访问父子数据的上下文逻辑*transition*.call(*function*, ...*arguments*)把过渡与可选参数传给函数调用一次并返回该过渡以支持链式调用。文档示例展示了“可复用函数 call”的组合模式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);*transition*.empty()是否不含非 null元素*transition*.nodes()返回所有非 null元素数组*transition*.node()返回第一个非 null元素空过渡返回 null*transition*.size()元素总数。7. 本仓库中的工程事实与验证路径d3 主仓库umbrella package并不包含 d3-transition 的实现源码而是将其作为依赖聚合。以下事实可直接在本仓库中查证版本与依赖package.json 声明version: 7.9.0即 d3 v7.9.0依赖项包含d3-transition: ^3.0.1另有d3-selection: ^3.0.0、d3-ease: ^3.0.1、d3-interpolate: ^3.0.1等过渡的配套模块运行时要求node 12。统一导出src/index.js 第 29 行export * from d3-transition;表明 d3-transition 的全部 APId3.transition、active、interrupt等被整体重导出到 d3 主命名空间import * as d3 from d3后即可直接使用本文全部接口。导出完整性测试test/d3-test.js 遍历 package.json 的每个 dependency 并断言其导出项除version外都出现在 d3 命名空间中从工程上保证d3.transition、d3.active等符号不会丢失。文档链接完整性测试test/docs-test.js 会递归扫描docs/下所有 Markdown校验每个内部链接的目标文件与锚点{#anchor}真实存在。这意味着本文引用的 docs/d3-transition/ 目录下的四个子文档及其锚点如selection_transition、transition_delay、the-life-of-a-transition都是仓库中经过测试保证有效的一手 API 参考。适用前提本文所述 API 与默认值delay 0ms、duration 250ms、ease 为 easeCubic 等以本仓库 v7.9.0 文档为准d3-transition 子包独立演进^3.0.1若单独安装该子包请以对应版本的 API 为准。8. 快速参考方法-用途-文档位置方法作用参考文档*selection*.transition(*name*)从 selection 派生过渡name 控制互斥域docs/d3-transition/selecting.mdd3.transition(*name*)在文档根元素上建过渡 / 类型判断docs/d3-transition/selecting.md*transition*.select/selectAll/selectChild/selectChildren/filter子选择继承 id、name、时序docs/d3-transition/selecting.md*transition*.merge(*other*)合并同 id 的两个过渡docs/d3-transition/selecting.md*transition*.transition()排定链式后续过渡docs/d3-transition/selecting.mdactive(*node*, *name*)取节点上活跃过渡构建循环动画docs/d3-transition/selecting.md*transition*.delay(*value*)延迟ms默认 0docs/d3-transition/timing.md*transition*.duration(*value*)持续时长ms默认 250docs/d3-transition/timing.md*transition*.ease(*value*)缓动函数默认 easeCubicdocs/d3-transition/timing.md*transition*.easeVarying(*factory*)逐元素自定义缓动docs/d3-transition/timing.md*transition*.attr/style目标值声明自动选插值器docs/d3-transition/modifying.md*transition*.attrTween/styleTween自定义属性/样式插值器工厂docs/d3-transition/modifying.md*transition*.text/textTween文本整体替换 / 逐帧插值docs/d3-transition/modifying.md*transition*.remove()过渡结束时移除元素docs/d3-transition/modifying.md*transition*.tween(*name*, *value*)通用命名 tweendocs/d3-transition/modifying.md*selection*.interrupt(*name*)/interrupt(*node*, *name*)打断活跃过渡、取消待启动过渡docs/d3-transition/control-flow.md*transition*.end()返回 Promise可 awaitdocs/d3-transition/control-flow.md*transition*.on(*typenames*, *listener*)start / end / interrupt / cancel 事件docs/d3-transition/control-flow.mdeach/call/empty/nodes/node/size与 selection 等价的工具方法docs/d3-transition/control-flow.md掌握以上内容后你可以独立回答 d3 动画开发中最常见的问题动画从哪里出发起始值在过渡开始时捕获、如何让多个元素同步或错峰transition 实例 delay/easeVarying、如何让动画串行或循环*transition*.transitionactive、如何自定义中间状态三类 Tween 自动插值器算法以及如何安全地打断、等待与监听interrupt / end / on 与生命周期五阶段。【免费下载链接】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),仅供参考