# 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)