原生JavaScript與jQuery(絕對、相對定位)實現拖拽效果

原生js絕對定位實現拖拽

首先我們來思考一下拖拽功能用到的事件
拖拽無非是鼠標按下點擊物體(DOM節點)
鼠標移動,物體移動
鼠標抬起,物體停止拖拽
所以這裡我們需要綁定三個事件
mousedown、mousemove、mouseup
不過通過剛才我們所思考的
mousemove和mouseup事件觸發應該是由一個鼠標按下的前提
我們可以利用一個佈爾變量儲存狀態表示當前是否鼠標按下
或者幹脆在mousedown事件處理函數中去綁定mousemove、mouseup
那麼現在我們大腦裡有瞭這樣一個實現拖拽功能函數的藍圖
(主要是邏輯,綁定事件等等兼容問題我就不具體實現瞭b( ̄▽ ̄)d)

.demo {
    width: 100px;
    height: 100px;
    background-color: orangered;
    border-radius: 50%;
}
.demo {
    width: 100px;
    height: 100px;
    background-color: orangered;
    border-radius: 50%;
}

var demo = document.getElementsByTagName('p');
drag(demo[0]);
drag(demo[1]);
drag(demo[2]);

利用我們剛剛封裝的絕對定位拖拽函數
我鼠標按下第一個球(DOM節點)的一瞬間
第二個球和第三個球跑到瞭上面

這是因為點擊節點的一瞬間,它多瞭樣式position:absolute
絕對定位會使節點脫離當前的文本流
於是沒有經過拖拽的節點2和節點3就看不到節點1瞭
(瀏覽器發生瞭reflow重排)

不僅僅這這樣,如果這個元素原來是float元素,absolute定位後float也會失效
同樣會產生這樣的問題
那麼怎樣解決這種問題呢?
最開始我的想法是創建一個一模一樣的透明節點占位
光是想一想都覺得很麻煩,(然而我不想麻煩)
反過來想一想relative或許更合適
相對定位不同於絕對定位的一點就是它不會脫離正常的文本流
而且它的left和top是相對於自己原來位置的,而不是相對於離自己最近的定位父級元素
有瞭這些想法,我們來嘗試重構函數

function drag(ele){
    var oldX, oldY, newX, newY;
    ele.onmousedown = function(e){
        this.style.position = 'relative';//元素相對定位
        if(!this.style.left && !this.style.top){//第一次設置left、top為0
            this.style.left = 0;
            this.style.top = 0;
        }
        oldX = e.clientX;//記錄初始光標相對於視窗坐標
        oldY = e.clientY;
        document.onmousemove = function(e){
            newX = e.clientX;//獲取當前新光標相對於視窗坐標
            newY = e.clientY;
            ele.style.left = parseInt(ele.style.left) + newX - oldX + 'px';//更新
            ele.style.top = parseInt(ele.style.top) + newY - oldY + 'px';
            oldX = newX;//新坐標變為老坐標
            oldY = newY;
        }
        document.onmouseup = function(){
            document.onmousemove = null;
            document.onmouseup = null;
        }
    }
}

這段代碼我就不細說瞭,也很容易看懂
如果你還想寫的再豐滿一些,可以添加z-index屬性

jQuery絕對定位實現拖拽

上面解釋瞭很多,這裡我就不那麼囉嗦瞭
直接上代碼,道理是一樣的

$.fn.extend({
    drag: function(){
        this.on('mousedown',function(e){
            $(this).css('position','absolute');
            var disX = e.clientX - $(this).position().left,
                disY = e.clientY - $(this).position().top,
                $self = $(this);
            $(document).on('mousemove',function(e){
                $self.css('left',e.clientX - disX);
                $self.css('top',e.clientY - disY);
            })
            $(document).on('mouseup',function(){
                $(document).off();
            })
        });
    }
});
$('.demo').drag();

還記得我 jQuery框架常用的性能優化裡提到的擴展插件麼
這裡我利用瞭$.fn.extend()封裝瞭對象方法的插件
提到這裡多說一嘴,這裡drag方法內部的this是一個jQuery對象,而不是元素,與on()綁定的事件觸發函數內部不同,這裡要特別註意
但是同樣會產生上面所說的瀏覽器reflow重排的災難問題

jQuery相對定位實現拖拽

$.fn.extend({
    drag: function(){
        var oldX, oldY, newX, newY;
        this.on('mousedown',function(e){
            $(this).css('position','relative');
            oldX = e.clientX;
            oldY = e.clientY;
            var $self = $(this);
            $(document).on('mousemove',function(e){
                newX = e.clientX,
                newY = e.clientY;
                $self.css('left','+=' + (newX - oldX));
                $self.css('top','+=' + (newY - oldY));
                oldX = newX;
                oldY = newY;
            })
            $(document).on('mouseup',function(){
                $(document).off();
            })
        });
    }
});
$('.demo').drag();

我也同樣利用jQuery的相對定位實現瞭拖拽
話說jQuery這個off()方法倒是很方便,不填參數把document所有事件都解除瞭

發佈留言