1. 程式人生 > >Vue / html5中實現拖放

Vue / html5中實現拖放

最近在學習Vue,邊做個小demo邊學習。其中有一個小功能需要使用到拖放,順便還學一下拖放。拖放是HTML5的標準,對著教程在普通的頁面上很容易就實現了,但是vue中基本都是資料驅動,不推薦直接操作DOM。

HTML5拖放

可拖動

首先,預設情況下,影象、連結和文字是可拖動的。而想讓其他元素變為可拖動,需要設定draggable屬性為true

<div draggable="true"></div>


拖放事件

拖放事件有的是在被拖動元素上觸發的,而有的則是在放置目標上觸發。當拖動某個元素時,將依次觸發以下事件:

  1. dragstart: 被拖動元素開始被拖動是觸發
  2. drag: 被拖動元素被拖動期間持續觸發,類似於mousemove
  3. dragend: 停止拖動元素時觸發,無論被拖動元素是否放置在有效的目標上

當某個元素被拖動放在一個目標元素上時,放置目標則依次放生以下事件:

  1. dragenter: 當拖動某個元素進入放置元素時觸發
  2. dragover: dragenter觸發後,會觸發dragover事件,並且如果拖動元素繼續在放置目標範圍內移動,該事件會持續觸發
  3. drop或dragleave: 在dragover事件後,如果是鬆開滑鼠,被拖動元素放到放置目標上,觸發drop事件;如果是繼續拖動被拖動元素繼續移動並離開了放置元素,這個放置目標元素就觸發dragleave
    事件;

拖動元素經過各個元素時,這些元素雖然支援放置元素的事件,但是預設是不允許放置的,因此也就不會觸發drop事件。需要在允許放置的元素中重寫dragenterdragover事件的預設行為,即使用Event.preventDefault()方法。

有些瀏覽器drop事件預設行為開啟放到放置目標的URL。如拖放一個圖片,就直接轉到圖片檔案。因此也需要取消drop事件的預設行為。

dataTransfer物件

在整個拖放過程中,需要進行資料交換的話,可以使用dataTransfer物件。dataTransfer物件作為event物件的屬性,擁有兩個主要方法setData()

getData()分別設定資料和獲取資料。語法

void dataTransfer.setData(format, data);

void dataTransfer.getData(format);


dataTransfer物件的兩個屬性dropEffecteffectAllowed,分別設定被拖動元素的放置效果和指定當元素被拖放時允許的效果。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