---
# System prepended metadata

title: Drag & Drop API 實作
tags: [實作, javascript]

---

# Drag & Drop API 實作

###### tags: `實作` `javascript`

[實作頁面連結](https://allenw0815.github.io/mission/mission_30/index.html)

![](https://i.imgur.com/HnLV0gl.png)


### Flowchart

1. HTML、CSS 一頁即可呈現
2. 了解 Drag & Drop API
3. 透過 JS 觸發拖曳事件
4. 最後比對陣列完成實作

> JS 流程

1.設定原始資料，這邊先複製一份原始資料，然後產生畫面
 1. map 回傳陣列，將每一個數字回傳成為一個物件，帶有原本的值還有一個隨機數 ( 用來排序用 )
 2. sort 將陣列透過剛剛的隨機數把陣列隨機打亂排列
 3. forEach 針對每個傳入的物件依序去生成 HTML 結構，到這邊已實現每次進入的順序都不同
 4. 接著加掛事件監聽

* 展開運算子
```javascript=
let number01 = [1, 2, 3]
let number02 = [...number01]
console.log(number02) // [1, 2, 3]
number01 == number02  // false
```
```javascript=
const number = ['08','18','27','33','47','52','66','79','87','99']
const createList = () =>{
    [...number]
    .map(num => ( { value : num, sort : Math.random()} ))
    .sort((a, b) => a.sort - b.sort)
    .forEach((item,index) => {
        const listItem = document.createElement('li')
        listItem.setAttribute('data-index', index) //透過這個index判斷拉出哪個拉進哪個
        listItem.innerHTML =
        `
        <span class="number"> ${index+1}.</span>
        <div class="draggable" draggable="true">
        <p>Drag Number : ${item.value}</p>
        <i class="fas fa-bars"></i>
        </div>        
        `
        listItems.push(listItem)
        dragArea.appendChild(listItem)
        });
    addEventListeners()
    }
```

2.接著一次將所需的節點加掛事件監聽，querySelectorAll 回傳的是 nodeList 需要用 forEach 方法來遍歷，最下方參考資料有所需要的事件說明。

```javascript=
const addEventListeners = () =>{
    const draggables = document.querySelectorAll('.draggable') // 抓到右邊的<div>
    const listItems = document.querySelectorAll('.drag-area li') // 抓到整個<li>

    draggables.forEach(draggable =>{
        draggable.addEventListener('dragstart',dragStart) 
    })

    listItems.forEach(item =>{
        item.addEventListener('dragenter',dragEnter)
        item.addEventListener('dragleave',dragLeave)
        item.addEventListener('dragover',dragOver)
        item.addEventListener('drop',dragDrop)
    })
}
```

3. 接著觸發各個事件的對應 function ( 這邊比較複雜 )，註解也有點饒舌，要慢慢看慢慢想。

```javascript=
function dragStart() {
    // 一拖曳的時候會觸發
    dragStartIndex = this.closest('li').getAttribute('data-index') //賦值 知道拖曳的目標物   
}
function dragEnter() {
    // 進入範圍時會觸發
    this.classList.add('over') //將拉入的<li>添加樣式
}
function dragLeave() {
    // 離開範圍時會觸發
    this.classList.remove('over') //將拖離的<li>移除樣式
}
function dragOver(e) {
    // 到目標物上時持續被觸發
    e.preventDefault(); //見下方紀錄
}
function dragDrop() {
    // 拖曳區塊被放置一個有效區域時觸發
    const dragEndIndex =  this.getAttribute('data-index') //定義要被交換的目標物
    swapItems(dragStartIndex, dragEndIndex)   
    this.classList.remove('over') // 要移除樣式 不然沒有觸發 dragLeave 樣式會被保留
}
const swapItems = (fromIndex, toIndex) =>{
    let firstItem = listItems[fromIndex].querySelector('.draggable') //被拖的那個<div>
    let secondItem = listItems[toIndex].querySelector('.draggable') //要拉進的那個<div>

    listItems[fromIndex].appendChild(secondItem) //被拖的<li>放進要被拉進去的<div>
    listItems[toIndex].appendChild(firstItem) //被拉進去<li>要放被拖的<div>
}
```

4. 檢查數字大小順序，比對答案陣列與當前陣列的數字，並添加提示樣式

```javascript=
function checkOrder(){
    const standerOrder = [] //正確順序
    let ps = document.querySelectorAll('p') //取得所有<p>
    ps.forEach(p => {
        standerOrder.push((p.innerText).slice(-2)) //取得所有最後兩碼的文字 推入陣列中 
    })
    standerOrder.sort().reverse(); //排序後反轉成由大到小

    ps.forEach((p,index) => {
          if((p.innerText).slice(-2) !== standerOrder[index]){
            p.classList.remove('correct')
            p.classList.add('wrong')
          } else{
            p.classList.remove('wrong')
            p.classList.add('correct')
          }
    })
}

```

### 記錄

* 觀察事件

```javascript=
const dragStart = () => {
    console.log('Event','dragStart');
}
const dragOver = () => {
    console.log('Event','dragOver');
}
const drop = () => {
    console.log('Event','drop');
}
const dragEnter = () => {
    console.log('Event','dragEnter');
}
const dragLeave = () => {
    console.log('Event','dragLeave');
}
```
* 按著移動時，觀察事件觸發的時機，再去指派要做什麼事情
> <font color="#f00">這邊發現一個重要的問題</font>
> 
> 原本使用 arrow function 時候 this 的指向會變成 window 導致失效報錯，所以先改回一般寫法，再另外研究 this 問題。
```javascript=
function dragEnter() {
    console.log('Event','dragEnter');
    console.log(this);
}
```
![](https://i.imgur.com/Xnel7uh.png)

---
* 抓取節點
> closest() 往上查找 只要找到符合條件的 就停止
> find() 往下查找 只要找到符合條件的 就停止
> parent() 只往上查找一層
> parents() 往上查找 不停止 找出所有符合條件的
---

* 這邊發現 dragover 事件會影響 drop 事件 (同時觸發時 drop 會失效)，所以在 dragover 事件觸發 function 內取消預設執行的動作 (但不用 dropover 就無法實現功能)
```javascript=
function dragOver(e) {
    e.preventDefault();
}
```

* 產生一個陣列長度為 10，並且內容為隨機數 ( 一行搞定 )
```javascript=
const setData = () => new Array(10).fill(0).map((e)=> e += Math.floor(Math.random()*100))   
}
```

### 小記

這一實作比較難一點，因為比較多陌生的東西，有發現這種拖曳的功能有現成的函式庫可以使用，之後可以再作補充，讓移動可以更順暢，瀏覽起來不生硬。


### 參考
[MDN Drag & Drop API](https://developer.mozilla.org/zh-TW/docs/Web/API/HTML_Drag_and_Drop_API)
[YT 有示範lib使用](https://www.youtube.com/watch?v=z54suepKiIU)
[Array相關文章](https://osk2.medium.com/array-%E5%8F%AF%E8%83%BD%E8%B7%9F%E4%BD%A0%E6%83%B3%E7%9A%84%E4%B8%8D%E4%B8%80%E6%A8%A3-%E4%B8%8A-21f2031e4697)
