起因
最近在公司打雜的時候,突然分到了一個鍋,就是要支持一個新的功能:用戶可以通過拖曳組件來改變組件的順序。因此,這陣子就看了一下網上的一些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