JS進階篇4---原生JS實現對元素的拖拽

原生 JS 實現對 html 元素的拖拽

1、背景介紹

此處爲鋪墊內容,可跳過】 隨着 Web 前端的不斷髮展,各類各樣的前端規範和新知識、新技術層出不窮,極大地拓展了開發者的操做空間,也大大地提高了用戶體驗。而隨着移動端的不斷髮展,在移動端,人機交互方式發生了很大轉變,新的人機交互方式對提供給用戶的 「動做」 有了更高要求,例如拖拽功能,就是在移動端會常常接觸到的功能。此文就詳細地講解一下,一個簡單的拖拽功能的實現(拖拽元素可改變,瀏覽器窗口邊界檢測)。html

2、知識準備

爲了讓各位對此文內容有更深入的掌握,須要掌握以下知識。前端

一、clientXclientY 屬性瀏覽器

clientXclientY ) 事件屬性返回當事件被觸發時鼠標指針相對於瀏覽器當前可視窗口的水平(垂直)座標。
注意:不包括工具欄和滾動條

二、offsetTopoffsetLeft 屬性函數

offsetTop ( offsetLeft ),指的是子元素距離其父元素的上邊框(左邊框)的偏移量,在不一樣的瀏覽器中其值不一樣,且與父元素的 position 屬性( position: static; 除外)有關。在不一樣瀏覽以及不一樣 position 下的具體值,讀者能夠自行查閱相關資料,因其內容較多,就不展開論述了。

三、clientWidthclientHeight屬性工具

Element.clientWidthElement.clientHeight )屬性表示元素的內部寬度,以像素計。該屬性包括內邊距,但不包括垂直滾動條(若是有)、邊框和外邊距。該屬性值會被四捨五入爲一個整數。若是你須要一個小數值,可以使用 element.getBoundingClientRect()

四、setCapturereleaseCapture 方法this

MDN 對 SetCapture() 函數的說明爲:「該函數在屬於當前線程的指定窗口裏設置鼠標捕獲。一旦窗口捕獲了 鼠標,全部鼠標輸入都針對該窗口,不管光標是否在窗口的邊界內。同一時刻只能有一個窗口捕獲鼠標。若是鼠標光標在另外一個線程建立的窗口上,只有當鼠標鍵按下時系統纔將鼠標輸入指向指定的窗口。」線程

通俗來說,舉個栗子:一隻羊被一根有彈性的繩子( SetCapture )拴在木樁,羊能夠在繩子能夠延展的範圍內 隨意活動,但永遠沒法擺脫繩子的束縛。除非有其餘因素致使繩子斷了(使用了 ReleaseCapture 或點擊了其餘窗口)。指針

ReleaseCapture() 用來釋放鼠標捕獲,當再也不須要繼續得到鼠標消息就要應該調用 ReleaseCapture() 釋放掉,不然別的線程想捕獲鼠標事件就會失敗。注意:SetCapture()ReleaseCapture() 必須成對出現。code

3、實現思路

若是想對元素進行拖拽,那麼必須使用三個事件,而且這三個事件的使用順序不能打亂。htm

一、onmousedown:鼠標按下事件
二、onmousemove:鼠標移動事件
三、onmouseup:鼠標擡起事件

拖拽的基本原理就是根據鼠標的移動來移動被拖拽的元素。鼠標的移動也就是 x、y 座標的變化;元素的移動就是元素 position 屬性的 topleft 值的改變。固然,並非任什麼時候候移動鼠標都要形成元素的移動,而應該判斷鼠標左鍵的狀態是否爲按下狀態,是不是在可拖拽的元素上按下的。具體過程以下:

拖拽狀態 = false

鼠標在元素上按下以後 {
    拖拽狀態 = true
    設置鼠標捕獲 
    記錄下鼠標的 x,y 座標
    記錄下元素的 x,y 座標
}

鼠標在元素上移動時 {
    若拖拽狀態爲 false 就什麼也不作
    若是拖拽狀態是 true,那麼
        元素的 y 座標 = 如今鼠標 y - 原來鼠標 y + 原來元素 y
        元素的 x 座標 = 如今鼠標 x - 原來鼠標 x + 原來元素 x
}

鼠標擡起時 {
    拖拽狀態 = false
}

