就是要一起拖拖拉拉!(一) - 瀏覽器 Drag and Drop API 操作

就是要一起拖拖拉拉!(一) - 瀏覽器 Drag and Drop API 操作

Drag and Drop 在一些網頁產品裡面算是蠻常見的應用,使用起來的效果也常常讓人印相深刻,在像是 Trello、Asana、Cakeresume 等需要進行一些計畫安排或是畫面規劃等互動性較強的工具,都會看到它的蹤影,由於之前的工作內容剛好都沒有機會碰到,最近又剛好想要研究,今天就讓我們一起來研究相關的 API 吧。

Outline

  • 基本概念
  • 宣告 draggable 與 droppable 元素
  • 可拖曳 (Draggble) 元素上的事件
  • 可拖曳 (Droppable) 元素上的事件
  • 實作 Drag and Drop 範例
  • 總結
  • 參考資料

基本概念

瀏覽器的拖拉功能基本上是由一連串的事件觸發而組成。在要被拖拉的元素上要先以 draggable的 屬性宣告,告訴瀏覽器這個元素是可以被拖移的,而在能夠放下拖拉元素的另外一個元素,相對的也必須以 dropabble 屬性宣告,才能夠接收拖移過來的元素內容。

而這一連串拖移的動作又可以拆分為幾個發生的時間段,說到這裡,對瀏覽器比較有概念的人應該可以猜到了。沒錯,這些時間段在瀏覽器內都是一個對應會被觸發的事件 (以下使用 Camelcase 做表示):

  • 開始拖移 (onDragStart)
  • 拖移中 (onDrag)
  • 拖移進入某個元素 (onDragEnter)
  • 拖移經過某個某個元素 (onDragOver)
  • 拖移離開某個元素 (onDragLeave)
  • 結束拖移 (onDragOver)

而拖拉期間與元素互動的方式,將會由開發者利用這些事件觸發來決定,詳細可以參考 MDN 官方網站,接下來會試著實作出基本拖放功能範例,以此說明一些常用到的拖拉事件觸發是如何發生。

宣告 draggable 與 droppable 元素

首先我們要先做出一個可拖曳的跟可放置的元素。分別在兩個素上將 draggable 屬性與 dropabble 屬性宣告為 true (這個專案使用 Bootstrap 作為輔助,後面會有範例)。

<div class="container"> 
  <div class="row justify-content-center"> 
    <div draggable="true" class=" box box-dragger"></div>
    <div droppable="true" class="box box-dropper"></div>
  </div>
</div>

之後,我們來看看上述提到的每個事件代表的意義與被觸發的時機,才能在實作時知道什麼時候該用什麼事件來搭配完成功能。

可拖曳 (Draggble) 元素上的事件

DragStart 事件

這個事件只會在被拖曳元素剛開始被拖曳時被觸發一次,我們可以試著在 .box-dragger 元素上宣告 dragstart 的事件監聽,接著就可以試著拖曳看看。

dragger.addEventListener("dragstart",function(){
 console.log("drag start!!!!")   
})

DragEnd 事件

dragend 事件會在拖曳結束時被觸發,應該很好理解,利用以下的程式碼片段:

dragger.addEventListener("dragend",function(){
 console.log("drag end!!!!")   
})

之後試著拖曳 .box-dragger 元素後再放開就可以看到上述的事件被觸發。

Drag 事件

drag 事件會在可以拖曳元素被拖曳期間持續被觸發:

dragger.addEventListener("drag",function(){
 console.log("draging!!")   
})

可以看見一如上面說的 dragstart 事件只會被觸發一次,而 drag 事件會在拖曳期間持續被觸發。

可放置 (Droppable) 元素上的事件

DragEnter 、DragLeave 事件

這兩個事件的觸發分別會在被拖曳元素進入可放置元素被拖曳元素離開可拖曳元素時發生:

dragger.addEventListener("dragenter",function(){
  console.log("dragenter")
})

dragger.addEventListener("dragleave",function(){
  console.log("dragleave")
})

