vue2实现在同个div上,既要能鼠标拖动还兼具点击事件

1,027 阅读2分钟

vue2实现在同个div上,既要能鼠标拖动还兼具点击事件

前阵子处理一个需求,当中有个小小的技术点,花了点小心思,特记录一下。 这个小小的技术点是:需要在同个div上,既要有鼠标点击移动功能,又要有单独的点击事件。

首先考虑上下移动效果:

  1. 使用fixed/absolute布局,将要实现上下移动效果的div与正常文档流隔离开来
  2. 位移上采用top来定位,通过获取鼠标的位置,计算出div位移值,再赋值给top
  3. 通过onmousedown(鼠标按下)绑定事件,在触发的事件中定义onmousemoveonmouseup:
    • onmousemove实现div跟随鼠标上下移动的功能;
    • onmouseup用来释放onmousemoveonmouseup事件
    • 注意:onmousedown事件中,需要调用e.preventDefault()e.stopPropagation(),来防止事件冒泡

其次考虑点击功能:

  1. 在上下移动基础上,增加点击事件,通过onclick触发点击事件。
  2. 了解事件触发流程:
    • 点击事件: 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需要脱离文档流,设置absolutefixed定位都可

.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来实现位移后,页面就没再出现卡顿点击事件失效的问题了~ 耶,超棒