不凡说 / 文章详情

JS 元素拖拽的那些事儿

2020-01-19 13:26 1337

拖拽行为在很多可视端都很常见,比如桌面拖拽应用图标,文件拖拽换位置等等。web 页面的拖拽行为也是初级前端开发者热衷掌握的技能,从以前根据元素的位置进行拖拽,到现在 web 标准出台新的的拖拽API规范,元素拖拽变得更加灵活。本文列举了旧版拖拽和新版规范拖拽的某些做法,希望能帮助到你。

旧有拖拽方式

一、拖拽API

Element.onmousedown:鼠标按下事件

window.onmousemove/document.onmousemove:鼠标在页面上的移动事件

Element.onmouseup:鼠标抬起事件

Element.offsetLeft/Top:返回元素与有定位的父类的上左边距,如果父类都没有定位,那么返回相对于页面的上左边距(本示例中页面内只有一个元素,故获取的值是元素相对于页面的距离)

event.pageX/Y:鼠标距离页面的上左边距

event.offsetX/Y:鼠标距离触发事件的元素的上左边距(本示例中只有一个元素,故获取的值为鼠标相对于盒子元素的上左边距)

二、拖拽原理

假设页面有一个 div 元素box,它从页面的左上方被做拖拽到了右下方,其中牵扯的逻辑有两种:

逻辑1、如下图所示:

image-20200117182455047.png

由上图我们可以看出:

  • 盒子被拖拽后与页面的距离 = 盒子起始与页面间的距离(已知) + 盒子运动的距离(未知)

  • 盒子运动的距离(蓝色标记) + 鼠标与盒子之间的距离(红色标记) = 鼠标移动的距离(橙色标记) + 鼠标与盒子之间的距离(红色标记)

    因为运动前后鼠标与盒子之间的距离保持不变,故 ===> 盒子运动的距离 = 鼠标移动的距离

  • 鼠标移动的距离 = 鼠标移动后鼠标的位置 - 鼠标移动前鼠标的位置

总结:盒子移动后的位置 = 盒子移动前的位置 + (鼠标移动后的位置 - 鼠标移动前的位置)

逻辑2、关系如下图:

image-20200118155923466.png

由上图我们可以看出:

  • 盒子移动前后 盒子与页面间的距离(蓝色标记) = 鼠标与页面间的距离(绿色) - 鼠标与盒子间的距离(未在图中标示) 这样的关系未发生变化
  • 在鼠标移动过程中我们可以获取鼠标与页面间的位置,那么我们只要获取开始拖拽是鼠标与盒子间的距离就可以求出盒子预见
  • 鼠标在盒子中的位置我们可以使用按下时 鼠标与页面间的距离 - 盒子与页面间的距离 求出,或者使用 event.offsetX/Y API

总结:盒子移动后的位置 = 鼠标移动后在页面的位置 - 鼠标在盒子中的位置

三、代码实现

/* css 样式 */
*{
    margin: 0;
    padding: 0;
}
.box{
    position: absolute; /* 添加绝对定位,让box相对于页面可移动 */
    top: 30px;
    left: 30px;
    width: 100px;
    height: 100px;
    background-color: red;
}
<!-- 页面代码 -->
<body>
    <!-- 添加盒子元素 -->
    <div class="box"></div>
</body>
/* 第一种逻辑实现 */
var box = document.querySelector(".box"); // 获取box元素

// 鼠标在元素上按下开始拖拽
box.onmousedown = function (){
    // 获取按下鼠标时 盒子与页面的距离
    var originBoxX = box.offsetLeft;
    var originBoxY = box.offsetTop;

    // 获取按下鼠标时 鼠标与页面的距离
    var mouseX = event.pageX;
    var mouseY = event.pageY;

    // 在页面上移动
    window.onmousemove = function (){
        // 鼠标滑动的距离 = 鼠标移动后的位置 - 按下鼠标时的位置
        var distanceX = event.pageX - mouseX;
        var distanceY = event.pageY - mouseY;
		
        // 给元素重新赋值 上左定位的位置
        box.style.left = originBoxX + distanceX + "px";
        box.style.top = originBoxY + distanceY + "px";
    }

    // 鼠标松开取消事件
    box.onmouseup = function (){
        // 解绑在页面上滚动的事件
        window.onmousemove = null;
    }
}
/* 第二种逻辑实现 */
var box = document.querySelector(".box"); // 获取box元素

// 鼠标在元素上按下开始拖拽
box.onmousedown = function (){
    // 利用公式求鼠标在元素的位置
    // var mouseX = event.pageX - box.offsetLeft;
    // var mouseY = event.pageY - box.offsetTop;

    // 获取鼠标在元素中的位置;当拖拽元素内部有元素的时候,不适用
    var mouseX = event.offsetX;
    var mouseY = event.offsetY;
	
    // 在页面上移动
    window.onmousemove = function (){
        // 元素的位置 = 鼠标与页面的距离 - 鼠标与元素的距离
        var targetX = event.pageX - mouseX;
        var targetY = event.pageY - mouseY;
		
        // 给元素重新赋值 上左定位的位置
        box.style.left = targetX + "px";
        box.style.top = targetY + "px";
    }
	
   	// 鼠标松开取消事件
    box.onmouseup = function (){
        // 解绑在页面上滚动的事件
        window.onmousemove = null;
    }
}

四、页面效果

普通拖拽.gif

新版拖拽

一、拖拽API

拖拽元素:页面中设置了draggable="true" 属性的元素,不能省略 = "true"
目标元素:页面中任何一个元素都可以成为目标元素

拖拽元素具有的方法

  • ondragstart 应用于拖拽元素,当拖拽开始时调用
  • ondragend 应用于拖拽元素,当拖拽结束时调用

二、代码实现

因为设置了拖拽属性的元素就可以被拖拽,结束拖拽的时候获取一下鼠标与页面的距离,减去鼠标与元素中的距离就行了,逻辑为旧有拖拽方式的第二种:盒子的位置 = 元素与页面间的距离 - 元素与盒子的距离

<style>
    *{
        margin: 0;
        padding: 0;
    }
    .box{
        position: absolute; /* 添加绝对定位,让box相对于页面可移动 */
        top: 30px;
        left: 30px;
        width: 100px;
        height: 100px;
        background-color: red;
    }
</style>
<body>
    <div class="box"></div>
    
    <script>
    	var box = document.querySelector(".box");
        // 开始拖拽
        box.ondragstart = function() {
            // 记录鼠标与盒子之间的距离
            mouse = {
                x: event.offsetX,
                y: event.offsetY
            }
        }
        // 拖拽结束
        box.ondragend = function() {
            // 盒子的位置 = 鼠标与页面之间的距离 - 鼠标与盒子之间的距离
            box.style.left = event.pageX - mouse.x + "px";
            box.style.top = event.pageY - mouse.y + "px";
        }
    </script>
</body>

三、页面效果

h5拖拽.gif