vue2实现在同个div上,既要能鼠标拖动还兼具点击事件
前阵子处理一个需求,当中有个小小的技术点,花了点小心思,特记录一下。
这个小小的技术点是:需要在同个div上,既要有鼠标点击移动功能,又要有单独的点击事件。
首先考虑上下移动效果:
- 使用
fixed/absolute布局,将要实现上下移动效果的div与正常文档流隔离开来 - 位移上采用
top来定位,通过获取鼠标的位置,计算出div位移值,再赋值给top - 通过
onmousedown(鼠标按下)绑定事件,在触发的事件中定义onmousemove和onmouseup:onmousemove实现div跟随鼠标上下移动的功能;onmouseup用来释放onmousemove和onmouseup事件- 注意:
onmousedown事件中,需要调用e.preventDefault()和e.stopPropagation(),来防止事件冒泡
其次考虑点击功能:
- 在上下移动基础上,增加点击事件,通过
onclick触发点击事件。 - 了解事件触发流程:
- 点击事件:
mousedown->mouseup->click - 上下移动:
mousedown->mousemove->mouseup综上,如果移动div时,实际上会触发:mousedown->mousemove->mouseup->click:就是说,当每次移动div结束后,鼠标在div上方松开,这时将会触发click事件(这个是不符合预期的),因此,要想区分两个不同的事件,需要通过加标志来区分,转化成代码逻辑为: - 上下移动事件:
mousemove时设置标志ifMove=true,mouseup时通过setTimeout重置ifMove=false - 点击事件:
click事件触发时,判断!ifMove,则继续,否则return
- 点击事件:
drag(e) {
e.preventDefault()
e.stopPropagation()
let then = this
let el = this.$refs.moveBox
let offsetHeight = el.offsetHeight
let minTop = 50
let maxTop = this.clientHeight - offsetHeight
document.onmousemove = (movee) => {
then.ifMove = true
let t = movee.pageY + (offsetHeight/2 * movee.movementY)
if(t < minTop) t = minTop
if(t > maxTop) t = maxTop
el.style.cssText += `top: ${t}px`
}
document.onmouseup = (upe) => {
document.onmousemove = null
document.onmouseup = null
setTimeout(() => {
then.ifMove = true
})
}
return false
}
以上,但是跑了几天,发现这种写法,打开页面持续1天,就会发现页面有明显卡顿的现象,想了下,觉得可能是频繁使用top布局导致的(top会造成页面的重绘回流,一开始写的时候觉得应该差别不大,且直接用top实现起来容易点,结果就是:打脸!果然不能懒,不然还得返工,更浪费时间~)
考虑了下,决定位移的实现方法改成用transform来实现,减少页面的重绘、回流,同时使用transform可以利用GPU加速,提升性能。
drag(e) {
e.preventDefault()
e.stopPropagation()
let then = this
let el = this.$refs.moveBox
let offsetHeight = el.offsetHeight
let originalTranslateY = 0
if(el.style.transform?.includes('translateY')) {
originalTranslateY = el.style.transform.slice(11, -3) - 0
}
let pageY = e.pageY - originalTranslateY // 计算出原始的位置
let maxTop = Math.ceil(this.clientHeight * 0.1) - offsetHeight
let minTop = 50 - this.clientHeight * 0.9 - offsetHeight
document.onmousemove = (movee) => {
then.ifMove = true
let translateY = movee.pageY - pageY
if(translateY < minTop) translateY = minTop
if(translateY > maxTop) translateY = maxTop
el.style.cssText += `transform: translateY(${translateY}px)`
}
document.onmouseup = (upe) => {
document.onmousemove = null
document.onmouseup = null
el = null
setTimeout(() => {
then.ifMove = true
})
}
return false
}
页面html,关键代码如下:
<div ref="moveBox" class="move-box" style="transform: translateY(0px)">
<div @mousedown="drag" title="按住拖动可上下移动窗口">
<div @click.prevent="handleShowBigBox">
// …………………………………………
</div>
</div>
// 点击事件,会收起上面的小窗口,打开大窗口
<transition name="form-scale">
<div class="big-box" v-show="modal.show">
// …………………………………………
// …………………………………………
// …………………………………………
</div>
</transition>
</div>
div需要脱离文档流,设置absolute、fixed定位都可
.move-box {
position: fixed;
top: 90%;
right: 6px;
transition: all linear .6s;
z-index: 99;
border-radius: 5px;
overflow: hidden;
::-webkit-scrollbar {
width:0 !important;
}
}
改用transform来实现位移后,页面就没再出现卡顿点击事件失效的问题了~
耶,超棒