一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第2天,点击查看活动详情。
项目需求: 最近有一个项目是电路的模拟实验平台,需求为需要有一个工作画布,和一个组件库,实现组件器械的可拖拽、可连接,实验状态可以控制,数据流向展示等功能。就像我们平时课上的虚拟仿真平台一样,又需要像流程图软件一样,可以托拽各种组件到画布中。最后我选择使用antv的X6来制作。
前清提要: 上一期我们讲了如何在react中引入antv x6以及如何创建画布,这一期我们讲一讲antv-X6中的拖拽组件Dnd和Stencil。最开始找了很久,我也是发现了它的这个拖拽组件,感觉就是我想要的样子,于是使用X6来做的。(此前,蚂蚁金服的antd我在别的项目中也是经常使用,样式简洁美观,也很好用,期望他的X6也能同样好用。)
Dnd和Stencil,两者均为x6中的插件,Dnd提供了基础的拖拽能力,Stencil则是在 Dnd 基础上的进一步封装,提供了一个类似侧边栏的 UI 组件,并支持分组、折叠、搜索等能力。接下来讲讲怎么用,走起。
Dnd
Step 1 初始化
引入并创建一个 Dnd 的实例,官方还提供了一些选项来定制拖拽行为。(graph还是需要先引入,可以加在componentDidMount中)
import { Graph, Dom, Addon } from '@antv/x6'
const { Dnd } = Addon
this.dnd = new Dnd({
target: graph,//绑定画布
scaled: false,
animation: true,
//validateNode用于拖拽结束时,验证节点是否可以放置到目标画布中。
validateNode(droppingNode, options) {
return droppingNode.shape === 'html'
? new Promise<boolean>((resolve) => {
const { draggingNode, draggingGraph } = options
const view = draggingGraph.findView(draggingNode)!
const contentElem = view.findOne('foreignObject > body > div')
Dom.addClass(contentElem, 'validating')
setTimeout(() => {
Dom.removeClass(contentElem, 'validating')
resolve(true)
}, 3000)
})
: true
},
})
Step 2 开始拖拽组件
如上图,左侧即为Dnd创建的可拖拽组件库,右侧为画布,红色圆圈内为拖拽时的矩形。
<div
data-type="rect"
className="dnd-rect"
onMouseDown={this.startDrag}//绑定
>
Rect
</div>
startDrag = (e: React.MouseEvent<HTMLDivElement, MouseEvent>) => {
const target = e.currentTarget //获取目标对象
const type = target.getAttribute('data-type')
const node =
type === 'rect'
? this.graph.createNode({
width: 100,//设置矩形拖拽时的样式
height: 40,
attrs: {
label: {
text: 'Rect',
fill: '#6a6c8a',
},
body: {
stroke: '#31d0c6',
strokeWidth: 2,
},
},
})
: this.graph.createNode({
width: 60,//设置圆形拖拽时的样式
height: 60,
shape: 'html',
html: () => {
const wrap = document.createElement('div')
wrap.style.width = '100%'
wrap.style.height = '100%'
wrap.style.display = 'flex'
wrap.style.alignItems = 'center'
wrap.style.justifyContent = 'center'
wrap.style.border = '2px solid rgb(49, 208, 198)'
wrap.style.background = '#fff'
wrap.style.borderRadius = '100%'
wrap.innerText = 'Circle'
return wrap
},
})
this.dnd.start(node, e.nativeEvent as any)
}
当按下鼠标时调用下面方法开始拖拽,不添加的话将无法实现拖拽功能。
dnd.start(node, e)
| 选项 | 类型 | 说明 |
|---|---|---|
| node | Node | 开始拖拽的节点。 |
| e | MouseEvent JQuery.MouseDownEvent | 鼠标事件。 |
- 开始拖拽时,根据
options.delegateGraphOptions选项创建一个代理画布,然后使用start提供的node作为options.getDragNode方法的参数,返回一个代理节点(默认克隆),并将代理节点添加到代理画布中。 - 拖拽过程中,根据鼠标位置实时更新代理画布的在页面中的绝对位置。
- 拖拽结束时,使用代理节点做为
options.getDropNode方法的参数,返回一个放置到目标画布的目标节点(默认克隆代理节点),然后调用options.validateNode方法来验证节点是否可以被添加到目标画布中,该验证方法支持异步验证,例如发送接口到远端验证或者将新节点插入到数据库。如果通过验证,则将目标节点添加到目标画布中,否则根据options.animation选项将代理画布移动到开始拖动的位置,最后销毁代理画布。 成功拖拽到右侧画布中的样子如下图:
此外还能自定义拖拽节点的样式,也就是拖拽出来的样式可以单独重新定义。
const dnd = new Addon.Dnd({
getDragNode(node) {
// 这里返回一个新的节点作为拖拽节点
return graph.createNode({
width: 100,
height: 100,
shape: 'rect',
attrs: {
body: {
fill: '#ccc'
}
}
})
}
})