如图实现了一个demo:把一个水果从一个盒子拿起来,然后放到另一个盒子。总结以下内部。
DataTransfer
DataTransfer
对象用于保存拖动并放下(drag and drop)过程中的数据。它可以保存一项或多项数据,这些数据项可以是一种或者多种数据类型。
可以在拿起水果时记录一些数据(比如从哪个盒子拿的什么水果),并在往另外的盒子里放的时候读取(判断下这个拿能不能放下这种水果)
水果上事件和属性
盒子上事件
- 事件
dragenter
,拖动的元素或者进入
一个有效的放置目标时触发,也就是拿起水果进入了盒子范围(松手就掉进去了)时触发。 - 事件
dragover
,拖动的元素或者被选择的文本被拖进一个有效的放置目标时(每几百毫秒)触发。拿着东西在盒子上面晃悠时,盒子每几百毫秒喊会一声。 - 事件
dragleave
,拖动的元素离开
一个有效的放置目标时被触发,和dragenter
相反。 - 事件
drop
,在元素被放置到有效的放置目标上时触发,放进盒子里时触发。为确保drop
事件始终按预期触发,应当在处理dragover
事件的代码部分始终包含preventDefault()
调用。
实现
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
<style>
body,
html {
user-select: none;
height: 100%;
margin: 0;
}
#draggable {
text-align: center;
background: white;
}
#app {
display: flex;
height: 100%;
}
.box {
width: 200px;
border: 1px solid blueviolet;
margin: 10px;
padding: 10px;
transition: all 0.5s;
}
.box.dragover {
box-shadow: 0 0 5px blueviolet;
}
.block-item {
text-align: center;
background: blueviolet;
margin: 5px;
}
</style>
</head>
<body>
<div id="app">
<!-- 放置目标 -->
<div v-for="(v, k) in boxList"
class="box"
:key="k"
@dragenter="boxDragEnter($event, k)"
@dragover="boxDrapOver($event, k)"
@dragleave="boxDragLeave($event, k)"
@drop="boxDop($event, k)">
<span>{{ k }}</span>
<!-- 拖拽元素 -->
<div v-for="block in boxList[k]"
class="block-item"
draggable="true"
:key="block.name"
@dragstart="blockDrapStart($event, block, k)">
{{block.name}}
</div>
</div>
</div>
<script>
const { createApp, reactive, onMounted } = Vue
createApp({
setup() {
// 俩个box,分别包含一个水果
const boxList = reactive({
box1: [{ name: '🍎' }],
box2: [{ name: '🍐' }]
});
// 该事件在放置目标上触发
// 拖动的元素进入一个有效的放置目标时触发
function boxDragEnter(event, box) {
console.log("【box】DragEnter:", box, event)
if (!event.target.classList.contains("dragover")) {
event.target.classList.add("dragover");
}
}
// 该事件在放置目标上触发
// 拖动的元素离开一个有效的放置目标时触发
function boxDragLeave(event, box) {
console.log("【box】DragLeave:", box, event)
if (event.target.classList.contains("dragover")) {
event.target.classList.remove("dragover");
}
}
// 该事件在放置目标上触发
function boxDrapOver(event, box) {
console.log("【box】DrapOver:", box)
// event.preventDefault(),使目标容器能够接收 drop 事件。
event.preventDefault()
}
// 元素被放置到目标元素上时触发.
// 为确保 drop 事件始终按预期触发,应当在处理 dragover 事件的代码部分始终包含 preventDefault() 调用。
function boxDop(event, box) {
console.log("【box】Dop:", box, event)
// 读取数据
const { sourceBox, data } = JSON.parse(event.dataTransfer.getData('text/plain'));
if (sourceBox !== box) {
boxList[box].push(data);
const sourceIndex = boxList[sourceBox].findIndex(item => item.name === data.name);
boxList[sourceBox].splice(sourceIndex, 1)
}
}
// 开始拖动元素时调用,在拖拽元素上触发
function blockDrapStart(event, data, sourceBox) {
console.log("【block】DrapStart:", data, sourceBox, event)
// 拖拽的内容存一下
event.dataTransfer.setData("text/plain", JSON.stringify({ sourceBox, data }))
}
return {
boxList,
boxDragEnter,
boxDragLeave,
boxDrapOver,
boxDop,
blockDrapStart
}
}
}).mount('#app')
</script>
</body>
</html>
遇到的问题
盒子上绑定的事件,其中的每个子元素也会触发,如上图动作 触发顺序为
css
[水果]dragenter -> [水果]dragover -> [box]dragenter -> [水果]draglave
导致了子元素也会加上放置反馈的样式。
解决方式:增加元素判断
js
// 该事件在放置目标上触发
// 拖动的元素进入一个有效的放置目标时触发
function boxDragEnter(event, box) {
console.log("【box】DragEnter:", box, event.target)
const isBox = event.target.classList.contains("box")
if (isBox && !event.target.classList.contains("dragover")) {
event.target.classList.add("dragover");
}
}
// 该事件在放置目标上触发
// 拖动的元素离开一个有效的放置目标时触发
function boxDragLeave(event, box) {
console.log("【box】DragLeave:", box, event.target)
const isBox = event.target.classList.contains("box")
if (isBox && event.target.classList.contains("dragover")) {
event.target.classList.remove("dragover");
}
}
类似问题:dragleave-of-parent-element-fires-when-dragging-over-children-elements
相关文档
npm package