@antv/g6自定义节点dom类型shape无法触发事件原因分析

版本

@antv/g6: 4.8.10

踩坑

根据官网文档注册自定义节点时如果绘制dom类型shape,会发现node相关事件全都无法触发,比如node:click等
例如:

G6.registerNode('my-dom-node', {
    
    
  draw(cfg, group) {
    
    
    const keyShape = group.addShape('dom', {
    
    
      attrs: {
    
    
        html:'<h1>hello</h1>'
      },
      name: 'dom-shape',
      draggable: true,
    });
    return keyShape;
  }
});

原因分析

g6只有svg渲染模式画布可以支持dom类型节点,原理是通过foreignObject标签渲染dom
在事件触发时canvas会对比svg dom拾取的对象和shape对应的标签确定触发哪个节点的事件
@antv/g-svg/src/canvas.ts

// 覆盖 Container 中通过遍历的方式获取 shape 对象的逻辑,直接走 SVG 的 dom 拾取即可
getShape(x: number, y: number, ev: Event): IShape {
    
    
  let target = <Element>ev.target || <Element>ev.srcElement;
  // 
  if (!SHAPE_TO_TAGS[target.tagName]) {
    
    
    let parent = <Element>target.parentNode;
    while (parent && !SHAPE_TO_TAGS[parent.tagName]) {
    
    
      parent = <Element>parent.parentNode;
    }
    target = parent;
  }
  return this.find((child) => child.get('el') === target) as IShape;
}

此逻辑中通过SHAPE_TO_TAGS的映射判断dom是否对应到shape的逻辑有问题,SHAPE_TO_TAGS值如下,可见映射关系反了,导致并不能通过foreignObject标签获取到类型dom,从而无法正确定位dom类型的shape

circle: "circle"
dom: "foreignObject"
ellipse: "ellipse"
image: "image"
line: "line"
marker: "path"
path: "path"
polygon: "polygon"
polyline: "polyline"
rect: "path"
text: "text"

在event-controller中只有当通过getShape拾取到shape时才会触发节点事件
@antv/g-base/src/event/event-controller.ts

 // 触发事件
  _triggerEvent(type, ev) {
    
    
    const pointInfo = this._getPointInfo(ev);
    // 每次都获取图形有一定成本,后期可以考虑进行缓存策略
    const shape = this._getShape(pointInfo, ev);
    const method = this[`_on${
      
      type}`];
    let leaveCanvas = false;
    if (method) {
    
    
      method.call(this, pointInfo, shape, ev);
    } else {
    
    
      const preShape = this.currentShape;
      // 如果进入、移出画布时存在图形,则要分别触发事件
      if (type === 'mouseenter' || type === 'dragenter' || type === 'mouseover') {
    
    
        this._emitEvent(type, ev, pointInfo, null, null, shape); // 先进入画布
        if (shape) {
    
    
          this._emitEvent(type, ev, pointInfo, shape, null, shape); // 再触发图形的事件
        }
        if (type === 'mouseenter' && this.draggingShape) {
    
    
          // 如果正在拖拽图形, 则触发 dragleave
          this._emitEvent('dragenter', ev, pointInfo, null);
        }
      } else if (type === 'mouseleave' || type === 'dragleave' || type === 'mouseout') {
    
    
        leaveCanvas = true;
        if (preShape) {
    
    
          this._emitEvent(type, ev, pointInfo, preShape, preShape, null); // 先触发图形的事件
        }
        this._emitEvent(type, ev, pointInfo, null, preShape, null); // 再触发离开画布事件
        if (type === 'mouseleave' && this.draggingShape) {
    
    
          this._emitEvent('dragleave', ev, pointInfo, null);
        }
      } else {
    
    
        this._emitEvent(type, ev, pointInfo, shape, null, null); // 一般事件中不需要考虑 from, to
      }
    }
    if (!leaveCanvas) {
    
    
      this.currentShape = shape;
    }
    // 当鼠标从画布移动到 shape 或者从 preShape 移动到 shape 时,应用 shape 上的鼠标样式
    if (shape && !shape.get('destroyed')) {
    
    
      const canvas = this.canvas;
      const el = canvas.get('el');
      el.style.cursor = shape.attr('cursor') || canvas.get('cursor');
    }
  }

另一个坑

自定义节点时最好覆盖drawShape方法而不是draw方法

源码分析

通过分析shapeBase源码可知,draw方法通过调用drawShap方法获取shape对象,并注册到shapeMap映射中,如果直接覆盖draw则导致无法正确映射
此外draw还额外增加了label的绘制

 /**
   * 绘制节点/边,包含文本
   * @override
   * @param  {Object} cfg 节点的配置项
   * @param  {G.Group} group 节点的容器
   * @return {IShape} 绘制的图形
   */
  draw: function draw(cfg, group) {
    
    
    group['shapeMap'] = {
    
    };
    this.mergeStyle = this.getOptions(cfg);
    // 获取要绘制的图形
    var shape = this.drawShape(cfg, group);
    // 设置样式
    shape.set('className', this.itemType + CLS_SHAPE_SUFFIX);
    // 添加映射关系
    group['shapeMap'][this.itemType + CLS_SHAPE_SUFFIX] = shape;
    if (cfg.label) {
    
    
      var label = this.drawLabel(cfg, group);
      label.set('className', this.itemType + CLS_LABEL_SUFFIX);
      group['shapeMap'][this.itemType + CLS_LABEL_SUFFIX] = label;
    }
    return shape;
  },
  drawShape: function drawShape(cfg, group) {
    
    
    // 默认没有绘制图形
    return null; 
  },

猜你喜欢

转载自blog.csdn.net/zhoudingding/article/details/130639121