4、完整源碼

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>原生JS實現元素拖拽</title>
    <style>
        *{
            margin: 0;
            height: 0;
        }
        #dragDiv{
            top: 0;
            left: 0;
            width: 100px;
            height: 100px;
            cursor: move;    /*鼠標呈拖拽狀*/  
            position: absolute;    /*設置絕對定位,脫離文檔流,便於拖拽時計算座標*/  
            background-color: red;
        }
    </style>
</head>

<body>
    
<div id="dragDiv"></div>
    
<script>

    window.onload = function(){
    
        var oDiv = document.getElementById("dragDiv");    // 獲取到要拖拽的元素
        drag(oDiv);    // 調用本身封裝的拖拽函數
        
        function drag(obj){
            obj.onmousedown = function(e){
            
                var e = e || window.event;    // 兼容 IE
                // 鼠標點擊物體那一刻相對於物體左側邊框的距離=點擊時的位置相對於瀏覽器
                // 最左邊的距離-物體左邊框相對於瀏覽器最左邊的距離,縱向同理
                var divX = e.clientX - this.offsetLeft;
                var divY = e.clientY - this.offsetTop;
                
                if(obj.setCapture){
                    obj.setCapture();    // 修復低版本 IE bug
                }
                
                document.onmousemove = function(e){
                
                    var e = e || window.event;
                    
                    var disX = e.clientX - divX;
                    var disY = e.clientY - divY;
                    
                    // 控制拖拽物體的範圍只能在瀏覽器視窗內,不容許出現滾動條或拖出可視區域
                    if ( disX < 0 ) {
                        disX = 0;
                    } else if ( disX > document.documentElement.clientWidth - obj.offsetWidth ) {
                        disX = document.documentElement.clientWidth - obj.offsetWidth;
                    }
                    
                    if ( disY < 0 ) {
                        disY = 0;
                    } else if ( disY > document.documentElement.clientHeight - obj.offsetHeight ) {
                        disY = document.documentElement.clientHeight - obj.offsetHeight;
                    }
                    
                    // 移動時從新獲得物體的距離,解決拖動時出現晃動現象  
                    obj.style.top = disY + "px";
                    obj.style.left = disX + "px";
                    
                    document.onmouseup = function(){    // 鼠標擡起時再也不移動  
                        // 預防鼠標彈起來後還會循環(即預防鼠標放上去的時候還會移動)
                        document.onmousedown = document.onmousemove = null;
                        if( obj.releaseCapture ){
                            obj.releaseCapture();    // 修復低版本 IE bug
                        }
                    }
                }
            }
        }
    }

</script>

</body>
</html>

注意事項
一、onmousedown 事件中的操做對象爲拖拽元素,而 onmousemoveonmouseup 事件中的操做對象爲 document,這是由於,點擊某物體時,用須要拖拽的對象便可,onmousemoveonmouseup 是全局區域,也就是整個文檔通用,應該使用 document 對象而不是被拖拽的對象(不然,採用拖拽對象時物體只能往右方或下方移動)

二、之因此使用 setCapture()releaseCapture(),其目的是爲了修復低版本 IE 的 bug。在低版本 IE 下,當咱們在要拖動的元素上,按下鼠標按鈕拖動時,當拖動過快,或者是超出瀏覽器的文檔窗口時,拖動對象身上的 onmousedown 事件就會失效。在 Chrome 咱們能夠爲 doucment 綁定 onmouseout 事件來判斷是否發生這樣的狀況,可是 IE 下卻行不通,因此最好的解決辦法就時爲要拖動的元素對象鎖定鼠標事件,在拖動後再解除事件鎖定。在本例中,這兩個方法用於 onmousedownonmouseup 中。

三、另外,在 Firefox 中有類似的功能,它們分別是:

  • captureEvents ( Event.eventType )
  • releaseEvents ( Event.eventType )

5、案例總結

雖然元素的拖拽算是一個比較基礎的知識點,但在實現的過程當中,有許多細節須要注意,例如計算座標的時候,對那幾個屬性的瞭解程度,再例如,事件的觸發順序,還有,IE 中的事件獲取, setCapture()releaseCapture() 等。

雖然 H5 直接提供了拖拽 API,但爲了兼容性,小夥伴們仍是須要用 js 去處理的。上例中,雖然對拖拽作了必定的兼容性處理和封裝,拖拽對象能夠是 div,圖片,文字等,但總的來講,是一個比較基礎的實現,但有了這個原型,小夥伴們能夠根據本身的需求,再加以封裝和拓展,例如限定拖拽方向、範圍、速度等等。有任何疑問或建議,能夠在評論區留言哦,轉載請註明出處。

相關文章
相關標籤/搜索