跳到主要內容

範例 - JavaScript 實作可拖曳物件

實作成果示範


物件拖曳的重點在於物件位置的計算公式 - 目前滑鼠位置-起始滑鼠位置+目標位置
單純拖曳物件的話不算困難
說的上比較困難的是拖曳之後要結合的動作(如:drop、sortable list等)
雖然HTML5有Drag相關的API,不過這邊就先不提了
先做出一個能相容於老舊瀏覽器(IE)的版本
GitHub檔案目錄

首先實作一個專門用來記錄位置的類別
物件的函式結果回傳位置本身,這樣寫主要是方便連綴的使用方法

function Position(x, y) {
    this.X = x ? x : 0;
    this.Y = y ? y : 0;

    this.add = function (val) {
        if (val) {
            if (!isNaN(val.X)) this.X += val.X;
            if (!isNaN(val.Y)) this.Y += val.Y;
        }
        return this;
    };

    this.subtract = function (val) {
        if (val) {
            if (!isNaN(val.X)) this.X -= val.X;
            if (!isNaN(val.Y)) this.Y -= val.Y;
        }
        return this;
    };

    this.min = function (val) {
        if (!val) return this;
        if (!isNaN(val.X)) this.X = Math.min(this.X, val.X);
        if (!isNaN(val.Y)) this.Y = Math.min(this.Y, val.Y);
        return this;
    };

    this.max = function (val) {
        if (!val) return this;
        if (!isNaN(val.X)) this.X = Math.max(this.X, val.X);
        if (!isNaN(val.Y)) this.Y = Math.max(this.Y, val.Y);

        return this;
    };

    this.apply = function (element, control) {
       if (!element) return;
       if (!isNaN(this.X)) {
         if (!control || !(control.orientation != 'horizontal' || (control.upperLimit && this.X > control.upperLimit)
                          || (control.lowerLimit && this.X < control.lowerLimit)))
             element.style.left = this.X + 'px';
       }
       if (!isNaN(this.Y)) {
         if (!control || !(control.orientation != 'vertical' || (control.upperLimit && this.Y > control.upperLimit)
                          || (control.lowerLimit && this.Y < control.lowerLimit)))
             element.style.top = this.Y + 'px';
       }
    };
}

接著撰寫一個取得現在滑鼠位置的method

function absoluteCursorPosition(e) {
    e = e || window.event;
    if (isNaN(window.scrollX))
        return new Position(e.clientX + document.documentElement.scrollLeft + document.body.scrollLeft,
                                      e.clientY + document.documentElement.scrollTop + document.body.scrollTop);
    else
        return new Position(e.clientX + window.scrollX, e.clientY + window.scrollY);
}

最後重點來了,實作拖曳物件的類別
裡頭包含了紀錄之前滑鼠位置與物件起始位置的類別成員
使用dragging當指標控管拖曳行為的發生
避免因滑鼠移動到別的拖曳物件上造成預期外的行為
另提供3個使用callback function的機會,如果要設計拖曳後的行為便是利用這裡設計

稍微注意一下 dragGo 事件是綁在document上
這是要避免滑鼠移太快而讓物件自身的 onmousemove 事件沒有繼續被觸發

function dragObject(element, startCallback, moveCallback, endCallback) {
    if (!element) return;

    var cursorStartPos, elementClientRect, elementStartPos;
    var dragging = false;
    var control;

    if (document.addEventListener) element.addEventListener("mousedown", dragStart, false);
    else element.attachEvent("onmousedown", dragStart);

    function dragStart(e) {
        e = e || window.event;
        if ((e.which && e.which != 1) || (e.button && e.button != 1)) return; //only allow mouse left key

        if (dragging) return;
        dragging = true;
        if (startCallback) control = startCallback(e, element);

        cursorStartPos = absoluteCursorPosition(e);
        elementClientRect = element.getBoundingClientRect();
        elementStartPos = new Position(elementClientRect.left - element.parentNode.scrollLeft, parseInt(element.style.top));
        if (document.addEventListener) {
            document.addEventListener("mousemove", dragGo, false);
            document.addEventListener("mouseup", dragStop, false);
        } else {
            document.attachEvent("onmousemove", dragGo);
            document.attachEvent("onmouseup", dragStop);
        }
    }

    function dragGo(e) {
        if (!dragging) return;
        absoluteCursorPosition(e).add(elementStartPos).subtract(cursorStartPos).apply(element, control);
        if (moveCallback) moveCallback(e, element);
    }

    function dragStop(e) {
        if (!dragging) return;
        dragging = false;
        cursorStartPos = null;
        elementStartPos = null;
        if (endCallback) endCallback(e, element);

        if (document.removeEventListener) {
            document.removeEventListener("mousemove", dragGo, false);
            document.removeEventListener("mouseup", dragStop, false);
        } else {
            document.detachEvent("onmousemove", dragGo);
            document.detachEvent("onmouseup", dragStop);
        }
    }

    this.dispose = function () {
        if (element.removeEventListener) element.removeEventListener("mousedown", dragStart, false);
        else element.detachEvent("onmousedown", dragStart);
    };
}


完整程式範例請至這裡
要取消已設定可拖曳的元件的拖曳行為,可以再呼叫dispose

//after drag action is over, remove it
dothis.dispose();

實務上也常搭配 z-index 使用,請視情況自行設計

留言