学学拖拽 - draggable

如图实现了一个demo:把一个水果从一个盒子拿起来,然后放到另一个盒子。总结以下内部。

DataTransfer

DataTransfer 对象用于保存拖动并放下(drag and drop)过程中的数据。它可以保存一项或多项数据,这些数据项可以是一种或者多种数据类型。

可以在拿起水果时记录一些数据(比如从哪个盒子拿的什么水果),并在往另外的盒子里放的时候读取(判断下这个拿能不能放下这种水果)

水果上事件和属性

  1. 属性 draggable ,设置为 "true",可以让它变的可拖拽。
  2. 事件 dragstart,用户开始拖动元素时调用,也可以理解为拿成水果时触发。

盒子上事件

  1. 事件 dragenter,拖动的元素或者进入一个有效的放置目标时触发,也就是拿起水果进入了盒子范围(松手就掉进去了)时触发。
  2. 事件 dragover,拖动的元素或者被选择的文本被拖进一个有效的放置目标时(每几百毫秒)触发。拿着东西在盒子上面晃悠时,盒子每几百毫秒喊会一声。
  3. 事件 dragleave,拖动的元素离开一个有效的放置目标时被触发,和dragenter相反。
  4. 事件 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

相关推荐
黄毛火烧雪下1 小时前
React 深入学习理解
前端·学习·react.js
自不量力的A同学2 小时前
如何下载和安装Firefox 134.0?
前端·firefox
@_猿来如此3 小时前
Web网页制作之JavaScript的应用
前端·javascript·css·html·html5
顾尘眠7 小时前
http常用状态码(204,304, 404, 504,502)含义
前端
王先生技术栈8 小时前
思维导图,Android版本实现
java·前端
悠悠:)9 小时前
前端 动图方案
前端
星陈~9 小时前
检测electron打包文件 app.asar
前端·vue.js·electron
Aatroox9 小时前
基于 Nuxt3 + Obsidian 搭建个人博客
前端·node.js
每天都要进步哦9 小时前
Node.js中的fs模块:文件与目录操作(写入、读取、复制、移动、删除、重命名等)
前端·javascript·node.js
brzhang10 小时前
开源了一个 Super Copy Coder ,0 成本实现视觉搞转提示词,效率炸裂
前端·人工智能