事件
在本章节中主要介绍事件相关的知识,通过阅读,你可以了解到
- 可以监听哪些类别的事件
- 如何监听事件
视图交互事件
通过鼠标、键盘或者各种可交互的组件与应用产生交互时触发的事件。
鼠标事件
| 事件 | cell 节点/边 | node 节点 | port 连接桩 | edge 边 | blank 画布空白区域 |
|---|---|---|---|---|---|
| 单击 | cell:click |
node:click |
node:port:click |
edge:click |
blank:click |
| 双击 | cell:dblclick |
node:dblclick |
node:port:dblclick |
edge:dblclick |
blank:dblclick |
| 右键 | cell:contextmenu |
node:contextmenu |
node:port:contextmenu |
edge:contextmenu |
blank:contextmenu |
| 鼠标按下 | cell:mousedown |
node:mousedown |
node:port:mousedown |
edge:mousedown |
blank:mousedown |
| 移动鼠标 | cell:mousemove |
node:mousemove |
node:port:mousemove |
edge:mousemove |
blank:mousemove |
| 鼠标抬起 | cell:mouseup |
node:mouseup |
node:port:mouseup |
edge:mouseup |
blank:mouseup |
| 鼠标滚轮 | cell:mousewheel |
node:mousewheel |
- | edge:mousewheel |
blank:mousewheel |
| 鼠标进入 | cell:mouseenter |
node:mouseenter |
node:port:mouseenter |
edge:mouseenter |
graph:mouseenter |
| 鼠标离开 | cell:mouseleave |
node:mouseleave |
node:port:mouseleave |
edge:mouseleave |
graph:mouseleave |
注意
需要注意,这里的 mousemove 事件不同于常规的鼠标移动事件:仅在按下鼠标拖动时触发。
除了 mouseenter 和 mouseleave 外,事件回调函数的参数都包含鼠标相对于画布的位置 x、y 和鼠标事件对象 e 等参数。
graph.on('cell:click', ({ e, x, y, cell, view }) => {})
graph.on('node:click', ({ e, x, y, node, view }) => {})
graph.on('edge:click', ({ e, x, y, edge, view }) => {})
graph.on('blank:click', ({ e, x, y }) => {})
graph.on('cell:mouseenter', ({ e, cell, view }) => {})
graph.on('node:mouseenter', ({ e, node, view }) => {})
graph.on('edge:mouseenter', ({ e, edge, view }) => {})
graph.on('graph:mouseenter', ({ e }) => {})
自定义点击事件
我们可以在节点/边的 DOM 元素上添加自定义属性 event 或 data-event 来监听该元素的点击事件,例如:
node.attr({
// 表示一个删除按钮,点击时删除该节点
image: {
event: 'node:delete',
xlinkHref: 'trash.png',
width: 20,
height: 20,
},
})
可以通过绑定的事件名 node:delete 或通用的 cell:customevent、node:customevent、edge:customevent 事件名来监听。
graph.on('node:delete', ({ view, e }) => {
e.stopPropagation()
view.cell.remove()
})
graph.on('node:customevent', ({ name, view, e }) => {
if (name === 'node:delete') {
e.stopPropagation()
view.cell.remove()
}
})
import './index.less'
import { createRoot } from 'react-dom/client'
import React from 'react'
import { Graph } from '@antv/x6'
Graph.registerNode(
'custom-click-node',
{
markup: [
{
tagName: 'rect',
selector: 'body',
},
{
tagName: 'text',
selector: 'label',
},
{
tagName: 'g',
children: [
{
tagName: 'text',
selector: 'btnText',
},
{
tagName: 'rect',
selector: 'btn',
},
],
},
],
attrs: {
btn: {
refX: '100%',
refX2: -28,
y: 4,
width: 24,
height: 18,
rx: 10,
ry: 10,
fill: 'rgba(255,255,0,0.01)',
stroke: 'red',
cursor: 'pointer',
event: 'node:delete',
},
btnText: {
fontSize: 14,
fill: 'red',
text: 'x',
refX: '100%',
refX2: -19,
y: 17,
cursor: 'pointer',
pointerEvent: 'none',
},
body: {
stroke: '#8f8f8f',
strokeWidth: 1,
fill: '#fff',
rx: 6,
ry: 6,
refWidth: '100%',
refHeight: '100%',
},
label: {
fontSize: 14,
fill: '#333333',
refX: '50%',
refY: '50%',
textAnchor: 'middle',
textVerticalAnchor: 'middle',
},
},
},
true,
)
export default class Example extends React.Component {
private container: HTMLDivElement
componentDidMount() {
const graph = new Graph({
container: this.container,
background: {
color: '#F2F7FA',
},
})
const source = graph.addNode({
shape: 'custom-click-node',
x: 40,
y: 80,
width: 120,
height: 40,
attrs: {
label: {
text: 'Source',
},
},
})
const target = graph.addNode({
shape: 'custom-click-node',
x: 360,
y: 80,
width: 120,
height: 40,
attrs: {
label: {
text: 'Target',
},
},
})
graph.addEdge({
source,
target,
attrs: {
line: {
stroke: '#8f8f8f',
strokeWidth: 1,
},
},
})
graph.on('node:delete', ({ view, e }: any) => {
e.stopPropagation()
view.cell.remove()
})
}
refContainer = (container: HTMLDivElement) => {
this.container = container
}
render() {
return (
<div className="custom-click-app">
<div className="app-content" ref={this.refContainer} />
</div>
)
}
}
const root = createRoot(document.getElementById('container')!)
root.render(<Example />)
window.addEventListener('pagehide', () => root.unmount(), { once: true })
.custom-click-app {
display: flex;
width: 100%;
border: 1px solid #f0f0f0;
padding: 16px 8px;
font-family: sans-serif;
.app-content {
flex: 1;
height: 200px;
margin-right: 8px;
margin-left: 8px;
box-shadow: 0 0 10px 1px #e9e9e9;
}
}画布缩放/平移
| 事件名 | 回调参数 | 说明 |
|---|---|---|
scale |
{ sx: number; sy: number; ox: number; oy: number } |
缩放画布时触发,sx 和 sy 是缩放比例,ox 和 oy 是缩放中心。 |
resize |
{ width: number; height: number } |
改变画布大小时触发,width 和 height 是画布大小。 |
translate |
{ tx: number; ty: number } |
平移画布时触发,tx 和 ty 分别是 X 和 Y 轴的偏移量。 |
graph.on('scale', ({ sx, sy, ox, oy }) => {})
graph.on('resize', ({ width, height }) => {})
graph.on('translate', ({ tx, ty }) => {})
节点或边移动
| 事件名 | 回调参数 | 说明 |
|---|---|---|
node:move |
{ e: Dom.MouseDownEvent; x: number; y: number; node: Node; view: NodeView } |
开始移动节点时触发。 |
node:moving |
{ e: Dom.MouseMoveEvent; x: number; y: number; node: Node; view: NodeView } |
移动节点时触发。 |
node:moved |
{ e: Dom.MouseUpEvent; x: number; y: number; node: Node; view: NodeView } |
移动节点后触发。 |
edge:move |
{ e: Dom.MouseDownEvent; x: number; y: number; node: Node; view: NodeView } |
开始移动边时触发。 |
edge:moving |
{ e: Dom.MouseMoveEvent; x: number; y: number; node: Node; view: NodeView } |
移动边时触发。 |
edge:moved |
{ e: Dom.MouseUpEvent; x: number; y: number; node: Node; view: NodeView } |
移动边后触发。 |
参数中的 x 和 y 是鼠标相对于画布的坐标。
graph.on('node:moved', ({ e, x, y, node, view }) => {})
节点嵌入
| 事件名 | 回调参数 | 说明 |
|---|---|---|
node:embed |
{ e: Dom.MouseDownEvent; x: number; y: number; node: Node; view: NodeView, currentParent: Node } |
开启嵌入,在开始拖动节点时触发。 |
node:embedding |
{ e: Dom.MouseMoveEvent; x: number; y: number; node: Node; view: NodeView, currentParent: Node, candidateParent: Node } |
寻找目标节点过程中触发。 |
node:embedded |
{ e: Dom.MouseUpEvent; x: number; y: number; node: Node; view: NodeView, previousParent: Node, currentParent: Node } |
完成节点嵌入后触发。 |
边连接/取消连接
当拖动边的起始/终止箭头将边连接到节点/边,或将边从节点/边上分离后,会触发 edge:connected,回调函数的参数如下。
interface Args {
e: Dom.MouseUpEvent // 鼠标事件对象
edge: Edge // 边
view: EdgeView // 边的视图
isNew: boolean // 是否是新创建的边
type: Edge.TerminalType // 操作的是起始箭头还是终止箭头('source' | 'target')
previousCell?: Cell | null // 交互前连接到的节点/边
previousView?: CellView | null // 交互前连接到的节点/边的视图
previousPort?: string | null // 交互前连接到的连接桩 ID
previousPoint?: Point.PointLike | null // 交互前连接到的点(将边的终端从空白处拖动到节点/边上时记录起始终端的位置)
previousMagnet?: Element | null // 交互前连接到的元素
currentCell?: Cell | null // 交互后连接到的节点/边
currentView?: CellView | null // 交互后连接到的节点/边的视图
currentPort?: string | null // 交互后连接到的连接桩 ID
currentPoint?: Point.PointLike | null // 交互后连接到的点(将边的终端从节点/边上拖动到空白处时记录拖动后终端的位置)
currentMagnet?: Element | null // 交互后连接到的元素
}
我们可以通过 isNew 来判断连线完成后,对应的边是否是新创建的边。比如从一个连接桩开始,创建了一条边并连接到另一个节点/连接桩,此时 isNew 就为 true。
graph.on('edge:connected', ({ isNew, edge }) => {
if (isNew) {
// 对新创建的边进行插入数据库等持久化操作
}
})
特别注意的是,参数中的 previous... 是记录操作终端在连接/取消连接之前的状态,并不是指 sourceCell,在创建新边后获取 sourceCell 时不能使用 previousCell,正确的使用方式是:
graph.on('edge:connected', ({ isNew, edge }) => {
if (isNew) {
const source = edge.getSourceCell()
}
})
节点/边
添加/删除/修改
当节点/边被添加到画布时,触发以下事件:
addedcell:addednode:added(仅当 cell 是节点时才触发)edge:added(仅当 cell 是边时才触发)
当节点/边被移除时,触发以下事件:
removedcell:removednode:removed(仅当 cell 是节点时才触发)edge:removed(仅当 cell 是边时才触发)
当节点/边发生任何改变时,触发以下事件:
changedcell:changednode:changed(仅当 cell 是节点时才触发)edge:changed(仅当 cell 是边时才触发)
可以在节点/边上监听:
cell.on('added', ({ cell, index, options }) => {})
cell.on('removed', ({ cell, index, options }) => {})
cell.on('changed', ({ cell, options }) => {})
或者在 Graph 上监听:
graph.on('cell:added', ({ cell, index, options }) => {})
graph.on('cell:removed', ({ cell, index, options }) => {})
graph.on('cell:changed', ({ cell, options }) => {})
graph.on('node:added', ({ node, index, options }) => {})
graph.on('node:removed', ({ node, index, options }) => {})
graph.on('node:changed', ({ node, options }) => {})
graph.on('edge:added', ({ edge, index, options }) => {})
graph.on('edge:removed', ({ edge, index, options }) => {})
graph.on('edge:changed', ({ edge, options }) => {})
change:xxx
当调用 setXxx(val, options) 和 removeXxx(options) 方法去改变节点/边的数据时,并且 options.silent 不为 true 时,都将触发对应的 change 事件,并触发节点/边重绘。例如:
cell.setZIndex(2)
cell.setZIndex(2, { silent: false })
cell.setZIndex(2, { anyKey: 'anyValue' })
将触发 Cell 上的以下事件:
change:*change:zIndex
和 Graph 上的以下事件:
cell:change:*node:change:*(仅当 cell 是节点时才触发)edge:change:*(仅当 cell 是边时才触发)cell:change:zIndexnode:change:zIndex(仅当 cell 是节点时才触发)edge:change:zIndex(仅当 cell 是边时才触发)
可以在节点/边上监听:
// 当 cell 发生任何改变时都将被触发,可以通过 key 来确定改变项
cell.on(
'change:*',
(args: {
cell: Cell
key: string // 通过 key 来确定改变项
current: any // 当前值
previous: any // 改变之前的值
options: any // 透传的 options
}) => {
if (key === 'zIndex') {
//
}
},
)
cell.on(
'change:zIndex',
(args: {
cell: Cell
current?: number // 当前值
previous?: number // 改变之前的值
options: any // 透传的 options
}) => {},
)
或者在 Graph 上监听:
graph.on(
'cell:change:zIndex',
(args: {
cell: Cell
current?: number // 当前值
previous?: number // 改变之前的值
options: any // 透传的 options
}) => {},
)
// 当 cell 为节点时触发
graph.on(
'node:change:zIndex',
(args: {
cell: Cell
node: Node
current?: number // 当前值
previous?: number // 改变之前的值
options: any // 透传的 options
}) => {},
)
// 当 cell 为边时触发
graph.on(
'edge:change:zIndex',
(args: {
cell: Cell
edge: Edge
current?: number // 当前值
previous?: number // 改变之前的值
options: any // 透传的 options
}) => {},
)
其他 change 事件如下列表,回调函数的参数与上面提到的 change:zIndex 的参数结构一致。
- Cell
change:*change:attrschange:zIndexchange:markupchange:visiblechange:parentchange:childrenchange:toolschange:viewchange:data
- Node
change:sizechange:anglechange:positionchange:portschange:portMarkupchange:portLabelMarkupchange:portContainerMarkupports:addedports:removed
- Edge
change:sourcechange:targetchange:terminalchange:routerchange:connectorchange:verticeschange:labelschange:defaultLabelvertexs:addedvertexs:removedlabels:addedlabels:removed
除了上述这些内置的 Key,我们也支持监听自定义的 Key,例如
cell.on('change:custom', ({ cell, current, previous, options }) => {
console.log(current)
})
当通过 cell.prop('custom', 'any data') 方法修改 custom 属性的值时将触发 change:custom 事件。
动画
动画系统中支持以下事件:
cell:animation:finish动画结束时触发cell:animation:cancel动画取消时触发node:animation:finish动画结束时触发(仅当 cell 是节点时才触发)node:animation:cancel动画取消时触发(仅当 cell 是节点时才触发)edge:animation:finish动画结束时触发(仅当 cell 是边时才触发)edge:animation:cancel动画取消时触发(仅当 cell 是边时才触发)
视图
由于 X6 实现了异步的渲染调度算法,所以节点的添加不一定意味着挂载到画布上。节点在被挂载到画布时以及从画布上卸载时会分别触发单独的事件。
| 事件名 | 回调参数 | 说明 |
|---|---|---|
view:mounted |
{ view: CellView } |
节点被挂载到画布上时触发。 |
view:unmounted |
{ view: CellView } |
节点从画布上卸载时触发。 |
graph.on('view:mounted', ({ view }) => {})
graph.on('view:unmounted', ({ view }) => {})
大家经常需要在调用 fromJSON 或 resetCells 后,监听画布完成渲染事件,此时可以使用 render:done 事件(2.15.1 版本新增)。
graph.on('render:done', () => {
// pass
})
graph.fromJSON([...])