写在前面
本文是可视化组件开发的第三篇了,讲一下组件在拖拉拽过程中触发的事件以及如何处理组件的属性以及展示方式。
组件图片绑定事件
也就是从可视化大屏左侧菜单里将组件拖进来的一个动作:
如图,左侧的其实并不是真正的组件实例,只是图片缩略图。
大屏添加组件通常通过点击组件图片或者将组件拖拽至画布上,因此需要给组件图片绑定点击事件和拖拽事件。
dragstart事件
用户开始拖拉时,在被拖拉的节点上触发,该事件的target属性是被拖拉的节点。
DataTransfer 对象用于保存拖动并放下(drag and drop)过程中的数据,它可以保存一项或多项数据,这些数据项可以是一种或者多种数据类型。通过dataTransfer对象的setData方法,保存当前拖拽的组件名称,供下文在画布上拖拽释放事件使用。
click事件
触发点击事件时,会直接将组件添加至画布,主要步骤包括创建组件、计算组件初始位置信息、将组件信息加入store、加载数据四步,类似下文第3节在画布上绑定drop释放事件。
画布绑定事件
第2节中对组件绑定了dragstart事件,当组件拖动到画布上时,触发画布的dragover事件,松开鼠标释放触发drop事件。
dragover事件
拖拉到当前节点上方时,在当前节点上触发dragover事件,该事件的target属性是当前节点,只要没有离开这个节点,dragover事件会持续触发。
默认情况下,数据/元素不能放置到其他元素中, 若要实现改功能,需要防止元素的默认处理方法,可以通过调用 event.preventDefault() 方法来实现 ondragover 事件。
运用dataTransfer对象,不仅仅能传输数据,还能通过dataTransfer对象确定被拖拽的元素以及作为放置目标的元素能够接收什么操作。要实现这样的功能就用到了dropEffect属性和effectAllowed属性。
其中,通过dropEffect属性可以知道被拖动的元素能够执行哪种行为。这个属性的四个值如下:
none:不能把拖动的元素放在这里。这是除了文本框之外所有元素默认的值。
move:应该把拖动的元素移动到放置目标。
copy:应该把拖动的元素复制到放置目标。
link:放置目标会打开拖动的元素(但拖动的元素必须是个链接,有URL地址)。
把元素拖动到放置目标上的时候,以上每一个值都会导致光标显示为不同的符号。
drop事件
在一个拖动过程中,最终释放鼠标按键时触发此事件,在可视化大屏场景中,释放鼠标时即增加组件至画布中:
1. 获取大屏缩放比例scale
const { scale } = canvas.value
2. 获取大屏面板左侧和顶部偏移距离(getPanelOffset方法)
const { left, top } = toolbarStore.getPanelOffset
getPanelOffset方法获取当前大屏画布的偏移位置,根据大屏展示/隐藏顶部工具栏、展示/隐藏左侧组件栏、展示/隐藏右侧配置项的状态来计算大屏的x、y、left、top值。
3. 根据画布缩放比例,计算事件触发处左侧和顶部偏移
const offsetLeft = (ev.clientX - left) / scale
const offsetTop = (ev.clientY - top) / scale
4. 减去组件自身宽度的一半获取组件当前放置的位置信息
com.attr.x = Math.round(offsetLeft - com.attr.w / 2)
com.attr.y = Math.round(offsetTop - com.attr.h / 2)
5. 添加组件,存放store
await comStore.add(com)
6. 加载数据源
画布上拖拽移动组件
在组件上绑定mousemove和mouseup事件
ev.clientX、ev.clientY:按下鼠标时的指针坐标,即初始位置时鼠标指针坐标 e.clientX、e.clientY:移动过程中当前鼠标指针坐标
平移组件
1. 选择组件后,在移动鼠标过程中触发mousemove事件,根据组件初始位置x和y、大屏缩放比例scale、栅格grid大小,同步计算更新组件的位置属性x和y。
如下图所示,其中变量 ox 和 oy 分别记录按下鼠标时被拖放元素的纵横坐标值,它们可以通过事件对象的 offsetLeft 和 offsetTop 属性获取。变量 mx 和 my 分别表示按下鼠标时、鼠标指针的坐标位置。event.mx 和 event.my 是事件对象的自定义属性,用它们来存储当鼠标移动时鼠标指针的实时位置。
当获取了上面 3 对坐标值之后,就可以动态计算拖动中元素的实时坐标位置,即 x 轴值为 ox+event.mx-mx,y 轴为 oy+event.my-my。
结合大屏缩放比例scale、栅格grid,优化后代码如下:
2. 移动到目标位置时,松开鼠标按键触发mouseup释放事件,停止监听mousemove和mouseup事件。
- 页面渲染展示:通过transform-translate属性对组件位置进行平移
缩放组件
选中组件时,默认给组件增加八个方位点,通过鼠标拖拉方位点来实现组件的缩放,方位点的命名如下图:
1. 初始状态
创建一个空对象来存放缩放过程中组件的位置信息xy、宽度w、高度h
获取点击坐标startPoint { x: ev.clientX, y: ev.clientY }
2. 移动过程中,通过mousemove事件触发,调用calcResizeForNormal()方法实时计算缩放后位置信息,其中calcResizeForNormal()方法接收参数为:
- ir:拖拽方向(t、rt、r、rb等八个方位点名称)
- attr:组件初始状态时的基本信息
- startPoint:拖拽起始点位置
- curPositon:当前拖拽位置
- scale:大屏缩放比例
- pos:当前组件在拖拽过程中的基本信息
- 当拖拽方位点为dir=t,即顶部中点时:
组件高度被缩放,同时位置y改变,宽度和位置x保持不变,结合大屏缩放比例scale,此时pos的计算方式如下:
- 当拖拽方向dir=lt,即左上顶点时:
组件高度、宽度同时被缩放,坐标x、y也同时改变,是八个方位点中改变最多的一个点,结合大屏缩放比例scale,pos的计算方式:
3. 步骤2计算得到pos对象的基本信息后,将其坐标信息、宽度和高度赋值给组件,并执行回调函数moveCallback()计算组件缩放比例:
4. 移动到目标位置时,松开鼠标按键触发mouseup释放事件,停止监听mousemove和mouseup事件,执行回调函数upCallback()渲染组件,并记下松开鼠标指针时拖动元素的基本信息,以及鼠标指针的位置,作为下一次拖放操作时的初始状态。
- 页面展示:通过transform-scale属性对组件进行缩放展示
旋转组件
获取组件中心点的位置:
调用js原生getBoundingClientRect()方法获取元素位置,可获得DOM元素的大小及其相对于视口的位置,该函数返回一个Object对象,该对象有6个属性:
- rect.top:元素上边到视窗上边的距离;
- rect.right:元素右边到视窗左边的距离;
- rect.bottom:元素下边到视窗上边的距离;
- rect.left:元素左边到视窗左边的距离;
- rect.width:是元素自身的宽度;
- rect.height是元素自身的高度;
属性对应的距离如下图所示:
根据left、top和元素自身的宽度高度计算组件中心点位置:
计算组件初始旋转角度(角度制)
Math.atan2()方法计算二维坐标系中任意一个点(x, y)和原点(0, 0)的连线与X轴正半轴的夹角大小,表示点 (x, y) 对应的偏移角度,为一个逆时针角度,以弧度为单位。
为了更好地理解,我们用一个更形象的过程来描述Math.atan2()方法的原理:将X轴正半轴绕着原点旋转,直到它经过目标点(x, y),在这个旋转过程中,X轴正半轴扫过的角就是Math.atan2()方法返回的弧度。
函数接受参数为Math.atan2(y, x),注意:参数先传递y坐标!
根据步骤1中计算的组件中心点坐标、初始位置时鼠标指针坐标,坐标相减得到偏移向量,通过Math.atan2()方法以及组件本身的角度,计算初始偏移角度,以角度为单位:
移动过程
通过mousemove事件触发
计算旋转角度原理同步骤2,不同的是偏移向量是组件中心点坐标相对的移动过程中鼠标指针的位置,减去步骤2中的初始旋转角度,为避免出现负数,再加上360度修正下角度:
移动到目标位置时,松开鼠标按键触发mouseup释放事件,同缩放组件的mouseup事件逻辑。