試著把元素拖曳進可放置元素再離開,可以發現 dragenterdragleave 分別被觸發了一次。

DragOver 事件

dragover 這個事件名稱代表的意義應該不難理解,意思是當可放置元素上有拖曳中的元素經過,就會持續觸發,有點像是 CSS 裡面的 :hover 所代表的使用情境,試著使用下面的程式碼試試看這個事件的綁定吧:

dropper.addEventListener("dragover",function(){
  console.log("dragover")
})

順利的話應該會看到以下結果:

Drop 事件

在 MDN 裡面, drop 事件的定義是「一個元素或文字選取區塊被放置至一個有效的放置目標時觸發。」,但想要順利的觸發這個事件必須要注意的一點是,在 drop行為發生之前的 dragover 事件的預設行為會阻止 drag 行為的完成,也會讓 drop 事件無法被觸發。

因此必須在 dragover事件裡面停止預設的行為,這可以用 preventDefault 方法來達到。對這個預設行為有興趣的可以先參考官方說明,這部分預計在下一篇文章會有更詳細的說明。

dropper.addEventListener("dragover",function(e){
   e.preventDefault()
})
dropper.addEventListener("drop",function(e){
  console.log("drop!")
})

實作 Drag and Drop 範例

現在就讓我以前面的說明為例,來看看如何做出能夠可以把 dragger 拖放到 dropper 元素內的拖曳功能吧,我們已經宣告完 draggable 以及 droppable 的元素了,接下來只要利用事件處理就能夠完成拖拉互動了。

現在我們有兩個方塊,左邊藍色方塊是前面提到的 dragger 右邊則是 dropper ,目標是讓藍色方塊可以被拖曳拉到右邊另一個方塊,而一個很基本的構想是在 drop 事件觸發時,拿掉原本的舊藍色方塊,並把藍色方塊複製一份,放到右邊橘色方塊裡面

我們可以用另一個常見的 Web API 叫做 appendChild ,原本是用來在元素內掛載新的元素,不過如果所掛載的對象是一個已經存在的元素,就會變成是移動元素的效果(驚喜不驚喜意外不意外?沒關係我也是現在才發現),可以參考 W3School 的範例

所以在 dragstart 發生時,我先試著把藍色方塊的內容暫存到另外一個變數裡面。

let dragger = document.querySelector(".box-dragger")
let dropper = document.querySelector(".box-dropper")
let dragTemp;

dragger.addEventListener("dragstart",function(e){
  dragTemp = e.target
})

接下來要記得上面提到的,在 dragover 裡面要阻止瀏覽器預設行為。

dropper.addEventListener("dragover",function(e){
  e.preventDefault()
})

這個時候 drop 事件已經可以順利被觸發,只要在 drop 事件觸發時,在 event.target 也就是 dropper 裡面掛載藍色方塊的元素,就可以完成移動。

上面會看到兩個方塊沒有對齊是因為我一開始有給一些 margin ,不過從開發者工具裡面可以確定藍色方塊已經被拖曳放到橘色方塊之中。

再搭配適合的事件觸發以及適當的樣式調整,像是 dragenter 時改變顏色、 dragleave 時復原,就可以讓拖曳的元素經過可放置元素時產生顏色的改變來提示使用者。

最後在 drop 事件觸發並掛載後,調整一下元素的 spacing 可以看到比較好看的效果了。

想了解完整程式碼的話,我把這個 Demo 放在 Codepen

總結

其實拖拉互動效果並沒有非常複雜,可以想成瀏覽器提供給我們從拖拉開始到結束,一連串的時間段,讓我們可以在期間自由地做互動效果的調整,只要知道什麼時候該做哪些處理,基本上就是網頁元素的互動而已。

而想要達成拖拉互動效果也並不是只有一種方法,也沒有最正確的答案,只要熟悉DOM 元素的操作,能達到心中想要的效果我相信都是可以的。下一章節一樣會針對瀏覽器這個拖拉功能做稍微深入一點的研究,希望今天的主題能夠帶給你一些新的啟發,那麼下次見啦!

參考資料

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×