基础动画

G6 中的动画分为两个层次:

  • 全局动画:全局性的动画,图整体变化时的动画过渡;
  • 元素(边和节点)动画:节点或边上的独立动画。

    全局动画G6 的全局动画指通过图实例进行某些全局操作时,产生的动画效果。例如:

  • graph.updateLayout(cfg) 布局的变化

  • graph.changeData() 数据的变化

通过实例化图时配置 animate: true,可以达到每次进行上述操作时,动画效果变化的目的。配合 animateCfg 配置动画参数:

  1. const graph = new G6.Graph({
  2. // ... // 图的其他配置项
  3. animate: true, // Boolean,切换布局时是否使用动画过度,默认为 false
  4. animateCfg: {
  5. duration: 500, // Number,一次动画的时长
  6. easing: 'linearEasing', // String,动画函数,可选项:''
  7. },
  8. });

easing 函数

easing 函数是指动画的函数。例如线性插值、先快后慢等。G6 支持所有 d3 中的动画函数。因此,上面代码中 animateCfg 配置中的 String 类型的 easing 可以取值有:'easeLinear''easePolyIn''easePolyOut''easePolyInOut''easeQuad''easeQuadIn''easeQuadOut''easeQuadInOut'

更多取值及所有取值含义参见:d3 Easings

元素动画

由于 G6 的内置节点和边是没有动画的,需要实现节点和边上的动画需要通过自定义节点自定义边时复写 afterDraw 实现。

节点动画

关于节点动画,以下面三个动画示例进行讲解:

  • 节点上图形的动画(如下图左);
  • 增加带有动画的背景图形(如下图中);
  • 节点上部分图形的旋转动画(如下图右)。

downloaddownloaddownload

以上三个动画节点的 demo 代码见:节点动画

节点上图形的动画

节点上的动画,即每一帧发生变化的是节点上的某一个图形。 download 本例实现节点放大缩小,通过 group.get('children')[0] 找到需要更新的图形(这里找到该节点上第 0 个图形),然后调用该图形的 animate 方法指定动画的参数及每一帧的变化( onFrame 方法返回每一帧需要变化的参数集)。

  1. // 放大、变小动画
  2. G6.registerNode(
  3. 'circle-animate',
  4. {
  5. afterDraw(cfg, group) {
  6. // 获取该节点上的第一个图形
  7. const shape = group.get('children')[0];
  8. // 该图形的动画
  9. shape.animate(
  10. {
  11. // 动画重复
  12. repeat: true,
  13. // 每一帧的操作,入参 ratio:这一帧的比例值(Number)。返回值:这一帧需要变化的参数集(Object)。
  14. onFrame(ratio) {
  15. // 先变大、再变小
  16. const diff = ratio <= 0.5 ? ratio * 10 : (1 - ratio) * 10;
  17. let radius = cfg.size;
  18. if (isNaN(radius)) radius = radius[0];
  19. // 返回这一帧需要变化的参数集,这里只包含了半径
  20. return {
  21. r: radius / 2 + diff,
  22. };
  23. },
  24. },
  25. 3000,
  26. 'easeCubic',
  27. ); // 一次动画持续的时长为 3000,动画效果为 'easeCubic'
  28. },
  29. },
  30. 'circle',
  31. ); // 该自定义节点继承了内置节点 'circle',除了被复写的 afterDraw 方法外,其他按照 'circle' 里的函数执行。

增加带有动画的背景图形

afterDraw 方法中为已有节点添加额外的 shape ,并为这些新增的图形设置动画。

本例在 afterDraw 方法中,绘制了三个背景 circle ,分别使用不同的颜色填充,再调用 animate 方法实现这三个 circle 逐渐变大、变淡的动画。本例中没有使用 onFrame 函数,直接在 animate 函数的入参中设置每次动画结束时的最终目标样式,即半径增大 10,透明度降为 0.1。 download

  1. G6.registerNode(
  2. 'background-animate',
  3. {
  4. afterDraw(cfg, group) {
  5. let r = cfg.size / 2;
  6. if (isNaN(r)) {
  7. r = cfg.size[0] / 2;
  8. }
  9. // 第一个背景圆
  10. const back1 = group.addShape('circle', {
  11. zIndex: -3,
  12. attrs: {
  13. x: 0,
  14. y: 0,
  15. r,
  16. fill: cfg.color,
  17. opacity: 0.6,
  18. },
  19. });
  20. // 第二个背景圆
  21. const back2 = group.addShape('circle', {
  22. zIndex: -2,
  23. attrs: {
  24. x: 0,
  25. y: 0,
  26. r,
  27. fill: 'blue', // 为了显示清晰,随意设置了颜色
  28. opacity: 0.6,
  29. },
  30. });
  31. // 第三个背景圆
  32. const back3 = group.addShape('circle', {
  33. zIndex: -1,
  34. attrs: {
  35. x: 0,
  36. y: 0,
  37. r,
  38. fill: 'green',
  39. opacity: 0.6,
  40. },
  41. });
  42. group.sort(); // 排序,根据 zIndex 排序
  43. // 第一个背景圆逐渐放大,并消失
  44. back1.animate(
  45. {
  46. r: r + 10,
  47. opacity: 0.1,
  48. repeat: true, // 循环
  49. },
  50. 3000,
  51. 'easeCubic',
  52. null,
  53. 0,
  54. ); // 无延迟
  55. // 第二个背景圆逐渐放大,并消失
  56. back2.animate(
  57. {
  58. r: r + 10,
  59. opacity: 0.1,
  60. repeat: true, // 循环
  61. },
  62. 3000,
  63. 'easeCubic',
  64. null,
  65. 1000,
  66. ); // 1 秒延迟
  67. // 第三个背景圆逐渐放大,并消失
  68. back3.animate(
  69. {
  70. r: r + 10,
  71. opacity: 0.1,
  72. repeat: true, // 循环
  73. },
  74. 3000,
  75. 'easeCubic',
  76. null,
  77. 2000,
  78. ); // 2 秒延迟
  79. },
  80. },
  81. 'circle',
  82. );

部分图形旋转动画

这一例也是在 afterDraw 方法中为已有节点添加额外的 shape (本例中为 image),并为这些新增的图形设置旋转动画。旋转动画较为复杂,需要通过矩阵的操作实现。 download

  1. G6.registerNode(
  2. 'inner-animate',
  3. {
  4. afterDraw(cfg, group) {
  5. const size = cfg.size;
  6. const width = size[0] - 12;
  7. const height = size[1] - 12;
  8. // 添加图片 shape
  9. const image = group.addShape('image', {
  10. attrs: {
  11. x: -width / 2,
  12. y: -height / 2,
  13. width: width,
  14. height: height,
  15. img: cfg.img,
  16. },
  17. });
  18. // 该图片 shape 的动画
  19. image.animate(
  20. {
  21. // 动画重复
  22. repeat: true,
  23. // 每一帧的操作,入参 ratio:这一帧的比例值(Number)。返回值:这一帧需要变化的参数集(Object)。
  24. onFrame(ratio) {
  25. // 旋转通过矩阵来实现
  26. // 当前矩阵
  27. const matrix = Util.mat3.create();
  28. // 目标矩阵
  29. const toMatrix = Util.transform(matrix, [
  30. ['r', ratio * Math.PI * 2],
  31. ]);
  32. // 返回这一帧需要的参数集,本例中只有目标矩阵
  33. return {
  34. matrix: toMatrix,
  35. };
  36. },
  37. },
  38. 3000,
  39. 'easeCubic',
  40. );
  41. },
  42. },
  43. 'rect',
  44. );

边动画

关于边动画,以下面三个动画示例进行讲解:

  • 圆点在沿着线运动(下图左);
  • 虚线运动的效果(下图中,gif 图片的帧率问题导致看起来是静态的,可以访问下面的 demo 链接查看);
  • 线从无到有的效果(下图右)。 downloaddownloaddownload 以上三个边动画的 demo 代码见:边动画

圆点运动

