彻底搞懂拖拽——基于鼠标事件的拖拽以及基于HTML5 API的拖拽完整实现

一、基于鼠标事件的拖拽

原理——onmousedown、onmousemove、onmouseup

  1. onmousedown
  • 该事件会在鼠标按键被按下时触发
  • 支持该事件的HTML标签:
   <a>, <address>, <area>, <b>, <bdo>, <big>, <blockquote>, <body>, <button>,
   <caption>, <cite>, <code>, <dd>, <dfn>, <div>, <dl>, <dt>, <em>, <fieldset>,  
   <form>, <h1> to <h6>, <hr>, <i>, <img>, <input>, <kbd>, <label>, <legend>,  
   <li>, <map>, <ol>, <p>, <pre>, <samp>, <select>, <small>, <span>, <strong>,  
   <sub>, <sup>, <table>, <tbody>, <td>, <textarea>, <tfoot>, <th>, <thead>,  
   <tr>, <tt>, <ul>, <var>
  • 支持该事件的JavaScript对象:

    button, document, link

  1. onmousemove
  • 该事件会在鼠标指针移动时触发
  • 支持该事件的HTML标签:
   <a>, <address>, <area>, <b>, <bdo>, <big>, <blockquote>, <body>, <button>,  
   <caption>, <cite>, <code>, <dd>, <dfn>, <div>, <dl>, <dt>, <em>, <fieldset>,  
   <form>, <h1> to <h6>, <hr>, <i>, <img>, <input>, <kbd>, <label>, <legend>,  
   <li>, <map>, <ol>, <p>, <pre>, <samp>, <select>, <small>, <span>, <strong>,  
   <sub>, <sup>, <table>, <tbody>, <td>, <textarea>, <tfoot>, <th>, <thead>,  
   <tr>, <tt>, <ul>, <var>
  • 支持该事件的JavaScript对象:

    默认情况下,onmousemove不是任何对象的事件,因为鼠标移动非常频繁

  1. onmouseup
  • 该事件会在鼠标按键被松开时触发
  • 支持该事件的HTML标签:
   <a>, <address>, <area>, <b>, <bdo>, <big>, <blockquote>, <body>, <button>,  
   <caption>, <cite>, <code>, <dd>, <dfn>, <div>, <dl>, <dt>, <em>, <fieldset>,  
   <form>, <h1> to <h6>, <hr>, <i>, <img>, <input>, <kbd>, <label>, <legend>,  
   <li>, <map>, <ol>, <p>, <pre>, <samp>, <select>, <small>, <span>, <strong>,  
   <sub>, <sup>, <table>, <tbody>, <td>, <textarea>, <tfoot>, <th>, <thead>,  
   <tr>, <tt>, <ul>, <var>
  • 支持该事件的JavaScript对象:

    button, document, link


具体实现

code:

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
    <style>
        #test {
            width: 100px;
            height: 100px;
            background: #000;
            position: absolute;
            color: #fff;
        }
    </style>
</head>

<body>
    <div id="test">4616125</div>
    <script>
        (function() {
            function Code() {}
            Code.prototype = {
                addEvent: function() {
                    var that = this;
                    var oDiv = document.getElementById('test');
                    oDiv.onmousedown = function(ev) {
                        var ev = ev || event;
                        var distanceX = ev.clientX - this.offsetLeft;
                        var distanceY = ev.clientY - this.offsetTop;
                        if (oDiv.setCapture) {
                            oDiv.setCapture();
                        }
                        document.onmousemove = function(ev) {
                            var ev = ev || event;
                            oDiv.style.left = ev.clientX - distanceX + 'px';
                            oDiv.style.top = ev.clientY - distanceY + 'px';
                        };
                        document.onmouseup = function(ev) {
                            document.onmousemove = document.onmouseup = null;
                            if (oDiv.releaseCapture) {
                                oDiv.releaseCapture();
                            }
                        };
                    };
                },

                init: function() {
                    var that = this;
                    window.onload = that.addEvent;
                },
            }
            new Code().init();
        })();
    </script>
</body>

</html>

注意事项以及存在的问题:

  1. 注意事项
  • 被拖动的div的position属性值一定是absolute
  • onmousedown事件需要在window.onload时加载
  • 如果被拖动的div上有文字会有自带的文字拖动效果,需要将改div上的所有拖动事件绑定在该div上,可以使用setCapture
  • onmousemove和onmouseup需要在onmousedown里面绑定
  1. 存在的问题
  • 会被拖出边界

超出边界

解决方案

  • 只需要实时计算拖拽的元素边框距离上下左右屏幕之间的距离就行了,具体代码如下:

