如下備忘拖拽的簡單實現和其中存在的問題,以此爲基石能夠擴展開發多種拖拽效果。
以下圖,咱們想實現的效果爲:html
當方塊從上方灰塊被拖拽到下方時,下方灰塊中會出現該方塊。vue
讓咱們先把以上頁面效果實現:html5
<style scoped> .drag-field, .drop-field{ height: 10rem; box-sizing: border-box; padding: 1rem; background-color: #eee; margin-top: 1rem; display: flex; justify-content: space-around; align-items: center; } .item{ width: 30%; height: 3rem; text-align: center; line-height: 3rem; font-size: .9rem; background-color: royalblue; color: #eee; } .item:hover{ cursor: pointer; } </style> <template> <div class="hello"> <div class="drag-field"> <div class="item" v-for="(item, index) in items" :key="index" > {{ item.label }} </div> </div> <div class="drop-field"> <div class="item" v-if="droppedItem !== ''"> {{ droppedItem }} </div> </div> </div> </template> <script> /* eslint-disable */ export default { name: '', data () { return { droppedItem: '', items: [ { id: 1, label: '模塊一' }, { id: 2, label: '模塊二' }, { id: 3, label: '模塊三' } ] } } } </script>
爲了讓 DOM 元素能夠拖拽,咱們須要爲元素增長 draggable="true"web
<div class="item" draggable="true" v-for="(item, index) in items" :key="index" > {{ item.label }} </div>
拖放事件,綁定於可拖放區域上。segmentfault
之因此把這個方法單獨拎出來,是由於在使用該方法時存在一些注意事項。app
當咱們這樣使用時:wordpress
<div class="drop-field" @drop="drop" > ... </div> methods: { drop (event) { console.log('drop', event) } }
發現當咱們將可拖拽元素拖放至此時,並無觸發事件。flex
根據 MDN 的文檔:this
A listener for the dragenter and dragover events are used to indicate valid drop targets, that is, places where dragged items may be dropped. Most areas of a web page or application are not valid places to drop data. Thus, the default handling for these events is to not allow a drop.", hence the only way for the drop event to be fired is to first cancel the dragenter or dragover event.
咱們必須阻止某一 DOM 元素對 dragover 的默認行爲,才能使 drop 事件在其上正確執行:spa
<div class="drop-field" @drop="drop" @dragover="dragover" > ... </div> methods: { drop (event) { console.log('drop', event) }, dragover (event) { event.preventDefault() } }
在 Vue 中,咱們能夠將組織默認行爲的過程簡寫以下:
@dragover="dragover" # 改成: @dragover.prevent
注意,不管是 dragxxx 或 drop 事件,其傳遞的參數都是 DragEvent。
讓我很費解的是,對於在拖放區綁定的 drop 事件而言,其 DragEvent 中居然沒法找到被拖拽元素。
這也就意味着,不借助額外變量,drop 事件是沒法知道被拖放者是什麼的。
但咱們仍能夠藉助 DragEvent 中的 DataTransfer 來進行被拖放對象的消息傳遞。
流程以下:
<div class="item" draggable="true" @dragstart="dragstart" v-for="(item, index) in items" :key="index" > {{ item.label }} </div> dragstart (event) { console.log('dragstart', event) event.dataTransfer.setData('my-info', 'hello') event.dataTransfer.setData('my-extra-info', 'world') }
<div class="drop-field" @drop="drop" @dragover.prevent > <div class="item" v-if="droppedItem !== ''"> {{ droppedItem }} </div> </div> drop (event) { console.log('drop', event) console.log(event.dataTransfer.getData('my-info')) console.log(event.dataTransfer.getData('my-extra-info')) }
<div class="item" draggable="true" @dragstart="dragstart" @dragend="dragend" v-for="(item, index) in items" :key="index" > {{ item.label }} </div> dragend (event) { console.log('dragend', event); event.dataTransfer.clearData() }
在整個拖拽過程當中,事件的前後順序爲:
Step1: 拖拽對象的 dropstart; Step2: 拖放區的 drop; Step3:拖拽對象的 dropend;
於是,若是在 dragend 中傳遞消息,是不能被 drop 捕獲的。
若是咱們在被拖拽對象的 dragover 事件中傳遞消息,因爲 dragover 事件的做用對象是「可拖放區」,即此時,該 dragover 中的 DragEvent 是以「可拖放區」身份施加的,故而不會傳遞到 drop 中。
dataTransfer
中設置的消息( 即 setData
的第二個參數 )只能是字符串類型。若是想要傳遞對象,須要先進行序列化。
在上面的代碼中,若是咱們在 @dragstart
中想傳遞一些參數,以下:
@dragstart="dragstart(item)"
就會遇到一個問題:默認傳遞的 DragEvent 參數丟失了。
此時,咱們須要使用 Vue 的特殊變量來實現事件參數的傳遞:
@dragstart="dragstart($event, item)"
結合以上內容,咱們的實現思路以下:
代碼以下:
<div class="item" draggable="true" @dragstart="dragstart($event, item)" @dragend="dragend" v-for="(item, index) in items" :key="index" > {{ item.label }} </div> <div class="drop-field" @drop="drop" @dragover.prevent > <div class="item" v-if="droppedItem !== ''"> {{ droppedItem }} </div> </div> methods: { drop (event) { this.droppedItem = event.dataTransfer.getData('item') }, dragstart (event, item) { event.dataTransfer.setData('item', item.label) }, dragend (event) { event.dataTransfer.clearData() } }
在 Vue 項目中,被拖拽對象和可拖放區域可能放在不一樣組件之中,此時,關鍵數據的傳遞最好藉助 Vuex 等數據總線實現。讓數據而非 DOM 流轉是 Vue 項目的基本思路。