本例通过在 afterDraw 方法中为边增加了一个 circle 图形,该图形沿着线运动。沿着线运动的原理:设定每一帧中,该 circle 在线上的相对位置。 download

  1. G6.registerEdge(
  2. 'circle-running',
  3. {
  4. afterDraw(cfg, group) {
  5. // 获得当前边的第一个图形,这里是边本身的 path
  6. const shape = group.get('children')[0];
  7. // 边 path 的起点位置
  8. const startPoint = shape.getPoint(0);
  9. // 添加红色 circle 图形
  10. const circle = group.addShape('circle', {
  11. attrs: {
  12. x: startPoint.x,
  13. y: startPoint.y,
  14. fill: 'red',
  15. r: 3,
  16. },
  17. });
  18. // 对红色圆点添加动画
  19. circle.animate(
  20. {
  21. // 动画重复
  22. repeat: true,
  23. // 每一帧的操作,入参 ratio:这一帧的比例值(Number)。返回值:这一帧需要变化的参数集(Object)。
  24. onFrame(ratio) {
  25. // 根据比例值,获得在边 path 上对应比例的位置。
  26. const tmpPoint = shape.getPoint(ratio);
  27. // 返回需要变化的参数集,这里返回了位置 x 和 y
  28. return {
  29. x: tmpPoint.x,
  30. y: tmpPoint.y,
  31. };
  32. },
  33. },
  34. 3000,
  35. ); // 一次动画的时间长度
  36. },
  37. },
  38. 'cubic',
  39. ); // 该自定义边继承内置三阶贝塞尔曲线 cubic

虚线运动的效果

虚线运动的效果是通过计算线的 lineDash ,并在每一帧中不断修改实现。 download

  1. // lineDash 的差值,可以在后面提供 util 方法自动计算
  2. const dashArray = [
  3. [0, 1],
  4. [0, 2],
  5. [1, 2],
  6. [0, 1, 1, 2],
  7. [0, 2, 1, 2],
  8. [1, 2, 1, 2],
  9. [2, 2, 1, 2],
  10. [3, 2, 1, 2],
  11. [4, 2, 1, 2],
  12. ];
  13. const lineDash = [4, 2, 1, 2];
  14. const interval = 9; // lineDash 的和
  15. G6.registerEdge(
  16. 'line-dash',
  17. {
  18. afterDraw(cfg, group) {
  19. // 获得该边的第一个图形,这里是边的 path
  20. const shape = group.get('children')[0];
  21. // 获得边的 path 的总长度
  22. const length = shape.getTotalLength();
  23. let totalArray = [];
  24. // 计算出整条线的 lineDash
  25. for (var i = 0; i < length; i += interval) {
  26. totalArray = totalArray.concat(lineDash);
  27. }
  28. let index = 0;
  29. // 边 path 图形的动画
  30. shape.animate(
  31. {
  32. // 动画重复
  33. repeat: true,
  34. // 每一帧的操作,入参 ratio:这一帧的比例值(Number)。返回值:这一帧需要变化的参数集(Object)。
  35. onFrame(ratio) {
  36. const cfg = {
  37. lineDash: dashArray[index].concat(totalArray),
  38. };
  39. // 每次移动 1
  40. index = (index + 1) % interval;
  41. // 返回需要修改的参数集,这里只修改了 lineDash
  42. return cfg;
  43. },
  44. },
  45. 3000,
  46. ); // 一次动画的时长为 3000
  47. },
  48. },
  49. 'cubic',
  50. ); // 该自定义边继承了内置三阶贝塞尔曲线边 cubic

线从无到有

线从无到有的动画效果,同样可以通过计算 lineDash 来实现。 download

  1. G6.registerEdge(
  2. 'line-growth',
  3. {
  4. afterDraw(cfg, group) {
  5. const shape = group.get('children')[0];
  6. const length = group.getTotalLength();
  7. shape.animate(
  8. {
  9. // 动画重复
  10. repeat: true,
  11. // 每一帧的操作,入参 ratio:这一帧的比例值(Number)。返回值:这一帧需要变化的参数集(Object)。
  12. onFrame(ratio) {
  13. const startLen = ratio * length;
  14. // 计算线的lineDash
  15. const cfg = {
  16. lineDash: [startLen, length - startLen],
  17. };
  18. return cfg;
  19. },
  20. },
  21. 2000,
  22. ); // 一次动画的时长为 2000
  23. },
  24. },
  25. 'cubic',
  26. ); // 该自定义边继承了内置三阶贝塞尔曲线边 cubic

