Vue / html5中實現拖放
最近在學習Vue,邊做個小demo邊學習。其中有一個小功能需要使用到拖放,順便還學一下拖放。拖放是HTML5的標準,對著教程在普通的頁面上很容易就實現了,但是vue中基本都是資料驅動,不推薦直接操作DOM。
HTML5拖放
可拖動
首先,預設情況下,影象、連結和文字是可拖動的。而想讓其他元素變為可拖動,需要設定draggable
屬性為true
。
<div draggable="true"></div>
拖放事件
拖放事件有的是在被拖動元素上觸發的,而有的則是在放置目標上觸發。當拖動某個元素時,將依次觸發以下事件:
- dragstart: 被拖動元素開始被拖動是觸發
- drag: 被拖動元素被拖動期間持續觸發,類似於
mousemove
- dragend: 停止拖動元素時觸發,無論被拖動元素是否放置在有效的目標上
當某個元素被拖動放在一個目標元素上時,放置目標則依次放生以下事件:
- dragenter: 當拖動某個元素進入放置元素時觸發
- dragover:
dragenter
觸發後,會觸發dragover
事件,並且如果拖動元素繼續在放置目標範圍內移動,該事件會持續觸發 - drop或dragleave: 在
dragover
事件後,如果是鬆開滑鼠,被拖動元素放到放置目標上,觸發drop
事件;如果是繼續拖動被拖動元素繼續移動並離開了放置元素,這個放置目標元素就觸發dragleave
拖動元素經過各個元素時,這些元素雖然支援放置元素的事件,但是預設是不允許放置的,因此也就不會觸發
drop
事件。需要在允許放置的元素中重寫dragenter
和dragover
事件的預設行為,即使用Event.preventDefault()
方法。有些瀏覽器
drop
事件預設行為開啟放到放置目標的URL。如拖放一個圖片,就直接轉到圖片檔案。因此也需要取消drop
事件的預設行為。
dataTransfer物件
在整個拖放過程中,需要進行資料交換的話,可以使用dataTransfer
物件。dataTransfer
物件作為event物件的屬性,擁有兩個主要方法setData()
getData()
分別設定資料和獲取資料。語法
void dataTransfer.setData(format, data);
void dataTransfer.getData(format);
dataTransfer
物件的兩個屬性dropEffect
和effectAllowed
,分別設定被拖動元素的放置效果和指定當元素被拖放時允許的效果。format是字串型別,表示資料型別的;data也是字串型別,即要儲存的值。
dataTransfer
物件還有個files
屬性包含拖動到瀏覽器視窗的檔案列表。可以用這個藉口實現檔案拖動上傳。
簡單的拖放例子可參考w3school的HTML5拖放
Vue拖放排序
小專案tomatodos中,todo列表我想加上一個拖放排序功能。就是todo列表中,允許使用者直接拖動某一項,插入到另一項後。Vue中都是資料驅動的,因此在拖放後應該改變的是資料順序,而不是直接操作dom。因此很自然想到的還是在drop
後改變資料的順序,拖動的元素則可以在dragstart
中獲取。我們的資料是
lists: ['1: apple', '2: banana', '3: orange', '4: melon']
在html使用v-for渲染,使用transition-group
元件加入動畫效果
<transition-group id='app' name="drog" tag="ul">
<li draggable="true" v-for="(item, index) in lists"
@dragstart="dragStart($event, index)" @dragover="allowDrop" @drop="drop($event, index)"
v-bind:key="item">
{{item}}
</li>
</transition-group>
每個li需要給定一個唯一的key,這樣才能很好的使用過渡效果
索引在dragstart
事件中傳入,可以使用dataTransfer
儲存
//開始拖動
dragStart(e, index){
e.dataTransfer.setData('Text', index);
}
放置元素觸發drop
事件,在drop
事件中,我們同時擁有放置目標元素的索引index
,以及被拖動元素的索引dragIndex
。比較簡單的做法是使用一個新陣列記錄整個過程,最後把新陣列賦給原資料變數。使用splice刪除和插入,效果就是從前面拖到後面是插入放置元素後面,而從後面往前拖放是插入到放置元素前面。這樣不需要再判斷方向,也能得到比較好的效果。
//放置
drop(e, index){
//取消預設行為
this.allowDrop(e);
//使用一個新陣列重新排序後賦給原變數
let arr = this.lists.concat([]),
dragIndex = e.dataTransfer.getData('Text');
temp = arr.splice(dragIndex, 1);
arr.splice(index, 0, temp[0]);
this.lists = arr;
}
完整demo