完成DOM元素拖拽应该考虑以下几个问题

  1. 监听的鼠标事情
  2. 拖拽定位父元素的兼容
  3. 拖拽超出父元素规模的处理
  4. 鼠标一直定位在被拖拽元素的中心点 – 合理优化

几个【间隔】特点

在正式开端写逻辑前,首先要对几个特点要有所了解

  • 获取元素的宽/高

    • offsetWidth/offsetHeight – 获取元素的宽度/高度(包括边框和内边距)
    • clientWidth/clientHeigh – 获取元素的宽度/高度(不包括边框)
    • clientLeft/clientTop – 获取左/上边框巨细
  • 鼠标方位

    • clientX/clientY – 鼠标相对于浏览器窗口左上角为坐标原点,X/Y轴的间隔
  • getBoundingClientRect()

    • 回来值是一个[DOMRect]目标,是包括整个元素的最小矩形(包括paddingborder-width)。该目标使用lefttoprightbottomxywidthheight这几个以像素为单位的只读特点描绘整个矩形的方位和巨细。除了widthheight以外的特点是相对于视图窗口的左上角来核算

几个【鼠标事情】监听

  • mousedown

    • 鼠标【按下】事情
    • 在这个事情中进行:
      1. 去获取子元素的DOMRect
      2. 敞开拖拽标识
  • mouseup

    • 鼠标【松开】事情
    • 在这个事情中进行:
      1. 关闭拖拽标识
      2. 收尾作业
  • mousemove

    • 鼠标【移动】事情
    • 在这个事情中进行:
      1. 改动拖拽元素的定位
      2. 处理拖拽元素超出规模问题

“拖拽目标” – dragDomObj

为了方便特点的调用,创建并露出一个“拖拽目标” - dragDomObj。这个目标存储公共特点、进口办法。

 const dragObjCom = {
    dragObj: null, // 被拖拽元素
    parentObj: null, // 被拖拽元素的父元素
    dragFlag: false, // 拖拽开端的标识
    topDistance: 0, // 被拖拽元素的top间隔
    leftDistance: 0, // 被拖拽元素的left间隔
    rectOfDragObj: null, // 获取被拖拽DOM的视口边际间隔 top right bottom left
    /*
    DOM元素拖拽函数封装
    @param { object } dragObj 被拖拽元素
    @param { object } parentObj 被拖拽元素的父元素
    */
    dragDomFunc(dragObj, parentObj = document.body) {
        this.dragObj = dragObj
        this.parentObj = parentObj
        // 监听鼠标按下事情
        document.addEventListener('mousedown', event => this.mouseDownforDrag(event))
        // 监听鼠标松开事情
        document.addEventListener('mouseup', () => this.mouseUpForDrag())
        // 监听鼠标移动事情
        document.addEventListener('mousemove', event => this.mouseMoveForDrag(event))
    }
 }

在这里之所以要传入参数parentObj 被拖拽元素的父元素,是为了兼容父元素是document.body自定寄父元素两种状况

mouseDownforDrag 的完成

// 鼠标按下办法
mouseDownforDrag(event) {
    // event不是被拖拽元素时,不作处理
    if (event.target !== this.dragObj) return
    this.dragFlag = true; // 开端拖拽
    if (this.parentObj === document.body) {
        this.rectOfDragObj = this.dragObj.getBoundingClientRect()
    } else {
        // 当自定义传入父元素时,要自主构建一个DOMRect目标
        this.rectOfDragObj = this.handleGetBoundingClientRect()
    }
},

handleGetBoundingClientRect 的完成

// getBoundingClientRect办法改写 - 当父元素不是document.body时
handleGetBoundingClientRect() {
    // 获取传入父元素的DOMRect
    const parentRectObj = this.parentObj.getBoundingClientRect();
    // 创建top/right/bototm/left特点
    const top = parentRectObj.top
    const right = parentRectObj.right
    const bottom = parentRectObj.bottom
    const left = parentRectObj.left 
    const boundingClientRectObj = {
        width: this.dragObj.offsetWidth,
        height: this.dragObj.offsetHeight,
        top,
        right,
        bottom,
        left,
    }
    return boundingClientRectObj
},

mouseUpForDrag 的完成

// 鼠标按下办法
mouseUpForDrag() {
    this.dragFlag = false; // 拖拽中止
    // 做元素定位左、上是否溢出父元素的规模
    if (this.dragObj.style.top.split('px')[0] < 0) this.dragObj.style.top = '0px';
    if (this.dragObj.style.left.split('px')[0] < 0) this.dragObj.style.left = '0px';
},

mouseMoveForDrag 的完成

// 鼠标移动办法
mouseMoveForDrag(e) {
    // 非拖拽状态下直接回来
    if (!this.dragFlag) return
    // clientX,clientY分别代表鼠标间隔屏幕可视区左上角的间隔
    /*
      减去被拖拽元素的宽高的1/2
      【意图】在拖动过程中鼠标一直停留在元素的中心方位
      【引发的问题】
        1. 中止拖动后,若 -top <= -rectOfDragObj.height/2, -left <= -rectOfDragObj.width/2 也辨认超出可视区外,所以在监听鼠标松开时做判别
        2. 拖动过程,间隔右、下间隔分别为 rectOfDragObj.width/2、rectOfDragObj.height/2 时,被拖拽元素敏捷定位到可视区域的宽高最大值,需要在,移出右、上的判别中减去这部分的值 
    */
    leftDistance = e.clientX - this.rectOfDragObj.width / 2
    topDistance = e.clientY - this.rectOfDragObj.height / 2
    // 处理鼠标移出屏幕可视区的判别
    let clientWidth = this.parentObj.clientWidth; // 获取父元素的可视宽度
    let clientHeight = this.parentObj.clientHeight; // 获取父元素的可视高度
    //【下面的判别都加了 this.rectOfDragObj.width(height)/2的判别,是因为上面逻辑把鼠标的方位放到拖拽元素的中心方位】
    // 移出左边区域
    if (e.clientX - this.rectOfDragObj.width / 2 <= 0) { leftDistance = 0;}
    // 移出上边区域
    if (e.clientY - this.rectOfDragObj.height / 2 <= 0) { topDistance = 0; }
    // 移出右边区域
    if (e.clientX + this.rectOfDragObj.width - this.rectOfDragObj.width / 2 > clientWidth) {
      leftDistance = clientWidth - this.rectOfDragObj.width;
    }
    // 移出下边区域
    if (e.clientY + this.rectOfDragObj.height - this.rectOfDragObj.height / 2 > clientHeight) {
      topDistance = clientHeight - this.rectOfDragObj.height;
    }
    // 改动元素定位
    this.dragObj.style.top = `${topDistance}px`
    this.dragObj.style.left = `${leftDistance}px`
}

html

<div id="drag_parent">
    <div id="drag_dom"></div>
</div>
<style>
    body {
      height: 100vh;
    }
    #drag_parent {
      position: relative;
      width:300px;
      height: 200px;
      background-color: beige;
    }
    #drag_dom {
      position: absolute;
      top: 0;
      left: 0;
      width: 100px;
      height: 80px;
      background-color:chocolate;
    }
</style>

成果展现

【每日编程】- JS实现元素拖拽

代码参考

  • GitLab: drag-dom-project/index.html main Vincent1900 / routine-program GitLab