code:

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
    <style>
        * {
            margin: 0;
            padding: 0;
        }
        #oDiv {
            width: 100px;
            height: 100px;
            background-color: #000;
            position: absolute;
        }
    </style>
</head>

<body>
    55555555555
    <div id="oDiv"></div>
    <script>
        oDiv.onmousedown = function(e) {
            var ev = e || event;
            var left = ev.clientX - oDiv.offsetLeft,
                top = ev.clientY - oDiv.offsetTop;
            document.onmousemove = function(e) {
                var ev = e || event;
                var leftW = ev.clientX - left;
                var topH = ev.clientY - top;
                //左边不能超出
                if (leftW < 0) {
                    leftW = 0;
                }
                //上边不能超出
                if (topH < 0) {
                    topH = 0;
                }
                //右边不能超出
                if (leftW > document.documentElement.clientWidth - oDiv.offsetWidth) {
                    leftW = document.documentElement.clientWidth - oDiv.offsetWidth;
                }
                //下边不能超出
                if (topH > document.documentElement.clientHeight - oDiv.offsetHeight) {
                    topH = document.documentElement.clientHeight - oDiv.offsetHeight;
                }
                oDiv.style.left = leftW + 'px';
                oDiv.style.top = topH + 'px';
            }
            document.onmouseup = function(e) {
                document.onmousemove = null;
                document.onmouseup = null;
            }
            return false;
        }
    </script>
</body>

</html>

result:
不能拖出边界

至此使用鼠标事件的拖拽大功告成!


二、基于HTML5拖拽API的拖拽

前序知识介绍

  一个典型的拖拽操作是这样的:用户用鼠标选中一个可拖动的(draggable)元素,移动鼠标到一个可放置的(droppable)元素,然后释放鼠标。 在操作期间,会触发一些事件类型,有一些事件类型可能会被多次触发(比如drag 和 dragover 事件类型)。
  这里涉及几个知识点:

  1. 可拖动元素:

    又称为源对象,是指我们鼠标点击之后准备拖动的对象(图片、div、文字等)

  2. 可放置元素:

    又称为目标对象,是指可以放置源对象的区域

  3. 事件:

EventOn Event HandlerDescription
dragondrag当拖动元素或选中的文本时触发
dragendondragend当拖拽操作结束时触发 (比如松开鼠标按键或敲“Esc”键)
dragenterondragenter当拖动元素或选中的文本到一个可释放目标时触发
dragexitondragexit当元素变得不再是拖动操作的选中目标时触发
dragleaveondragleave当拖动元素或选中的文本离开一个可释放目标时触发
dragoverondragover当元素或选中的文本被拖到一个可释放目标上时触发
dragstartondragstart当用户开始拖动一个元素或选中的文本时触发
dropondrop当元素或选中的文本在可释放目标上被释放时触发

ps:当从操作系统向浏览器中拖动文件时,不会触发dragstart 和dragend 事件

  1. 接口:

HTML5为所有的拖动相关事件提供了一个新的属性:

  • 源对象和目标对象的事件间传递数据

    ev.dataTransfer {}//数据传递对象

  • 源对象上的事件处理中保存数据:

    ev.dataTransfer.setData(key,value);//key,value必须都是字符串类型

  • 目标对象上的事件处理中读取数据:

    var value2 = ev.dataTransfer.getData(key);

  1. 兼容性

兼容性
ps:图片来源(CAN I USE?


具体实现代码

code:

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
    <style type="text/css">
        #thatDiv {
            width: 500px;
            height: 100px;
            border: 1px solid red;
            position: relative;
        }
        #thisDiv {
            width: 500px;
            height: 100px;
            border: 1px solid black;
            margin-bottom: 20px;
        }
        #tarDiv,
        #tarDiv1,
        #tarDiv2,
        #tarDiv3,
        #tarDiv4 {
            float: left;
            width: 50px;
            height: 50px;
            background-color: #000;
            border: 1px #fff solid;
        }
        .tarDiv {
            color: #fff;
            text-align: center;
            line-height: 50px;
        }
    </style>
</head>

