Please enable Javascript to view the contents

G6中实现双指拖动范围限制

 ·  ☕ 4 分钟  ·  ✍️ 东 · 👀... 阅读

最近用G6做的一个图结构需求要做拖动范围限制,就是我们不希望用户可以把图完全拖出去画布,这样容易找不到造成困惑的局面,为了支持各种用户的拖动习惯,设计要求同时满足鼠标按键拖动和双指拖动。

鼠标按键拖动很容易就能实现,因为G6有一些默认的全局自定义行为,可以根据需要来在生成G6对象的时候传入参数,当然也支持拖动范围的限制,配置如下:

1
2
3
4
5
6
7
8
modes: {
    // 设置一些内置默认行为
    ['default']: [
      {
        type: 'drag-canvas',
        scalableRange: -100,// 拖动 canvas 可扩展的范围,默认为 0
      },
      ...

对于scalableRange 的配置,可以看这里;文档中给出了比较好的图片解释;设置scalableRange -100,就可以是我们的图在画布中拖动时候始终保持100px 的内容保持在画布内;

那么问题来了,没有双指拖动的默认行为怎么办?G6 提供了registerBehavior 可以注册自定义行为,在new G6ES.Graph 之前自定义一个行为,并且在上面的default 行为中添加成默认行为:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
G6ES.registerBehavior('double-finger-drag-canvas', {
      getEvents: function getEvents() {
        return {
          wheel: 'onWheel',
        };
      },

      onWheel: (ev) => {
        if (ev.ctrlKey) {
          // 按住ctrl 键滚轮缩放 这里也是考虑到了一些通用的用户习惯,于是我们保留了demo 中的这个操作
          const canvas = graph.get('canvas');
          const point = canvas.getPointByClient(ev.clientX, ev.clientY);
          let ratio = graph.getZoom();
          if (ev.wheelDelta > 0) {
            ratio += ratio * 0.05;
          } else {
            ratio -= ratio * 0.05;
          }
          graph.zoomTo(ratio, {
            x: point.x,
            y: point.y,
          });
          config.setZoomValue(Math.floor(ratio * 100));
        } else { // 双指拖动
          const x = ev.deltaX || ev.movementX;
          const y = ev.deltaY || ev.movementY;
          graph.translate(-x, -y);
        }
        ev.preventDefault();
      },
    });


// default 行为中添加成默认行为
modes: {
    // 设置一些内置默认行为
    ['default']: [
      {
        type: 'drag-canvas',
        scalableRange: -100,// 拖动 canvas 可扩展的范围,默认为 0
      },
      'double-finger-drag-canvas',
      ...

终于实现了让人为难的双指移动需求,那么下一个问题来了,怎么实现限制范围?

翻遍文档没有找到画布移动时候上下左右的位置信息接口,也就是说没有办法得到我们移动dom 元素时候能获得的元素的div.style.left 以及鼠标对象的clientX 等可以计算位置的信息,这就有点愁人了……

但是人家明明已经在鼠标拖动的时候实现了这个需求,于是我想的是,先看看能不能抄抄作业!都是基于这个图用的同一个坐标体系,说不定可以拿来借鉴,于是找到了项目源码中的 node_modules/@antv/g6/es/behavior/drag-canvas.js 文件 (我们的G6版本是4.0.1),发现onMouseMove的时候会调用updateViewport方法,那么肯定是这个方法更新位置,在这个方法内打个断点:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
updateViewport: function updateViewport(e) {
    debugger;
    var origin = this.origin;
    var clientX = +e.clientX;
    var clientY = +e.clientY;

    if (isNaN(clientX) || isNaN(clientY)) {
      return;
    }

    var dx = clientX - origin.x; // x 方向的位移
    var dy = clientY - origin.y; // y 方向的位移

    if (this.get('direction') === 'x') { // drag-canvas 中还有可能设置只有一个方向可以拖动
      dy = 0;
    } else if (this.get('direction') === 'y') {
      dx = 0;
    }

    this.origin = {
      x: clientX,
      y: clientY
    };
    var width = this.graph.get('width');
    var height = this.graph.get('height');
    var graphCanvasBBox = this.graph.get('canvas').getCanvasBBox();

    // 传入的scalableRange 就在这里起作用!
    if (graphCanvasBBox.minX <= width + this.scalableRange && graphCanvasBBox.minX + dx > width + this.scalableRange || graphCanvasBBox.maxX + this.scalableRange >= 0 && graphCanvasBBox.maxX + this.scalableRange + dx < 0) {
      dx = 0;
    }

    if (graphCanvasBBox.minY <= height + this.scalableRange && graphCanvasBBox.minY + dy > height + this.scalableRange || graphCanvasBBox.maxY + this.scalableRange >= 0 && graphCanvasBBox.maxY + this.scalableRange + dy < 0) {
      dy = 0;
    }

    this.graph.translate(dx, dy);
  },

1613220531236

终于,在graphCanvasBBox 这个变量中我们获得了想要的变量!!!好了,实现这个需求没有问题了!

接下来当然是写一个我们自己的范围限制函数,首先道理和我们限制Dom元素移动的时候位置判断是一样的,这里用一个简图来表示一下4个方向的临界点:

1613221341171

也就是说通过minX,maxX,minY,maxY 和宽高的关系就可以判断出当前图画在画布的位置,经过调试发现跟上面的updateViewport方法不同的是他提供的onWheel 方法回调中直接就能拿到跟移动之前的相对位移,不需要根据

clientX - origin.x 来计算,于是经过几次调试终于得到了我们的updateViewport 方法:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
let scalableRange = -100; // 自己定义一个范围限制
onWheel: (ev) => {
        if (ev.ctrlKey) {
          // 按住ctrl 键滚轮缩放
          const canvas = graph.get('canvas');
          const point = canvas.getPointByClient(ev.clientX, ev.clientY);
          let ratio = graph.getZoom();
          if (ev.wheelDelta > 0) {
            ratio += ratio * 0.05;
          } else {
            ratio -= ratio * 0.05;
          }
          graph.zoomTo(ratio, {
            x: point.x,
            y: point.y,
          });
          config.setZoomValue(Math.floor(ratio * 100));
        } else {
          // 这里是双指异动 + 范围限制
          let dx = ev.deltaX || ev.movementX;
          let dy = ev.deltaY || ev.movementY;
          let width = this.instance.get('width');
          let height = this.instance.get('height');
          let graphCanvasBBox = this.instance.get('canvas').getCanvasBBox();
          if (
            (graphCanvasBBox.minX <= width + scalableRange &&
              graphCanvasBBox.minX - dx > width + scalableRange) ||
            (graphCanvasBBox.maxX + scalableRange >= 0 &&
              graphCanvasBBox.maxX + scalableRange - dx < 0)
          ) {
            dx = 0;
          }
          if (
            (graphCanvasBBox.minY <= height + scalableRange &&
              graphCanvasBBox.minY - dy > height + scalableRange) ||
            (graphCanvasBBox.maxY + scalableRange >= 0 &&
              graphCanvasBBox.maxY + scalableRange - dy < 0)
          ) {
            dy = 0;
          }
          // eslint-disable-next-line no-use-before-define
          graph.translate(-dx, -dy);
        }
        ev.preventDefault();
      },

好了这个需求完成了~

从这个需求得到的经验就是,如果需要实现一个类似的需求,但是没有直接的方法和API,可以去他已经实现的代码借鉴,会有一些踩坑的过程,但是因为有之前的写过的拖动DOM 的需求经验,所以大概能很快找到他在这里的实现思路,算是一种经验和推理的产物吧~

参考的主要是G6 的官方文档:https://g6.antv.vision/zh/docs/manual/advanced/coordinate-system

分享

目录