1. 程式人生 > >徹底搞懂拖拽——基於滑鼠事件的拖拽以及基於HTML5 API的拖拽完整實現

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

具體實現程式碼

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