交互动画

在交互的过程中也可以添加动画。如下图所示,当鼠标移到节点上时,所有与该节点相关联的边都展示虚线运动的动画。交互动画.gif上图完整 demo 即代码参见:状态切换动画

这种动画涉及到了边的 状态。在自定义边时复写 setState 方法,可对边的各种状态进行监听。鼠标移动到节点上,相关边的某个状态被开启,setState 方法中监听到后开启动画效果。步骤如下:

  • 自定义边中复写 setState 方法监听该边的状态,以及某状态下的动画效果;
  • 监听中间的节点的 mouseentermouseleave 事件,触发相关边的状态变化。

下面代码节选自 demo 状态切换动画,请注意省略了部分代码,只展示了交互相关以及边动画相关的代码。

  1. // const data = ...
  2. // const graph = new G6.Graph({...});
  3. // lineDash 的差值,可以在后面提供 util 方法自动计算
  4. const dashArray = [
  5. [0, 1],
  6. [0, 2],
  7. [1, 2],
  8. [0, 1, 1, 2],
  9. [0, 2, 1, 2],
  10. [1, 2, 1, 2],
  11. [2, 2, 1, 2],
  12. [3, 2, 1, 2],
  13. [4, 2, 1, 2],
  14. ];
  15. const lineDash = [4, 2, 1, 2];
  16. const interval = 9; // lineDash 的总长度。
  17. // 注册名为 'can-running' 的边
  18. G6.registerEdge(
  19. 'can-running',
  20. {
  21. // 复写setState方法
  22. setState(name, value, item) {
  23. const shape = item.get('keyShape');
  24. // 监听 running 状态
  25. if (name === 'running') {
  26. // running 状态为 true 时
  27. if (value) {
  28. const length = shape.getTotalLength();
  29. let totalArray = [];
  30. for (var i = 0; i < length; i += interval) {
  31. totalArray = totalArray.concat(lineDash);
  32. }
  33. let index = 0;
  34. shape.animate(
  35. {
  36. // 动画重复
  37. repeat: true,
  38. // 每一帧的操作,入参 ratio:这一帧的比例值(Number)。返回值:这一帧需要变化的参数集(Object)。
  39. onFrame(ratio) {
  40. const cfg = {
  41. lineDash: dashArray[index].concat(totalArray),
  42. };
  43. index = (index + 1) % interval;
  44. return cfg;
  45. },
  46. },
  47. 3000,
  48. ); // 一次动画的时长为 3000
  49. } else {
  50. // running 状态为 false 时
  51. // 结束动画
  52. shape.stopAnimate();
  53. // 清空 lineDash
  54. shape.attr('lineDash', null);
  55. }
  56. }
  57. },
  58. },
  59. 'cubic-horizontal',
  60. ); // 该自定义边继承了内置横向三阶贝塞尔曲线边 cubic-horizontal
  61. // 监听节点的 mouseenter 事件
  62. graph.on('node:mouseenter', ev => {
  63. // 获得当前鼠标操作的目标节点
  64. const node = ev.item;
  65. // 获得目标节点的所有相关边
  66. const edges = node.getEdges();
  67. // 将所有相关边的 running 状态置为 true,此时将会触发自定义节点的 setState 函数
  68. edges.forEach(edge => graph.setItemState(edge, 'running', true));
  69. });
  70. // 监听节点的 mouseleave 事件
  71. graph.on('node:mouseleave', ev => {
  72. // 获得当前鼠标操作的目标节点
  73. const node = ev.item;
  74. // 获得目标节点的所有相关边
  75. const edges = node.getEdges();
  76. // 将所有相关边的 running 状态置为 false,此时将会触发自定义节点的 setState 函数
  77. edges.forEach(edge => graph.setItemState(edge, 'running', false));
  78. });
  79. // graph.data(data);
  80. // graph.render();

注意:running 为 false 时,要停止动画,同时把 lineDash 清空。

[ 上一篇

内置的 Behavior ]($1780cda10ef6942c.md)[ 下一篇

自定义布局 Layout ]($740c4caa4f0162d2.md)