07#Web 實戰:實現 GitHub 個人主頁專案拖拽排序

Enziandom發表於2022-11-28

實現效果圖

image

GitHub 和 Gitee 個人主頁中可以對自己的專案進行拖拽排序,於是我就想自己實現一個。本隨筆只是記錄一下大概的實現思路,如果感興趣的小夥伴可以透過程式碼和本隨筆的說明去理解實現過程。?我的 GiteeGitHub 地址。

線上瀏覽地址:11.拖拽排序,裡面還有更多的例子。

思路構思

要實現元素拖拽可替換位置,就必須要鎖定每一個元素的具體位置,且要直到兩個元素的 transform: translate()。從程式碼上看,這是一個二維陣列。從介面上看,就是一個網格佈局。06#Web 實戰:實現可滑動的標籤頁是透過一維陣列實現的。

元素的佈局不可能透過display: grid來進行,得用transform: translate(),實現元素得平移,且需要使用絕對和相對定位。

靜態介面程式碼

這裡給出初始的靜態介面程式碼,draggable 表示開啟這個元素的可拖拽功能:

<div class="drop-box">
  <div class="drag-item item-0">
    <div class="ontology" draggable="true">Item 0</div>
  </div>
  <div class="drag-item item-1">
    <div class="ontology" draggable="true">Item 1</div>
  </div>
  <div class="drag-item item-2">
    <div class="ontology" draggable="true">Item 2</div>
  </div>
  <div class="drag-item item-3">
    <div class="ontology" draggable="true">Item 3</div>
  </div>
</div>

老樣子,我喜歡把不必要的程式碼給省略掉,如果樣式不全,去我的倉庫複製:

.drop-box {
  transition: all 0.5s ease-in-out;
  box-sizing: border-box;
  /* 在這裡設定 drop-box 的高寬 */
  width: 420px;
  height: 300px;
  /* 在這裡設定 drop-box 的高寬 */
  border-radius: 10px;
  border: 1px solid #cccccc;
  position: relative;
}

.drag-item {
  transition: all 0.5s ease-in-out;
  box-sizing: border-box;
  border-radius: 10px;
  border: 1px solid #cccccc;
  width: 200px;
  height: 50%;
  position: absolute;
  top: 0;
  left: 0;
}

.drag-item > div.ontology {
  width: 100%;
  height: 100%;
}

構建二維陣列

拖拽每一個元素不代表真實地改變了 DOM 所在的位置。給這些元素設定監聽器,並獲取 index,拖拽之後都不會影響它的索引值。

上面給的 HTML 結構,在介面上生成之後,從 1 ~ 4 這樣的序列是不會改變的,即便是我們修改了它的 translate(平移元素)之後,也不會影響它原本在 DOM 樹上的順序。

為了方便在程式碼中修改元素的transform: translate(),我們需要在頁面載入時就虛擬化這些元素到二維陣列中。元素虛擬化進二維陣列的目的是讓程式設計更加易於使用。

let virtualGridElem = [];

function initVirtualGrid(elem, init) {
  let elemIndex = 0;
  for (let rowIndex = 0; rowIndex < init.rowNum; rowIndex++) {
    virtualGridElem[rowIndex] = [];
    for (let colIndex = 0; colIndex < init.colNum; colIndex++) {
      $(elem[elemIndex]).attr("data-row-index", rowIndex);
      $(elem[elemIndex]).attr("data-col-index", colIndex);
      $(elem[elemIndex]).css({ width: init.width, height: init.height, transform: gridVals[rowIndex][colIndex] });
      initEvents(elem[elemIndex], elemIndex, rowIndex, colIndex);
      virtualGridElem[rowIndex][colIndex] = elem[elemIndex++];
    }
  }
}

在虛擬化之前,需要獲得這個介面中的網格資訊,即網格有多少行,每一行有多少列。

let gridVals = [];

function initGridVals(elNum, colNum, rowMaxWidth, colMaxWidth) {
  let rowNum = Math.ceil(elNum / colNum);
  let widthPerRow = rowMaxWidth / colNum;
  let heightPerCol = colMaxWidth / rowNum;

  let translateX = 0;
  for (let rowIndex = 0; rowIndex < rowNum; rowIndex++) {
    let translateY = 0;
    gridVals[rowIndex] = [];
    for (let colIndex = 0; colIndex < colNum; colIndex++) {
      gridVals[rowIndex][colIndex] = `translate(${translateY}px, ${translateX}px)`;
      translateY += widthPerRow;
    }
    translateX += heightPerCol;
  }

  return {
    width: widthPerRow,
    height: heightPerCol,
    rowNum: rowNum,
    colNum: colNum
  };
}

到目前為止,得到了兩個重要的二維陣列:virtualGridElem 和 gridVals。virtualGridElem 不會被改變,一直保持原有的位置,與實際的可拖拽元素的 DOM 樹保持一致。gridVals 會與 virtualGridElem 發生出入,會根據操作而修改。

let dragItem = $(".drop-box").children(".drag-item");

initVirtualGrid(dragItem, initGridVals($(dragItem).length, 2, 420, 300));

拖拽排序功能

拖拽在 HTML5 就已經存在,drop、dragover、dragstart、dragend 都是實現本案例中最重要的幾個監聽事件。其中 drop 表示可拖拽元素到目標元素之後的元素,即 item1 拖拽到 item2 之後,獲取 item2 的元素。

function initEvents(elem, index, rowIndex, colIndex) {
  // drop 是獲取拖拽目標元素
  $(elem).on("drop", e => {
    e.preventDefault();
    $(virtualGridElem[rowIndex][colIndex]).css({ transform: gridVals[currRowIndex][currColIndex] });
    $(virtualGridElem[currRowIndex][currColIndex]).css({ transform: gridVals[rowIndex][colIndex] });
    // let tempTargetGridVals = gridVals[currRowIndex][currColIndex];
    // gridVals[currRowIndex][currColIndex] = gridVals[rowIndex][colIndex];
    // gridVals[rowIndex][colIndex] = tempTargetGridVals;
    [gridVals[currRowIndex][currColIndex], gridVals[rowIndex][colIndex]] = [gridVals[rowIndex][colIndex], gridVals[currRowIndex][currColIndex]];
  });

  // 必須寫這一段程式碼,否則 drop 監聽器不生效
  $(elem).on("dragover", e => {
    e.preventDefault();
  });

  // drag 相關的監聽是對拖拽元素目標有效的
  let ontology = $(elem).children(".ontology");

  $(ontology).on("dragstart", e => {
    currRowIndex = rowIndex;
    currColIndex = colIndex;
    $(elem).css({ opacity: "0.5" });
  });

  $(ontology).on("dragend", e => {
    $(elem).css({ opacity: "1" });
  });
}

程式碼最多的是 drop 事件,在開始拖拽時,也就是獲取拖拽的元素資訊,在這裡我們要把這個拖拽的元素透明度調低一點,表示被拖拽中的元素。之後,記錄改拖拽元素的二維索引值,rowIndex、colIndex,記錄為 currXxxIndex。

在拖拽完成之後,就要觸發 drop 事件。drop 事件中,對 gridVals 進行值的交替。ES6 中解構賦值不需要中間變數臨時儲存,就可以實現值交換:

let x = 1, y = 2;

[x, y] = [y, x]

替換之後,x = 2,y = 1。

結束語

具體實現過程請去看我倉庫中的程式碼? GiteeGitHub 地址。喜歡的話,請點個贊?再走哦!後續帶來更多的 Web 實踐。

相關文章