徹底搞懂拖拽——基於鼠標事件的拖拽以及基於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. 事件:

Event

On Event Handler

Description

drag

ondrag

當拖動元素或選中的文本時觸發

dragend

ondragend

當拖拽操作結束時觸發 (比如鬆開鼠標按鍵或敲“Esc”鍵)

dragenter

ondragenter

當拖動元素或選中的文本到一個可釋放目標時觸發

dragexit

ondragexit

當元素變得不再是拖動操作的選中目標時觸發

dragleave

ondragleave

當拖動元素或選中的文本離開一個可釋放目標時觸發

dragover

ondragover

當元素或選中的文本被拖到一個可釋放目標上時觸發

dragstart

ondragstart

當用戶開始拖動一個元素或選中的文本時觸發

drop

ondrop

當元素或選中的文本在可釋放目標上被釋放時觸發

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:


參考資料

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章