版本
@antv/g6: 4.8.10
踩坑
根据官网文档注册自定义节点时如果绘制dom类型shape,会发现node相关事件全都无法触发,比如node:click等 例如:
代码语言:javascript复制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
代码语言:javascript复制// 覆盖 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
代码语言:javascript复制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
代码语言:javascript复制 // 触发事件
_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的绘制
代码语言:javascript复制 /**
* 绘制节点/边,包含文本
* @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;
},