跳到主要內容

範例 - JavaScript 實作可拖曳排序清單(sortable list)

這篇文章接續拖曳物件的教學
利用之前寫好的架構再接續較複雜的行為
示範的目標是一水平排列的清單內容,並做到藉由拖曳清單項目改變其排列順序
首先準備好兩種CSS,分別給一般狀態以及拖曳中狀態下的清單項目使用
.undrag {
    position:relative;
    z-index: 1;
    opacity: 1;
    filter: alpha(opacity=100);
    top: 0px;
    left: 0px;
    cursor: pointer;
}
.drag {
    z-index: 100;
    position:absolute;
    opacity: .50;
    filter: alpha(opacity=50);
    cursor: move;
}

要撰寫使用的callback function需先設置幾個全域變數
sourceList 即是來源清單,因清單中除了要套上拖曳行為的標籤外還有其餘元素
所以在額外設置了 sourceLinks 暫存那些標籤,用來建立dragObject
placeHolder為一跟被拖曳的物件同等大小、用來佔位置的物件
placeHolder的class不套用drag/undrag,其餘同一般標籤
最後拖曳行為結束後會將被拖曳的元素放到 placeHolder 所佔的位置上
undragLinks 存放的元素較 sourceLinks 少了正在被拖曳中的元素,以方便實作排序
dragArray儲存拖曳物件,最後須要靠它解除拖曳的設置

var sourceList, placeHolder, sourceLinks, undragLinks, dragArray;
function sortTabs() {
    if (dragArray) return;

    sourceList = document.getElementById("topTabs");
    sourceLinks = document.getElementsByClassName("undrag");
    dragArray = [];
    placeHolder = document.createElement("DIV");

    placeHolder.className = "Tab";
    placeHolder.style.backgroundColor = "rgb(225,225,225)";
    for (var i = 0; i < sourceLinks.length; i++) {
        dragArray.push(new dragObject(sourceLinks[i], itemDragBegin, itemMoved, itemDragEnd));
    }
}

function itemDragBegin(e, element) {
    element.style.top = element.offsetTop + 'px';
    element.style.left = element.offsetLeft + 'px';
    element.className = element.className.replace("undrag", "drag");

    undragLinks = document.getElementsByClassName("undrag");
    sourceList.insertBefore(placeHolder, element);
    placeHolder.innerHTML = element.innerHTML;
    placeHolder.style.visibility = "hidden";

    return {
        orientation: "horizontal",
        upperLimit: sourceList.getBoundingClientRect().right,
        lowerLimit: sourceList.getBoundingClientRect().left
    };
}

在move callback function實作排序
因這次是排序水平標籤,所以設定當拖曳的物件移動的範圍到前、後物件一半的寬度就交換位置

function itemMoved(e, element) {
    e = e || window.event;
    var elementPos = element.getBoundingClientRect();
    var elementLeft = elementPos.left;
    var elementRight = elementPos.right;
    var placeHolderPos = placeHolder.getBoundingClientRect();
    var moveRight = elementLeft >= placeHolderPos.left;
    var itemPos;
    for (var i = 0; i < undragLinks.length; i++) {
        itemPos = undragLinks[i].getBoundingClientRect();

        if (moveRight) {
            if(itemPos.right <= placeHolderPos.left) continue;
            if(elementRight > itemPos.left + undragLinks[i].offsetWidth * 0.5) {
                sourceList.insertBefore(undragLinks[i], placeHolder);
                break;
            }
        } else {
            if(itemPos.right - undragLinks[i].offsetWidth * 0.5 > elementLeft) {
                sourceList.insertBefore(placeHolder, undragLinks[i]);
                break;
            }
        }
    }

    //prevent cursor move too fast that move callback function is not invoked as much times as it should be
    var maxScrollLeft = sourceList.scrollWidth - sourceList.clientWidth;
 
    if (elementRight > $("sideCreateNewApp").getBoundingClientRect().left) {
        topTabsScrollRight();
        if(sourceList.scrollLeft == maxScrollLeft) sourceList.appendChild(placeHolder);
    } else if (elementLeft - element.offsetWidth * 0.5 < sourceList.getBoundingClientRect().left) {
        topTabsScrollLeft();
        if(sourceList.scrollLeft == 0) sourceList.insertBefore(placeHolder, undragLinks[0]);
    }
}

function itemDragEnd(e, element) {
    sourceList.replaceChild(element, placeHolder);

    element.className = element.className.replace("drag", "undrag");
    element.style.top = '0px';
    element.style.left = '0px';
}


用完之後的清理動作
for(var i = 0; i < dragArray.length; i++){
dragArray[i].dispose();
}
dragArray = null;

留言