学学拖拽 - 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

相关推荐
QGC二次开发11 分钟前
Vue3:mitt实现组件通信
前端·javascript·vue.js·vue
Fightting8824 分钟前
Openpyxl 插入数据添加数据
前端·windows·python
only-lucky31 分钟前
QT之QML从入门到精通(第三章)
前端·javascript·qt
anyup_前端梦工厂40 分钟前
探秘 Web Bluetooth API:连接蓝牙设备的新利器
前端·javascript·html
anyup_前端梦工厂1 小时前
深入理解 JavaScript 三大作用域:全局作用域、函数作用域、块级作用域
前端·javascript·html
等你许久_孟然2 小时前
【webpack4系列】设计可维护的webpack4.x+vue构建配置(终极篇)
前端·javascript·vue.js
我的运维人生2 小时前
Nginx:高性能Web服务器与反向代理的深度解析
服务器·前端·nginx·运维开发·技术共享
小白小白从不日白2 小时前
react hooks--useMemo
前端·javascript·react.js
资深前端之路2 小时前
react 创建react项目
前端·javascript·react.js
知否技术3 小时前
vue实现动态Tab标签页功能,10分钟拿下!
前端·vue.js