- 作者:老汪软件技巧
- 发表时间:2024-09-10 07:01
- 浏览量:
拖拽功能
馒头儿~ 嗷呜~ 馒头儿~ 嗷呜~ 馒头儿~ 嗷呜~ 馒头儿~ 嗷呜~
背景
哎…手写这个功能也是情非得已,有现代的科技draggable谁愿意回到石器时代,谁让我们是开发者呢,俗称在整个项目流程中最没有尊严的一个节点,没有任何的决策权,有时候真的想只当个切图仔。
回到现实,该干还得干,怎么个事儿呢,因为项目需要通过串口,实现某个三方设备通信,分为安卓app,windows桌面端两个版本,那么怎样才能使用一套代码实现目前的需求呢,第一想到的肯定是跨平台框架uniapp,uni固然可以实现安卓app的,努努力串口应该也是可以通信的,那么如果二期迭代需要加入其他原生能力的话,uni还是有点相襟见卓。
经过讨论,最后采用unity + web内嵌的方案来开发本项目,由unity唤起底层的功能,web专注于业务层,这个时候就会产生几个问题:
经过最后的调研得出,windows与android的webView版本功能的显然不一致,需要开发者自行兼容,插件库就不要想了,不是难用就是不兼容,尝试过最新的vue3 DnD都无法满足,话不多说,采用最原始的办法,我不信还是不行。
实现思路
利用鼠标事件mousedown,mousemove,mouseup,在鼠标按下时,对鼠标的移动与抬起进行监听,当鼠标拖拽至目标元素时,通过抬起以达到释放元素至目标位置的目的,移动端touchstart,touchmove,touchend同理。
代码实现style
body {
padding-top: 100px;
width: 100vw;
display: flex;
justify-content: center;
}
.wrap {
display: flex;
}
.drop-ul {
width: 400px;
height: 400px;
margin-right: 50px;
padding: 20px;
border: 1px solid rgb(6, 161, 40);
}
.drag-ul {
width: 400px;
height: 400px;
border: 1px solid rgb(6, 161, 40);
padding: 20px;
}
.drop-li {
width: 100px;
height: 40px;
text-align: center;
margin-top: 10px;
line-height: 40px;
border: 1px solid rgb(0, 0, 0);
}
.drag-li {
width: 100px;
height: 40px;
text-align: center;
margin-top: 10px;
line-height: 40px;
color: #fff;
}
.drag-li:nth-child(1) {
background-color: red;
}
.drag-li:nth-child(2) {
background-color: green;
}
.drag-li:nth-child(3) {
background-color: blue;
}
HTML
<body>
<div class="wrap">
<ul class="drop-ul">
<li class="drop-li">目标一li>
<li class="drop-li">目标二li>
<li class="drop-li">目标三li>
<li class="drop-li">目标四li>
<li class="drop-li">目标五li>
<li class="drop-li">目标六li>
ul>
<ul class="drag-ul">
<li class="drag-li">redli>
<li class="drag-li">greenli>
<li class="drag-li">blueli>
ul>
div>
body>
JS实现
/**
* @returns 当前设备
*/
function getDeviceType() {
const userAgent = navigator.userAgent || navigator.vendor || window.opera;
if (/android/i.test(userAgent)) {
return 'Android';
}
if (/iPad|iPhone|iPod/.test(userAgent)) {
return 'iOS';
}
if (/Windows Phone/i.test(userAgent)) {
return 'Windows Phone';
}
return 'Browser';
}
/**
* @param {*} fatherNode
* @param {*} posterityNode
* @returns
*/
function isContainsNode(fatherNode, posterityNode) {
// 是否包含某个元素
return fatherNode.contains(posterityNode)
}
/**
* @param {*} node
* @param {*} target
* @returns
*/
function removeChildNode(node, target) {
// 根据父节点,删除某个子节点
if (!isContainsNode(node, target)) {
console.log('删除失败,当前元素不包含删除目标');
return false
}
node.removeChild(target)
}
/**
* @param {*} nodeList // 节点列表
* @param {dragClientX, dragClientY} client // 鼠标的位置
* @param {*} callback // 鼠标出现在某目标上空的回调
*/
function findNode(nodeList, { dragClientX, dragClientY }, callback) {
// 查找上空出现鼠标的节点
for (let i = 0; i < nodeList.length; i++) {
const node = nodeList[i]
const { left, top, height, width } = node.getBoundingClientRect()
const condition1 = dragClientX >= left && dragClientX <= left + width
const condition2 = dragClientY >= top && dragClientY <= top + height
if (condition1 && condition2) {
callback(true, node)
} else {
callback(false, node)
}
}
}
const dragSource = document.querySelectorAll('.drag-li')
for (let i = 0; i < dragSource.length; i++) {
const item = dragSource[i]
item.addEventListener("mousedown", mousedown);
item.addEventListener("touchstart", mousedown);
}
const dragHoverStyle = { bg: 'rgba(0,0,0,0.3)', co: '#fff' }
function mousedown(event) {
// 鼠标按下时
console.log(event);
let previewNode = null // 模拟拖拽时的预览
const device = getDeviceType()
const dragTarget = event.target
const dropLis = document.querySelectorAll('.drop-li'); // 目标
const body = document.querySelector('body');
previewNode = dragTarget.cloneNode(true) // 克隆一个拖拽元素,模拟拖拽预览
let clientX = device === 'Browser' ? event.clientX : event.touches[0].clientX
let clientY = device === 'Browser' ? event.clientY : event.touches[0].clientY
let offsetX = clientX - dragTarget.getBoundingClientRect().left; // 获取拖拽元素的初始位置,并记录
let offsetY = clientY - dragTarget.getBoundingClientRect().top; // 获取拖拽元素的初始位置,并记录
function drag(event) {
// 拖拽时做些什么
body.appendChild(previewNode) // 拖拽时预览节点放置body显示
const dragClientX = device === 'Browser' ? event.clientX : event.touches[0].clientX
const dragClientY = device === 'Browser' ? event.clientY : event.touches[0].clientY
previewNode.style.position = 'fixed' // 设置预览节为固定定位,跟随鼠标
previewNode.style.opacity = 0.5;
previewNode.style.left = dragClientX - offsetX + "px"; // 拖拽时预览元素跟随鼠标
previewNode.style.top = dragClientY - offsetY + "px"; // 拖拽时预览元素跟随鼠标
dragTarget.style.opacity = 0.5; // 给拖拽源一个样式
findNode(dropLis, { dragClientX, dragClientY }, (is, targetNode) => {
if (is) {
// 拖拽至目标上空时,做些什么
console.log('目标:', targetNode.innerText);
targetNode.style.backgroundColor = dragHoverStyle.bg
targetNode.style.color = dragHoverStyle.co
} else {
targetNode.style.backgroundColor = ''
targetNode.style.color = ''
}
})
}
function stopDrag() {
// 结束拖拽时做些什么
dragTarget.style.opacity = 1;
removeChildNode(body, previewNode)
document.removeEventListener("mousemove", drag);
document.removeEventListener("mouseup", stopDrag);
document.removeEventListener("touchmove", drag);
document.removeEventListener("touchend", stopDrag);
}
document.addEventListener("mousemove", drag);
document.addEventListener("mouseup", stopDrag);
document.addEventListener("touchmove", drag);
document.addEventListener("touchend", stopDrag);
}
至此,兼容pc与移动端的拖拽功能就实现了,感兴趣的也可以将其抽象为一个hook,作为一个小插件来使用,示例仅作为一个实现思路,每个项目的业务都有不同,但核心思路不会改变,可以根据自身需求结合至vue或react扩展。
结尾
这个示例是我单独写的,真实的业务代码较多,业务也比较繁杂,就不做展示,当然,情况允许下,还是建议使用draggable方案来实现,或者引入DnD插件来实现,会比较合适,也节省开发时间,不用我们太过于在意拖拽实现的层面,但是draggable方案也要注意兼容性问题,例如event.dataTransfer在一些环境或浏览器下,有不同的设置,可以查询文档看看。
呼~半夜睡不着,最近有点喜欢上了写作,起来随便发挥一下,有误请在评论区指出,晚安!!!
馒头儿~ 嗷呜~ 馒头儿~ 嗷呜~ 馒头儿~ 嗷呜~ 馒头儿~ 嗷呜~