实现一个简单的拖拽原理

300 阅读2分钟

前言

这里我根据我实现的一个拖拽组件,简单把主要实现原理和注意点说明一下,总有人会有需求😄

原理

  • 监听拖拽元素的 mousedown 事件
onMouseDown(event) {
    // 监听事件
    // 为了避免不必要的事件监听,这里在 mousedown 事件中才去监听其他事件;
    // 这里直接用 document 监听 mousemove 和 mouseup 是因为拖拽过程中我会设置拖拽
    // 元素 css 属性 pointer-events: none;这样元素就监听不到事件了,这样做是为了能
    // 更自由的拖拽,和找到我拖拽的目标位置存在什么元素方便处理业务
    document.addEventListener('mousemove', this.onMoving)
    document.addEventListener('mouseup', this.onMouseUp)

    // 更新状态
    this.dragging = true // 记录当前是否在拖拽中
    this.style = null // 用于后续调整拖拽元素位置
    this.prevX = event.clientX // 记录开始位置
    this.prevY = event.clientY // 记录开始位置
    // 存之前 body 的 cursor 属性,因为拖拽过程中为了生动,我会重新设置 css 属性 cursor: grab; 和 
    // cursor: grabbing; 所以这里记录一下,之后方便还原
    this.prevCursor = document.body.style.cursor
    // 存之前 body 的 user-select 属性样式,这个点很重要,下面会重新设置 body 的 user-select 属性
    // 为 none 这样的话用户就不能在网页上选中区域了,如果不设置这个,你在拖拽过程中,滑动过的元素如果有
    // 文本内容就会被选中,然后拖拽就会被中断,你可以试试注释下面的配置看效果就知道了
    this.prevUserSelect = document.body.style.userSelect
    document.body.style.cursor = 'grabbing'
    document.body.style.userSelect = 'none'
}
  • mousemove 事件中我们做了什么,下面定义了两个方法,find 和 onMoving,代码中会告诉你他们可以用来干嘛
onMoving(event) {
    if (!this.dragging) return
    
    // 更新位置,这里用最新的x、y位置减去之前记录的x、y位置,就是偏移量,也就是拖拽偏移位置
    const translate3d = `
        translate(${event.clientX - this.prevX}px, ${event.clientY - this.prevY}px)
    `
    this.style = { transform: translate3d }

    // 寻找当前我所拖拽到的目标元素,因为之前拖拽元素设置了样式 pointer-events: none; 
    // 所以这里就可以很方便的找到我当前拖拽到的目标位置相关的信息,比如我可以用来做一个拖拽
    // 元素和目标元素交换位置的需求,这样这个处理就很有用
    this.find(event)
},
find(event) {
    this.$emit('find', event)
}
  • 最后 mouseup 事件中处理一下结尾逻辑就好
onMouseUp(event) {
    // 销毁事件,记得结束拖拽后销毁事件
    document.removeEventListener('mousemove', this.onMoving)
    document.removeEventListener('mouseup', this.onMouseUp)

    // 重置状态
    this.dragging = false
    this.style = { transition: 'all .2s ease' } // 配置一个过渡样式,让恢复拖拽时效果生动
    // 还原一下之前的 body 样式
    document.body.style.cursor = this.prevCursor
    document.body.style.userSelect = this.prevUserSelect
}