• 作者:老汪软件技巧
  • 发表时间: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在一些环境或浏览器下,有不同的设置,可以查询文档看看。

呼~半夜睡不着,最近有点喜欢上了写作,起来随便发挥一下,有误请在评论区指出,晚安!!!

馒头儿~ 嗷呜~ 馒头儿~ 嗷呜~ 馒头儿~ 嗷呜~ 馒头儿~ 嗷呜~