X6 图形引擎动画实战:从 animate API 到声明式动画与事件控制的完整指南

X6 图形引擎动画实战:从 animate API 到声明式动画与事件控制的完整指南 X6 图形引擎动画实战从 animate API 到声明式动画与事件控制的完整指南【免费下载链接】X6 JavaScript diagramming library that uses SVG and HTML for rendering.项目地址: https://gitcode.com/GitHub_Trending/x6/X6X6antv/x6是基于 SVG 与 HTML 渲染的 JavaScript 图形编辑引擎节点与边的动画能力是提升画布视觉表现与交互体验的关键手段。本文以 X6 官方教程中的动画章节animation.en.md为骨架结合仓库内动画模块源码src/model/animation/与测试用例animation.spec.ts系统讲解animateAPI 的用法、基于属性路径的动画机制、声明式animation配置、Shape 级动画注册、动画控制暂停/恢复/取消/反向/变速以及两类动画事件监听帮助你从 API 使用深入到引擎实现原理。添加动画animateAPI 入门在 X6 中为节点或边添加动画有多种实现途径包括animateAPI、CSS 动画和 SMIL 动画。其中animateAPI 能力最全面本文聚焦于它。其用法与 Web Animations APIWAAPI标准高度兼容实现了其大部分核心特性。一个最基本的示例让节点从原始位置沿 x 方向移动到坐标 300再往返循环import { Graph } from antv/x6 const graph new Graph({ container: document.getElementById(container), height: 300, background: { color: #F2F7FA, }, }) const node graph.addNode({ shape: rect, label: hello X6, x: 100, y: 140, width: 100, height: 50, attrs: { body: { strokeWidth: 1, rx: 6, ry: 6, }, }, }) node.animate( // 将节点位置从原始坐标动画移动到 (300, 140) { position/x: 300 }, // 单个周期 1000ms到达终点后往返反向无限循环 { duration: 1000, direction: alternate, iterations: Infinity }, )animateAPI 接收两个关键参数关键帧配置Keyframe configuration指定要动画的属性及其目标值。上例中keyposition/x要动画的属性路径value300目标值使节点从当前位置移动到 x 坐标为 300 的位置。动画配置Animation configuration控制动画行为的参数包括duration1000表示每个动画周期持续 1000msdirectionalternate表示动画在起点与终点之间往返交替iterationsInfinity表示动画无限循环。duration也可以直接以数字形式作为第二个参数传入等价于{ duration: 1000 }。从源码看Cell.animate 内部会先归一化参数NumberExt.isNumber(options)时将数字包装为{ duration: options }随后创建KeyframeEffect与Animation实例、登记到动画管理器并立即play()。动画属性为什么是position/x而不是x有开发者会疑惑为什么动画属性用position/x而非直接写x原因是 X6 的动画系统基于cell.setPropByPath()方法实现通过属性路径property path修改属性值。节点属性结构大致如下可通过cell.prop()获取{ // ... position: { x: 370, y: 180 }, size: { width: 100, height: 60 }, attrs: { // ... } }因此动画位置要通过position/x指定子属性使用/拼接。其他属性的动画规则相同例如动画化节点尺寸// 以动画方式把节点宽度设置为 200 node.animate({ size/width: 200 }, 1000)属性路径机制同样适用于attrs内的视觉属性例如attrs/body/fill填充色或attrs/label/text文本内容。从源码结构看KeyframeEffect.setKeyframes 会通过this.target.getPropByPath(prop)收集动画属性的原始值作为插值起点动画过程中再通过setPropByPath写入插值结果这正是路径机制贯穿动画全过程的实现依据。理解了这套属性机制动画对象就不限于内置属性还可以作用于自定义属性。例如动画化节点data中的自定义ratio属性驱动一个自定义 HTML 节点的宽高比例变化import { Dom, Graph, Shape } from antv/x6 Shape.HTML.register({ shape: custom-html, width: 160, height: 80, effect: [data], html(cell) { // 自定义 HTML 节点接收面积比例以控制 div 尺寸该比例可被动画驱动 const { ratio } cell.getData() ?? {} const div document.createElement(div) const area 12000 const width Math.sqrt(area / ratio) const height width * ratio Dom.css(div, { width, height, background: #fff, borderRadius: 10, border: 1px solid #000, }) return div }, }) const graph new Graph({ container: document.getElementById(container), height: 300, background: { color: #F2F7FA, }, }) graph.addNode({ shape: custom-html, x: 80, y: 80, data: { ratio: 1 }, animation: [ // 动画化自定义节点的宽高比例 [{ data/ratio: 3 / 5 }, { duration: 1000, iterations: Infinity }], ], })声明式动画直接写在节点配置里除了用 API 编程式添加动画animateAPI 还支持通过节点的animation配置项声明动画省去一次额外调用在很多场景下更实用。下面示例与最开头的 API 示例实现完全相同的动画效果import { Graph } from antv/x6 const graph new Graph({ container: document.getElementById(container), height: 300, background: { color: #F2F7FA, }, }) graph.addNode({ shape: rect, label: hello X6, x: 100, y: 140, width: 100, height: 50, attrs: { body: { strokeWidth: 1, rx: 6, ry: 6, }, }, // 动画配置直接写在节点配置中节点加入画布后自动开始播放 animation: [ [ { position/x: 300 }, { duration: 1000, direction: alternate, iterations: Infinity }, ], ], })animation配置是一个数组每个数组项都是一个动画参数数组与animate的参数一一对应即[关键帧配置, 动画配置]节点加入画布后动画会按配置自动播放。其底层实现位于 Cell 的构造函数节点监听自身的added事件当事件触发时读取store中的animation配置若非空则逐项调用cell.animate(...p)。因此无需手动触发节点被graph.addNode加入画布的那一刻动画即自动启动。注册带动画的 Shape批量复用动画节点常见需求是给多个节点添加同一套动画。无论反复调用animateAPI 还是重复书写配置每个节点都要重复操作。X6 本身支持通过注册Shape复用节点动画同样可以注册进Shape从而让一组节点一次性具备动画能力import { Graph } from antv/x6 Graph.registerNode( animated-rect, { inherit: rect, width: 150, height: 60, attrs: { body: { strokeWidth: 1, rx: 6, ry: 6, }, }, // 注册 Shape 时预配置动画 animation: [ [ { position/x: 300 }, { duration: 1000, direction: alternate, iterations: Infinity }, ], ], }, true, ) const graph new Graph({ container: document.getElementById(container), height: 300, background: { color: #F2F7FA, }, }) graph.addNode({ shape: animated-rect, x: 100, y: 50, label: hello X6, }) graph.addNode({ shape: animated-rect, x: 100, y: 150, label: hello Animation, })注册的animation会作为 Shape 默认配置继承给每个实例addNode时每个节点都会自动拥有该动画。这一模式特别适合仪表盘、可视化大屏等需要成批带动画节点的场景。动画控制暂停、播放、取消、反向与变速由于animateAPI 基于 Web Animations API 设计它同样支持灵活的控制能力如暂停、恢复、取消等。下面示例演示点击节点时暂停其动画import { Graph } from antv/x6 const graph new Graph({ container: document.getElementById(container), height: 300, background: { color: #F2F7FA, }, }) const node graph.addNode({ shape: rect, label: Click me to pause, x: 100, y: 140, width: 150, height: 60, attrs: { body: { strokeWidth: 1, rx: 6, ry: 6, }, }, animation: [ [ { position/x: 300 }, { duration: 1000, direction: alternate, iterations: Infinity }, ], ], }) graph.on(node:click, ({ cell }) { if (cell node) { const [animation] cell.getAnimations() animation.pause() } })cell.getAnimations()返回该节点上所有动画实例的数组底层由 AnimationManager.getAnimations 维护取到后即可调用控制方法。除pause()外animateAPI 支持以下控制能力方法作用源码位置pause()暂停动画animation.ts#L96-L106play()播放 / 从暂停处恢复animation.ts#L75-L94cancel()取消动画属性复位animation.ts#L145-L165finish()直接结束动画并按fill模式落定最终状态animation.ts#L108-L143reverse()反向播放将playbackRate取反animation.ts#L171-L173updatePlaybackRate(rate)更新播放速率如 2 倍速animation.ts#L167-L169源码细节值得留意pause()会先cancelAnimationFrame取消渲染帧并记录_pausedTimeplay()恢复时会用_startTime now - _pausedTime / _playbackRate保持视觉连续性运行中调用updatePlaybackRate()也会同步重算_startTime保证变速瞬间不跳帧animation.ts#L51-L65。这些行为均有对应测试用例验证例如 animation.spec.ts 验证暂停/恢复后currentTime精确延续第 188-209 行验证 2 倍速及运行中变速的时间累计正确性。动画事件WAAPI 风格与 X6 原生事件X6 支持两种风格的动画事件。第一种基于 Web Animations API 设计通过onfinish等属性监听import { Graph } from antv/x6 const graph new Graph({ container: document.getElementById(container), height: 300, background: { color: #F2F7FA, }, }) const node graph.addNode({ shape: rect, label: Click me to start, x: 100, y: 140, width: 150, height: 60, attrs: { body: { strokeWidth: 1, rx: 6, ry: 6, }, }, }) graph.on(node:click, ({ cell }) { if (cell ! node) return const animation node.animate( { position/x: [100, 300] }, { duration: 1000, direction: alternate, iterations: 1 }, ) // 通过 onfinish 监听动画事件 animation.onfinish () { console.log(Animation finished) } })注意此处关键帧写法{ position/x: [100, 300] }key 的值为数组时表示从 100 到 300的关键帧区间这与对象形式关键帧的展开逻辑一一对应——KeyframeEffect.getKeyframes 会取所有属性值数组的最大长度逐下标生成多个关键帧。Web Animations API 风格支持的事件onfinish动画完成时触发oncancel动画被取消时触发。第二种基于 X6 自身设计用onAPI 监听动画事件import { Graph } from antv/x6 const graph new Graph({ container: document.getElementById(container), height: 300, background: { color: #F2F7FA, }, }) const node graph.addNode({ shape: rect, label: Click me to start, x: 100, y: 140, width: 150, height: 60, attrs: { body: { strokeWidth: 1, rx: 6, ry: 6, }, }, }) graph.on(node:click, ({ cell }) { if (cell ! node) return node.animate( { position/x: [100, 300] }, { duration: 1000, direction: alternate, iterations: 1 }, ) }) // 通过 on 监听动画事件 graph.on(node:animation:finish, () { console.log(Animation finished) })X6 原生事件系统支持的事件cell:animation:finish动画完成cell:animation:cancel动画被取消。事件在源码中有明确落点Animation.finish()在触发onfinish回调的同时调用this._effect.target.notify(animation:finish, event)animation.ts#L141-L142cancel()同理触发animation:cancelanimation.ts#L163-L164随后 Cell.notify 会把animation:finish前缀补全为cell:animation:finish、node:animation:finish、edge:animation:finish逐级派发到图实例。事件对象由AnimationPlaybackEvent构造animationEvent.ts携带currentTime、timelineTime、target、cell等字段便于回调中获取上下文。关于 X6 事件系统的整体设计可继续阅读 events.en.md。原理深入关键帧、插值与缓动透过 API 看实现X6 动画模块由三个核心类协作完成src/model/animation/index.tsAnimation控制播放状态机idle/running/paused/finished内部通过requestAnimationFrame驱动逐帧渲染animation.ts#L175-L213KeyframeEffect解析关键帧、计算插值并写回节点属性AnimationManager管理节点上的动画实例集合animationManager.ts。KeyframeEffect的默认时间参数keyframeEffect.ts#L251-L258为参数默认值说明delay0延迟启动毫秒数directionnormal播放方向normal/reverse/alternate/alternate-reverseduration0单周期时长ms为 0 时动画立即结束easinglinear缓动函数名kebab-case 写法fillnone结束后状态none/forwards/backwards/bothiterations1迭代次数可设为Infinity方向判断由isReverseDirection实现utils.ts#L10-L18alternate在奇数迭代反向、alternate-reverse在偶数迭代反向。测试用例 animation.spec.ts#L283-L301 及第 325-359 行的direction × fill × iterations矩阵化断言覆盖了所有方向与填充模式的最终值正确性。插值阶段keyframeEffect.ts#L143-L226会根据属性值形态自动选择插值器src/common/animation/interp.ts以#开头的颜色字符串 →Interp.color按 RGB 通道线性插值以transform结尾的非数字值 →Interp.transform解析translate(...)等函数参数逐项插值带单位的值如10px、50%→Interp.unit数值插值并保留单位与精度其余数值 →Interp.number。每个关键帧之间的进度再经由缓动函数映射Timing[easingName]缺省Timing.linear。缓动函数库定义在 src/common/animation/timing.ts内置linear、quad、cubic、inout、exponential、bounce以及easeIn/Out/InOut系列的 Sine、Quad、Cubic、Quart、Quint、Expo、Circ、Back、Elastic、Bounce 等 30 余种在easing中使用 kebab-case 名称如ease-out-back即可引用。动画循环的核心是Animation._tickanimation.ts#L175-L213每帧用(now - startTime) * playbackRate计算当前时间减去delay后得到迭代进度与迭代序号结合方向反转后调用effect.apply(iterationTime)将插值结果写回属性当currentTime endTime或currentTime 0时自动finish()。fill: forwards | both时finish()会把效果停留在最后一个迭代的边界值fill: none | backwards则清除效果animation.ts#L108-L143。小结X6 的动画体系以 Web Animations API 为蓝本通过animateAPI 提供了从属性路径动画、自定义属性动画、声明式配置到 Shape 注册复用的一整套能力并配套pause/play/cancel/finish/reverse/updatePlaybackRate六种控制方法与两类事件监听机制。无论是给单个节点做位移动效、让自定义 HTML 节点的比例随数据变化还是让整组节点循环往复都可以在几行代码内完成。若需进一步了解事件系统全貌或与其他基础能力组合使用可继续阅读 events.en.md 及 node.en.md 等教程。【免费下载链接】X6 JavaScript diagramming library that uses SVG and HTML for rendering.项目地址: https://gitcode.com/GitHub_Trending/x6/X6创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考