HTML5 drag和drop的親手實踐


起因

最近在公司打雜的時候,突然分到了一個鍋,就是要支持一個新的功能:用戶可以通過拖曳組件來改變組件的順序。因此,這陣子就看了一下網上的一些drag和drog的文章以及W3C的介紹,然后自己親手實踐了一下,畢竟打碼,才能變得更強。
首先,先放一個我的demo,大家可以去那里隨便拖動一下玩一玩:
https://chenjigeng.github.io/example/drag.html

知識儲備

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

  • draggable屬性: 如果你想讓一個元素變得可以拖曳的話,那么你就必須設置它的draggable=true,如下
<div class='target' draggable="true"></div>

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

  • 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>

效果:
這里寫圖片描述
2.為每個div都設置一個ondragstart函數,當該函數觸發的時候,進行初始化操作,比如記錄當前的目標對象,拖動目標的y值,以及設置拖動的效果。

// 拖動的目標對象
let target = ''
// 拖動的目標對象的y值
let targetOffsetTop = 0
// 當元素開始被拖動時,觸發該事件,目標對象是被拖動的元素
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下來跑一跑

本文地址在->本人博客地址, 歡迎給個 start 或 follow


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM