如下备忘拖拽的简单实现和其中存在的问题,以此为基石能够扩展开发多种拖拽效果。
以下图,咱们想实现的效果为: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 项目的基本思路。