<body>
    <div id="thisDiv">
        <div id="tarDiv" class="tarDiv" draggable="true">1</div>
        <div id="tarDiv1" class="tarDiv" draggable="true">2</div>
        <div id="tarDiv2" class="tarDiv" draggable="true">3</div>
        <div id="tarDiv3" class="tarDiv" draggable="true">4</div>
        <div id="tarDiv4" class="tarDiv" draggable="true">5</div>
    </div>
    <div id="thatDiv"></div>

    <script type="text/javascript">
        var tarDiv = document.getElementsByClassName("tarDiv");
        var thisDiv = document.getElementById("thisDiv");
        var thatDiv = document.getElementById("thatDiv");
        thisDiv.ondragstart = function(ev) {
            var ev = ev || window.event;
            ev.dataTransfer.setData("text", ev.target.id); //将被拖拽的元素的id存入dataTransfer对象中
            window.thisId = ev.target.id;
            ev.dataTransfer.effectAllowed = "copy";
        }
        thatDiv.ondragover = function(ev) { //阻止dragover的默认事件
            var ev = ev || window.event;
            if (typeof ev.preventDefault == "function") {
                ev.preventDefault();
            } else {
                ev.returnValue = false;
            }
            var div = document.getElementById(window.thisId);
            thatDiv.appendChild(div);
            div.style.cssText = "border:1px #fff dashed;";

            ev.preventDefault();
            ev.dataTransfer.dropEffect = "copy";
        }
        thatDiv.ondragenter = function(ev) { //阻止dragenter的默认事件
            var ev = ev || window.event;
            if (typeof ev.preventDefault == "function") {
                ev.preventDefault();
            } else {
                ev.returnValue = false;
            }
        }
        thatDiv.ondragleave = function(ev) {
            var ev = ev || window.event;
            var removeDiv = document.getElementById(window.thisId);
            thatDiv.removeChild(removeDiv);
            thisDiv.appendChild(removeDiv);
            removeDiv.style.cssText = "border:1px #fff solid;";
            ev.preventDefault();
        }
        thatDiv.ondrop = function(ev) {
            var ev = ev || window.event;
            var divId = ev.dataTransfer.getData("Text"); //从dataTransfer对象中取出数据
            if (typeof ev.preventDefault == "function") { //阻止drop事件的默认行为
                ev.preventDefault();
            } else {
                ev.returnValue = false;
            }
            var moveDiv = document.getElementById(divId);
            thatDiv.appendChild(moveDiv);
            moveDiv.setAttribute('draggable', 'false');
            moveDiv.style.cssText = "border:1px #fff solid;";

        }
    </script>
</body>

</html>

result:

拖拽展示


参考资料

新博客地址:http://www.caomage.com

  • 9
    点赞
  • 43
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
好的,这个问题我可以回答。以下是一个简单的HTML和JavaScript实现图片放大缩小和拖拽的示例: ```html <!DOCTYPE html> <html> <head> <title>Image Zoom and Drag</title> <style> #image-container { position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); border: 1px solid black; overflow: hidden; cursor: move; } #image-container img { position: absolute; top: 0; left: 0; width: 100%; height: 100%; } </style> </head> <body> <div id="image-container"> <img src="https://picsum.photos/id/237/300/300" id="image"> </div> <script> var image = document.getElementById("image"); var imageContainer = document.getElementById("image-container"); var isDragging = false; var lastX, lastY; imageContainer.addEventListener("mousedown", function(e) { isDragging = true; lastX = e.clientX; lastY = e.clientY; }); imageContainer.addEventListener("mouseup", function(e) { isDragging = false; }); imageContainer.addEventListener("mousemove", function(e) { if (isDragging) { var deltaX = e.clientX - lastX; var deltaY = e.clientY - lastY; var newTop = image.offsetTop + deltaY; var newLeft = image.offsetLeft + deltaX; image.style.top = newTop + "px"; image.style.left = newLeft + "px"; lastX = e.clientX; lastY = e.clientY; } }); imageContainer.addEventListener("wheel", function(e) { e.preventDefault(); var scale = e.deltaY > 0 ? 0.9 : 1.1; var newWidth = image.clientWidth * scale; var newHeight = image.clientHeight * scale; image.style.width = newWidth + "px"; image.style.height = newHeight + "px"; }); </script> </body> </html> ``` 这个示例中,我们首先创建了一个容器 `image-container`,然后在容器中放置了一个图片 `image`。我们使用 CSS 来将容器居中并添加了一些样式,例如设置容器为相对定位、边框、隐藏溢出部分和鼠标指针为移动。 然后我们使用 JavaScript 来添加拖拽和缩放的功能。我们使用了三个变量 `isDragging`、`lastX` 和 `lastY` 来跟踪拖拽状态和鼠标位置。当用户按下鼠标按钮时,我们设置 `isDragging` 为 `true` 并记录下当前鼠标位置。当用户释放鼠标按钮时,我们设置 `isDragging` 为 `false`。当用户移动鼠标时,我们检查 `isDragging` 是否为 `true`,如果是,就计算鼠标移动的距离,并将图片的位置相应地调整。这样就实现拖拽的功能。 最后,我们添加了一个滚轮事件监听器来实现缩放功能。当用户滚动鼠标滚轮时,我们阻止默认行为,然后根据滚轮的方向计算缩放比例,并将图片的宽度和高度相应地调整。 希望这个示例能够帮助你实现所需的功能。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值