Drag N Drop
1. 实现效果
2. 具体实现
2.1 使用Mouseevent实现
该方案的关键是
elementFromPoint方法 该方案主要是用了比较基础的mouseevent, 动画也算是比较流畅, 使用的时候需要自己进行一些调整, 代码如下
vue
<template>
<div class="mouse-drag-wrapper view-demo-component">
<div class="wrapper">
<div class="area target"></div>
<div class="area"></div>
<div class="area"></div>
<div class="area"></div>
<div class="area"></div>
</div>
</div>
</template>
<script setup>
import { onMounted } from 'vue'
onMounted(() => {
const areas = document.querySelectorAll('.area')
const wrapper = document.querySelector('.wrapper')
const dragWrapper = document.querySelector('.mouse-drag-wrapper')
// 我们当前正在飞过的潜在的 droppable 的元素
let currentDroppable = null;
// 当前元素
let curTarget = areas[0]
const handleEleLeave = () => {
if (currentDroppable) {
// 处理“飞出” droppable 的元素时的处理逻辑(移除高亮)
currentDroppable.classList.contains('active') && currentDroppable.classList.remove('active')
}
}
const handleEleEnter = (elemBelow) => {
if (currentDroppable) {
// 处理“飞入” droppable 的元素时的逻辑
!elemBelow.classList.contains('active') && elemBelow.classList.add('active')
}
}
areas.forEach(area => {
area.addEventListener('mousedown', (e) => {
// 如果当前元素不是默认拖拽元素, return
if (!area.classList.contains('target')) return
area.classList.remove('target')
// 设置temp元素, 添加动画
const { offsetX, offsetY, layerX, layerY } = e
const temp = document.createElement('div')
temp.style.position = 'absolute'
temp.style.left = layerX - offsetX + 'px'
temp.style.top = layerY - offsetY + 'px'
temp.style.width = area.offsetWidth + 'px'
temp.style.height = area.offsetHeight + 'px'
temp.style.opacity = '0.3'
temp.style.border = '4px solid #000'
temp.style.background = 'background: #4E5969 url("../../../../public/logo.jpeg") center/cover no-repeat'
// 监听temp元素移动
temp.addEventListener('mousemove', (e) => {
// 设置temp的位置
temp.style.left = e.pageX - dragWrapper.getBoundingClientRect().left - dragWrapper.getBoundingClientRect().width * 0.05 + 'px'
temp.style.top = e.pageY - dragWrapper.getBoundingClientRect().top - dragWrapper.getBoundingClientRect().height * 0.05 + 'px'
// 这里通过先隐藏后展示, 来获取被temp遮挡的下层元素
temp.hidden = true
let elemBelow = document.elementFromPoint(e.clientX, e.clientY);
temp.hidden = false
// 移除上一个可拖拽元素的样式
handleEleLeave()
// 如果当前鼠标移动过的元素是null 或者 不符合拖拽, return
if (!elemBelow || !elemBelow.classList.contains('area')) return
// 更新新的可拖拽元素
currentDroppable = elemBelow;
// 为新的可拖拽元素添加样式
handleEleEnter(elemBelow)
})
// 监听temp元素上鼠标抬起
temp.addEventListener('mouseup', (e) => {
// temp元素作用完成, 移除
temp.remove()
// 获取当前鼠标弹起时下方的元素
let elemBelow = document.elementFromPoint(e.clientX, e.clientY);
// 判断是否是可拖拽元素, 不是的话就保持之前状态
if (!elemBelow.classList.contains('area')) {
// 保持之前状态
curTarget.classList.add('target')
// 移除拖拽样式
curTarget.classList.contains('active') && curTarget.classList.remove('active')
// 处理“飞出” droppable 的元素时的处理逻辑(移除高亮
handleEleLeave()
return
}
// 当前元素是可拖拽元素, 进行更新
curTarget = elemBelow
elemBelow.classList.add('target')
elemBelow.classList.contains('active') && elemBelow.classList.remove('active')
})
// 父元素添加temp
wrapper.append(temp)
})
})
})
</script>在上面这段代码中最关键的是elementFromPoint这个方法, 该方法返回给定坐标点下最上层的元素, 同时配合下段代码
vue
// 这里通过先隐藏后展示, 来获取被temp遮挡的下层元素
temp.hidden = true
let elemBelow = document.elementFromPoint(e.clientX, e.clientY);
temp.hidden = false这样就可以定位到当前鼠标的target元素.
2.2 使用drag事件实现
这里有篇关于setTimout和拖拽优化相关的文章也推荐读下, https://segmentfault.com/a/1190000039267035 该方案无疑是更简单的方案, 也是原项目的使用方案. 在这个方案中主要是处理不同的拖拽事件.
vue
<template>
<div class="mouse-drag-wrapper view-demo-component">
<div class="wrapper">
<div class="area">
<div class="target" draggable="true"></div>
</div>
<div class="area"></div>
<div class="area"></div>
<div class="area"></div>
<div class="area"></div>
</div>
</div>
</template>
<script setup>
import { onMounted } from 'vue'
onMounted(() => {
const areas = document.querySelectorAll('.area')
const target = document.querySelectorAll('.target')
// 对可拖拽目标添加对应的事件监听
target.addEventListener('dragstart', dragStart)
target.addEventListener('dragend', dragEnd)
// 对可拖放区域添加对应的事件监听
areas.forEach(area => {
area.addEventListener('dragover', dragOver)
area.addEventListener('dragenter', dragEnter)
area.addEventListener('dragleave', dragLeave)
area.addEventListener('drop', dragDrop)
})
// 设置无意义类名, 隐藏可拖放区域的背景
// 这里使用异步回调, 因为同步的话会导致不能进行拖拽等后续操作(因为没有target这个可拖拽目标)
function dragStart() {
this.classList.add('hold')
setTimeout(() => this.className = 'invisible', 0)
}
// 重置类名
// 如果拖拽到非可拖拽区域, 也是将dragStart里面的历史元素重置类名进行展示
function dragEnd() {
this.className = 'target'
}
function dragOver(e) {
e.preventDefault()
}
// 移进可拖拽区域
function dragEnter(e) {
e.preventDefault()
this.classList.add('active')
}
// 移出可拖拽区域
function dragLeave() {
this.classList.remove('active')
}
// 松手后设置背景(因为只有可拖拽区域添加这个监听了)
function dragDrop() {
this.append(target)
this.classList.remove('active')
}
})
</script>