X6 图编辑器动画使用完全指南:从声明式配置到 Web Animation API 深度实践
【免费下载链接】X6🚀 JavaScript diagramming library that uses SVG and HTML for rendering.项目地址: https://gitcode.com/GitHub_Trending/x6/X6
导读
本指南以 X6(基于 SVG 与 HTML 渲染的 JavaScript 图编辑库)的动画使用示例为核心,系统讲解节点/边动画的声明式配置语法、组合动画与关键帧、播放控制 API、自定义属性动画以及 CSS 动画方案。示例全部来源于仓库 site/examples/animation/usage/demo 目录,底层原理可对照 src/model/animation 源码进行印证。读完本文,你将能够独立完成从"让节点动起来"到"精细化控制动画播放"的完整实战。
一、动画体系总览:两种使用方式
X6 的动画能力围绕Web Animation API的Animation与KeyframeEffect模型实现(见 src/model/animation/animation.ts 的类注释),使用方式分为两类:
- 声明式配置:在
graph.addNode/graph.addEdge时通过animation字段声明动画,节点/边创建后自动播放,适合"图元自带动态效果"的场景(如呼吸、涟漪、流动线)。 - 命令式控制:通过
cell.getAnimations()获取动画实例,再调用play()/pause()/cancel()/updatePlaybackRate()/reverse()/finish()等方法进行播放控制,适合交互场景(如点击按钮暂停、加速)。
从源码结构看,每个 Cell(节点/边)内部持有一个AnimationManager(src/model/animation/animationManager.ts),cell.getAnimations()即委托它返回当前动画列表,入口定义在 src/model/cell.ts。
二、基本使用:四种最常用的动画目标
示例 basic.ts 通过 6 个节点演示了最基本的动画写法,其核心结构是:
animation: [ [ { 'position/x': 50 }, // 关键帧:目标值 { duration: 1000, direction: 'alternate', iterations: Infinity }, // 播放选项 ], ]外层数组的每一项是一组独立动画;每组内部第一个对象描述动画目标(目标路径与目标值),第二个对象描述播放选项。示例覆盖四类常用目标:
| 动画目标路径 | 效果 | 示例值 |
|---|---|---|
position/x | 水平位移 | { 'position/x': 50 } |
attrs/body/fill | 填充颜色渐变 | { 'attrs/body/fill': ['#9254de', '#06b6d4'] } |
size/width | 宽度伸缩 | { 'size/width': 120 } |
angle | 旋转 | { angle: 360 } |
attrs/body/opacity | 透明度变化 | { 'attrs/body/opacity': [0.5, 1] } |
attrs/text/fontSize | 文字字号变化 | { 'attrs/text/fontSize': [10, 16] } |
关键点说明:
- 目标值是单值(如
50)时,表示从当前值动画到该值;目标值是数组(如['#9254de', '#06b6d4'])时,表示在多个关键帧之间插值。 direction: 'alternate'让动画正向→反向往复;iterations: Infinity表示无限循环,配合alternate即形成经典的"往复循环"效果。
三、组合动画:多属性并行、多组独立、多关键帧
示例 composition.ts 展示了三种进阶组合方式:
3.1 单组内并行多个属性
在一个关键帧对象中同时写入多个路径,它们将并行插值:
animation: [ [ { 'size/width': 85, 'size/height': 85, 'attrs/body/opacity': [1, 0.9], }, { duration: 900, iterations: Infinity }, ], ]3.2 多组动画分别配置
外层数组可包含多组动画,每组独立设置时长、方向等选项:
animation: [ [{ angle: [0, 360] }, { duration: 3000, iterations: Infinity }], [{ 'attrs/body/fill': '#D94F00' }, { duration: 2000, iterations: Infinity, direction: 'alternate' }], ]例如示例中星星图形一边 3 秒旋转一周、一边 2 秒往复变色。
3.3 多关键帧(数组内多个值)
目标值数组超过两个元素时即构成多个关键帧,可构造复杂轨迹:
animation: [ [ { 'position/x': [370, 390, 350], 'position/y': [40, 50, 60], angle: [0, 40, 0, 30], }, { duration: 2000, direction: 'alternate', iterations: Infinity }, ], ]'position/x'在 370→390→350 三点间移动,angle在 0→40→0→30 四帧间旋转,各属性关键帧长度可以不同,未定义 offset 时按均分处理。
四、控制动画播放:取消、倍速、暂停与继续
示例 control.ts 演示了通过node:click事件驱动动画控制的完整流程:
graph.on('node:click', ({ cell }) => { const [ani] = cell.getAnimations() if (cell === cancelNode) { ani.cancel() // 取消动画 } if (cell === rateNode) { ani.updatePlaybackRate(2) // 2 倍速播放 } if (cell === pauseNode) { if (label === '点击暂停动画') { ani.pause() // 暂停 } else { ani.play() // 继续播放 } } })对照 src/model/animation/animation.ts 源码,这些方法的行为如下:
cancel()(animation.ts):停止动画、状态回到idle,并通过KeyframeEffect.apply(null)把属性恢复到动画前原始值,随后触发animation:cancel事件(oncancel回调)。updatePlaybackRate(2)(animation.ts):设置倍速;源码中当动画正在运行时,会按新旧倍速重算_startTime,保证切换倍速的瞬间画面不跳变(见playbackRate的 setter,animation.ts)。pause()(animation.ts):记录当前时间_pausedTime,停止requestAnimationFrame循环,状态置为paused。play()(animation.ts):若处于暂停态,则从_pausedTime处继续;若处于初始态则从 0 开始,并启动内部_tick()的 rAF 循环。
Animation类还提供reverse()(反转播放方向)、finish()(跳到结束并依据fill模式落定最终状态)等方法,可组合实现更丰富的交互控制。此外动画实例还暴露onfinish、oncancel回调以及currentTime、playbackRate、playState等属性(animation.ts)。
五、边动画:流动虚线、沿线移动标记与渐变流光
示例 edge.ts 展示了四种边动画实战:
5.1 流动虚线
通过让strokeDashoffset循环递减,虚线产生向前"流动"效果:
graph.addEdge({ source: { x: 60, y: 60 }, target: { x: 240, y: 60 }, attrs: { line: { strokeDasharray: 5, strokeDashoffset: 0 } }, animation: [[{ 'attrs/line/strokeDashoffset': -20 }, { duration: 1000, iterations: Infinity }]], })5.2 标记沿边移动
自定义marker元素并动画其atConnectionRatio(连接线上位置比例 0→1),实现圆点沿平滑曲线(connector: { name: 'smooth' })滑行:
markup: [ { tagName: 'circle', selector: 'marker', attrs: { stroke: 'none', r: 5 } }, ...Shape.Edge.getMarkup(), ], attrs: { line: { strokeDasharray: 5, strokeDashoffset: 0, stroke: '#9DADCE' }, marker: { fill: '#C7D5F6', atConnectionRatio: 0 } }, animation: [[{ 'attrs/marker/atConnectionRatio': 1 }, { duration: 2000, iterations: Infinity }]],5.3 不同 easing 的多组动画
同一组动画可以分别指定不同的缓动函数,例如描边粗细用ease-in-out-back、透明度用ease-in-out-quad;还支持"路径 + 单值"的简写形式(路径、目标值、选项三段式):
animation: [ [{ 'attrs/line/strokeWidth': 4 }, { duration: 2500, iterations: Infinity, direction: 'alternate', easing: 'ease-in-out-back' }], ['attrs/line/opacity', 1, { duration: 2500, iterations: Infinity, direction: 'alternate', easing: 'ease-in-out-quad' }], ]5.4 渐变流光
结合线性渐变stroke(type: 'linearGradient'+ 三段stops)与strokeDashoffset循环,并叠加透明度呼吸,形成彩色流光:
animation: [ [{ 'attrs/line/opacity': [0.7, 1] }, { duration: 1000, fill: 'forwards', direction: 'alternate', iterations: Infinity }], [{ 'attrs/line/strokeDashoffset': [30, 0] }, { duration: 500, iterations: Infinity }], ],这里显式使用了fill: 'forwards',表示动画结束后保留最后一帧状态(源码 keyframeEffect.ts 的EffectTiming定义支持none/forwards/backwards/both四种模式)。
六、自定义属性动画:让 HTML 节点跟随数据变化
示例 custom-attr.ts 演示了如何对自定义节点的自定义数据属性做动画:通过Shape.HTML.register注册custom-html节点,其html(cell)回调根据cell.getData().ratio动态计算 div 尺寸,然后对data/ratio声明动画:
Shape.HTML.register({ shape: 'custom-html', width: 160, height: 80, effect: ['data'], html(cell) { const { ratio } = cell.getData() ?? {} // ...根据 ratio 计算并返回 div }, }) graph.addNode({ shape: 'custom-html', x: 80, y: 80, data: { ratio: 1 }, animation: [[{ 'data/ratio': 3 / 5 }, { duration: 1000, iterations: Infinity }]], })其底层原理是:KeyframeEffect在初始化时用target.getPropByPath(prop)收集动画属性的原始值,在每一帧插值后用target.setPropByPath(prop, value)写回(keyframeEffect.ts 与 keyframeEffect.ts)。因此只要属性路径可读可写(包括data/*这类自定义路径),就能被动画驱动——这正是"自定义属性动画"能够成立的关键机制。
七、呼吸效果:多节点统一动画
示例 breathing-circle.ts 通过自定义markup为每个圆形节点叠加一层wrapper圆环,动画其半径attrs/wrapper/r从radius放大到radius + 10再往复,配合wrapper的opacity: 0.4,形成柔和的"呼吸"光晕:
markup: [ { tagName: 'circle', selector: 'wrapper' }, { tagName: 'circle', selector: 'body' }, ], attrs: { body: { fill: color, strokeWidth: 0 }, wrapper: { r: radius, cx: radius, cy: radius, fill: color, opacity: 0.4, strokeWidth: 0 }, }, animation: [[{ 'attrs/wrapper/r': radius + 10 }, { duration: 1000, direction: 'alternate', iterations: Infinity }]],八、涟漪效果:利用 delay 制造相位差
示例 ripple-circle.ts 用程序化markup生成 5 层涟漪圆环(ripple0~ripple4,共享groupSelector: 'rippleGroup'),并为每层生成一组动画,通过delay参数错开启动时间,形成依次扩散的水波:
animation: Array.from({ length }).map((_, aniIndex) => [ { [`attrs/ripple${aniIndex}/r`]: [r, r + length * 5], [`attrs/ripple${aniIndex}/opacity`]: [opacity, 0], }, { duration: 1000 * length, iterations: Infinity, delay: 1000 * aniIndex, // 每层延迟 1 秒启动 }, ]),delay是EffectTiming的标准字段(默认 0,见 keyframeEffect.ts 的默认值定义),在Animation._tick()中以currentTime - delay计算播放进度,从而支持精确的错峰编排。
九、CSS 动画:两种接入方式
示例 css.ts 说明动画也可以完全交给 CSS 处理,X6 图元本质是 DOM 元素(SVG/HTML),因此 CSS 动画天然可用:
- 内联 style 直接声明:在 attrs 的
style中写animation:
attrs: { body: { fill: '#efdbff', stroke: 'none', rx: 10, style: { animation: 'trans 1s infinite linear' }, }, },- 添加 class 引入样式:在 attrs 中指定
class,动画样式定义在外部样式表:
attrs: { body: { fill: '#85C054', stroke: 'none', rx: 10, class: 'css-animation-node' } },@keyframes trans { to { transform: translateX(50px); } } .css-animation-node { animation: trans 1s infinite linear; }(示例使用insert-css仅为演示方便,实际项目建议将样式写入样式文件。)声明式动画配置与 CSS 动画的选择依据:需要与图元数据/几何状态联动、需要在运行时精细控制播放(暂停、倍速、取消)的场景优先使用animation配置;纯视觉表现(如平移、闪烁)可直接走 CSS,成本更低。
十、底层原理:关键帧插值与播放选项
10.1 关键帧标准化与 offset 计算
KeyframeEffect支持数组关键帧与"属性索引对象"两种写法,对象写法会被拆分为多个关键帧,未显式指定offset时自动均分:首帧为 0、末帧为 1、中间帧按index / (length - 1)计算(keyframeEffect.ts)。示例中'position/x': [370, 390, 350]即被解析为 offset 0 / 0.5 / 1 的三帧。
10.2 插值策略:按值类型自动选择
apply(iterationTime)在每帧计算当前进度后,根据属性值类型自动选择插值器(keyframeEffect.ts):
- 以
#开头的字符串 → 颜色插值(Interp.color); - 以
transform结尾的属性且非数值 → 变换矩阵插值(Interp.transform); - 含单位(如
px、%,由unitReg判断)→ 单位插值(Interp.unit); - 其余 → 数值插值(
Interp.number)。
每个关键帧还可以携带独立的easing,未指定时继承前序帧或总选项中的 easing(keyframeEffect.ts),最终经Timing[easingName]查表得到缓动函数(如ease-in-out-back、ease-in-out-quad),缺省回退linear。
10.3 EffectTiming 播放选项总表
结合 keyframeEffect.ts 的接口定义与默认值,animation第二项(播放选项)可用字段如下:
| 字段 | 类型 | 默认值 | 说明 |
|---|---|---|---|
duration | number | 0 | 单次播放时长(毫秒),示例常用 500~3000 |
delay | number | 0 | 延迟启动时间(毫秒),涟漪示例用于错峰 |
iterations | number | 1 | 播放次数,Infinity表示无限循环 |
direction | 'normal' \| 'reverse' \| 'alternate' \| 'alternate-reverse' | 'normal' | 播放方向,alternate实现往复 |
easing | CamelToKebabCase<Timing.Names> | 'linear' | 缓动函数名,如ease-in-out-quad |
fill | 'none' \| 'forwards' \| 'backwards' \| 'both' | 'none' | 动画结束后的填充模式,forwards保留末帧 |
需要说明的是,源码注释标注backwards与both的"初始应用效果"尚在实现完善中(keyframeEffect.ts),生产使用建议优先forwards/none,并对动画结束后的状态做显式设计。
十一、运行与查看示例
示例代码可直接在仓库的 site 工程中运行查看:进入site目录安装依赖后启动开发服务,即可在动画示例页(Usage 分组)逐个预览基本使用、组合动画、控制播放、边动画、自定义属性动画、呼吸、涟漪与 CSS 动画共 8 个 demo;每个 demo 的源码对应 demo 目录下的basic.ts、composition.ts、control.ts、edge.ts、custom-attr.ts、breathing-circle.ts、ripple-circle.ts、css.ts,demo 标题与截图信息见 meta.json。
结语
X6 的动画能力覆盖了"声明式配置 + 命令式控制 + CSS 兜底"三档方案:animation数组声明让图元开箱即动,getAnimations()结合Animation类方法实现细粒度交互控制,而KeyframeEffect的多关键帧、自动 offset、按类型插值与EffectTiming选项则提供了接近 Web Animation API 的表达力。结合仓库 site/examples/animation/usage 的示例与 src/model/animation 的实现,开发者可以快速搭建出呼吸、涟漪、流动线、数据驱动 HTML 节点动画等常见图编辑动效。
【免费下载链接】X6🚀 JavaScript diagramming library that uses SVG and HTML for rendering.项目地址: https://gitcode.com/GitHub_Trending/x6/X6
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考