在Vue2中,如何实现浮窗移动功能如何通过组件监听的形式,实现浮窗拖拽式移动呢?下面,跟随我的脚步,一步一步了解功能细节
原功能基于 el-dialog
实现,因而代码中参数名称会与该组件有所关联
效果展示
相关DOM
了解所需DOM
首先,需要的明确的是,过程中涉及对哪几个DOM元素的操作?
-
为使得浮窗移动,需要动态调整其样式。因而,浮窗自身 便是操作元素之一
-
而我们还需要某个区域,用于触发功能。这个区域往往是浮窗的头部;因而,浮窗头部 也是操作元素之一
-
触发了功能,下一步便是执行移动操作。我们需要在视口相同范围内监听鼠标的位置。浮窗自身尺寸远小于视口,不满足需求。因而,我们只能采取其 外层遮罩 作为操作元素
综上,涉及的DOM元素有3个,分别是 浮窗自身、浮窗头部、外层遮罩
获取DOM
同一document
中,可能同时存在多个浮窗;直接使用 querySelector
获取DOM,易导致查询错误
因而,我们需要借助 Vue refs 缩小查询范围
外层遮罩 通过 $refs
获取,其余两个DOM元素,在其范围内查询
// 外层遮罩(切记在对应标签上设置ref)
this.dialogWrapperDom = this.$refs.dialog.$el
// 浮窗
this.dialogDom = this.dialogWrapperDom.querySelector('.el-dialog')
// 触发区域
this.dragBodyDom = this.dialogDom.querySelector('.el-dialog__header')
相关监听事件
鼠标「按下」
// 将函数定义到 DOM对象中,便于后期移除
this.dragBodyDom.dragMouseDownEvent = (e) => {}
// 设置鼠标按下操作
this.dragBodyDom.addEventListener(
'mousedown',
this.dragBodyDom.dragMouseDownEvent
)
鼠标「移动」
// 移动函数(单独定义,便于移除)
const move = (e) => {}
// 鼠标按下后,添加 鼠标移动 监听事件
this.dialogWrapperDom.addEventListener('mousemove', move)
鼠标「弹起」
// 鼠标抬起后,移除 鼠标移动 监听事件
this.dialogWrapperDom.addEventListener(
'mouseup',
() => {
// 移除 鼠标移动 事件
this.dialogWrapperDom.removeEventListener('mousemove', move)
},
// 执行一次后随即移除。避免多次开启移动后,累加 mouseup 事件
{ once: true }
)
浮窗位置计算逻辑
设置浮窗的位置时,我们需要明确其 左侧、顶部 距离视口边缘的距离(即 left
、top
)
借助 MDN官方文档 ,了解到 mouse相关事件 回调函数的 event
均包含两个参数 pageX
、pageY
,二者分别是鼠标指针相对于整个文档的 X轴、Y轴 坐标。
在 mousedown
和 mousemove
两个阶段中,我们均能获取到 pageX
和 pageY
。前者是一个 瞬时值,后者则是 动态值
计算「鼠标指针」与「浮窗边框」距离
在 mousedown
阶段,通过差值运算,便能得到 鼠标指针 与 浮窗左侧、顶部 的距离
const { offsetLeft, offsetTop } = this.dialogDom
// 计算鼠标距离盒子左边框、上边框的距离
const x = e.pageX - offsetLeft
const y = e.pageY - offsetTop
计算浮窗 left、top
在 mousedown
阶段,我们得到了 鼠标指针 与 浮窗左侧、顶部 的距离
而在 moudemove
阶段,我们可以实时获取到 pageX
和 pageY
通过 e.pageX - x
和 e.pageY - y
计算,便能得到 left
和 top
完整代码
下方 setDragListener
方法,需在 浮窗显示 时调用。为避免获取DOM元素失败,还需使用 $nextTick
包裹
为 避免DOM元素紊乱,未在 mounted
生命周期中定义,而选择在 setDragListener
方法内部定义
// 拖拽监听设置
setDragListener() {
// 获取DOM对象
this.dialogWrapperDom = this.$refs.dialog.$el
this.dialogDom = this.dialogWrapperDom.querySelector('.el-dialog')
this.dragBodyDom = this.dialogDom.querySelector('.el-dialog__header')
// 将函数定义到 DOM对象中,便于后期移除
this.dragBodyDom.dragMouseDownEvent = (e) => {
const { offsetLeft, offsetTop } = this.dialogDom
// 计算鼠标距离盒子左边框、上边框的距离
const x = e.pageX - offsetLeft
const y = e.pageY - offsetTop
// 移动函数(单独定义,便于移除)
const move = (e) => {
this.dialogDom.style.left = `${e.pageX - x}px`
this.dialogDom.style.top = `${e.pageY - y}px`
}
// 鼠标按下后,添加 鼠标移动 监听事件
this.dialogWrapperDom.addEventListener('mousemove', move)
// 鼠标抬起后,移除 鼠标移动 监听事件
this.dialogWrapperDom.addEventListener(
'mouseup',
() => {
this.dialogWrapperDom.removeEventListener('mousemove', move)
},
// 执行一次后随即移除,避免累加 mouseup事件
{ once: true }
)
}
// 设置鼠标按下操作
this.dragBodyDom.addEventListener(
'mousedown',
this.dragBodyDom.dragMouseDownEvent
)
},
浮窗关闭后,执行下方代码,移除监听,使浮窗返回原始位置
// 清除鼠标按下监听事件
this.dragBodyDom.removeEventListener(
'mousedown',
this.dragBodyDom.dragMouseDownEvent
)
// 清除拖拽时的位置信息
this.dialogDom.removeAttribute('style')
备注
浮窗基础样式
// 浮窗自身
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
// 触发区域
cursor: move;
user-select: none;
现存问题
当 el-dialog
组件使用了上述方法,且配置了 destroy-on-close
,会造成关闭时,闪回原始位置
destroy-on-close
会在关闭时,重置页面结构,进而导致原绑定在DOM元素上的 left
、top
属性丢失
为避免该问题,建议无特殊需求下,避免使用该属性
转载自:https://juejin.cn/post/7396934542958837771