天天看點

HTML5 drag和drop的實踐

起因

最近在公司打雜的時候,突然分到了一個鍋,就是要支援一個新的功能:使用者可以通過拖曳元件來改變元件的順序。是以,這陣子就看了一下網上的一些drag和drog的文章以及W3C的介紹,然後自己親手實踐了一下,畢竟打碼,才能變得更強。

首先,先放一個我的demo,大家可以去那裡随便拖動一下玩一玩:

https://chenjg1996.top/example/drag.html

知識儲備

與drag和drog有關的屬性和事件

  • draggable屬性: 如果你想讓一個元素變得可以拖曳的話,那麼你就必須設定它的draggable=true,如下

    這樣,該元素就可以拖動了

  • ondragstart: 當元素開始被拖動時,觸發該事件,目标對象是被拖動的元素
  • ondragover: 當被拖動元素在懸挂元素上移動的時候,該事件觸發。目标對象是被拖動元素懸挂的那個元素。
  • ondragleave: 當被拖動元素離開懸挂元素時,觸發該事件。目标對象是被拖動元素懸挂的那個元素。
  • ondrop: 當滑鼠松開被拖動元素的時候,觸發該事件。目标對象是被拖動元素懸挂的那個元素。
  • ondragend: 當滑鼠松開被拖動元素的時候,觸發該事件。目标對象是被拖動的元素。其中,ondrop事件會先于ondragend事件觸發。
  • event.preventDefault: 當觸發ondragover事件的時候,必須使用event.preventDefault(),否則的話,ondrop事件就不會觸發
  • event.dataTransfer.effectAllowed:設定或傳回被拖動元素允許發生的拖動行為。可設定的屬性很多,這裡我們就不細說,感興趣的可以去查下,一般來說,我們都設定為”move”.

插入節點的方法

  • 将節點插入到另一個節點前面,代碼如下
    function insertBefore(insertNode, node) {
       node.parentNode.insertBefore(insertNode, node)
    }
               
    這個其實比較簡單,就是找到節點的父親,然後将要插入的節點放到節點的前面。
  • 将節點插入到另一個節點後面,代碼如下圖
    function insertAfter(insertNode, node) {
       if (node.nextElementSibling) {
         insertBefore(insertNode, node.nextElementSibling)
       } else {
         node.parentNode.appendChild(insertNode)
       }
    }
               
    這個其實也挺簡單的,就是如果該節點有兄弟節點的話,那麼就将插入節點放到它兄弟節點的前面,否則,則說明該節點是父節點的最後一個節點,是以直接将插入節點放到父節點的末尾。

實踐

在這裡,我們要做的就是一個支援各個圖檔拖曳來交換位置的玩意,不過,當圖檔交換位置的時候,不單單是圖檔交換位置,而是包含圖檔的容器交換位置。

1.我們先放置幾張圖檔,并且将它們的dragable設定為true,這樣它們就可以拖動了。代碼如下:

<body>
    <div class='target' draggable="true">
      <img src="./imgs/1.jpeg" alt="1">
    </div>
    <div class='target' draggable="true">
      <img src='./imgs/2.jpg' />
    </div>
    <div class='target' draggable="true">
      <img src="./imgs/3.jpg" alt="ss">
    </div>    
    <div class='target' draggable="true">
      <img src="./imgs/4.jpg" alt="ss">
    </div>   
  </body>
           

效果:

HTML5 drag和drop的實踐

2. 為每個div都設定一個ondragstart函數,當該函數觸發的時候,進行初始化操作,比如記錄目前的目标對象,拖動目标的y值,以及設定拖動的效果。

// 拖動的目标對象
let target = ''
// 拖動的目标對象的y值
let targetOffsetTop = 
// 當元素開始被拖動時,觸發該事件,目标對象是被拖動的元素
function handleDragStart(ev) {
  target = findTarget(ev.target)
  targetOffsetTop = ev.target.offsetTop
  ev.dataTransfer.effectAllowed = 'move'
}
// 找到類名為target的目标對象
function findTarget(node) {
  if (!node || node == document) {
    return null
  }
  if (node.classList.contains('target')) {
    return node;
  }
  return findTarget(node.parentNode)
}
           

3.為每個div注冊一個ondragover事件和ondragleave事件,在ondragover事件裡,主要是調用event.preventDefault來防止ondrog不會被觸發,并且為了看起來更明顯,當ondragover事件觸發的時候,為目标對象增加一個dotted類。當ondragleave事件觸發的時候,則把dotted類從目标對象移除。

// 當被拖動元素在懸挂元素上移動的時候,該事件觸發。目标對象是被拖動元素懸挂的那個元素。
// 必須執行event.preventDefault(),不然的話ondrop不會觸發
function handleDragOver(ev) {
  ev.preventDefault();
  ev.target.classList.add('dotted')
}
// 當被拖動元素離開懸挂元素時,觸發該事件。目标對象是被拖動元素懸挂的那個元素。
function handleDragLeave(ev) {
  ev.target.classList.remove('dotted')
}
           

4.為每個div注冊ondrog事件和ondragend事件,ondrog事件是重點,它主要是根據被拖動元素和被拖動元素懸挂的那個元素的坐标,來決定是要将被拖動元素插入到懸挂元素的前面還是後面。而ondragend主要是用于将target設定為null,代碼如下:

// 當滑鼠松開被拖動元素的時候,觸發該事件。目标對象是被拖動元素懸挂的那個元素。
function handleDrog(ev) {
  let resultOffsetTop = ev.target.offsetTop
  if (targetOffsetTop < resultOffsetTop) {
    insertAfter(target, findTarget(ev.target))
  }
  else {
    insertBefore(target, findTarget(ev.target))
  }
  ev.target.classList.remove('dotted')
}
// 将節點插入到另一個節點前面
function insertBefore(insertNode, node) {
  node.parentNode.insertBefore(insertNode, node)
}
// 将節點插入到另一個節點後面
function insertAfter(insertNode, node) {
  if (node.nextElementSibling) {
    insertBefore(insertNode, node.nextElementSibling)
  } else {
    node.parentNode.appendChild(insertNode)
  }
}
// 當松開滑鼠的時候,觸發該事件。目标對象是被拖動的對象
function handleDragEnd(ev) {
  target = null
}
           

這樣子,我們就實作了一個可以通過拖曳來改變圖檔順序的一個小玩意啦~完整的代碼放到https://github.com/chenjigeng/something 上了~有興趣的可以git clone下來跑一跑

繼續閱讀