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

相关推荐
水银嘻嘻6 小时前
12 web 自动化之基于关键字+数据驱动-反射自动化框架搭建
运维·前端·自动化
小嘟嚷ovo6 小时前
h5,原生html,echarts关系网实现
前端·html·echarts
十一吖i6 小时前
Vue3项目使用ElDrawer后select方法不生效
前端
只可远观6 小时前
Flutter目录结构介绍、入口、Widget、Center组件、Text组件、MaterialApp组件、Scaffold组件
前端·flutter
周胡杰7 小时前
组件导航 (HMRouter)+flutter项目搭建-混合开发+分栏效果
前端·flutter·华为·harmonyos·鸿蒙·鸿蒙系统
敲代码的小吉米7 小时前
前端上传el-upload、原生input本地文件pdf格式(纯前端预览本地文件不走后端接口)
前端·javascript·pdf·状态模式
是千千千熠啊7 小时前
vue使用Fabric和pdfjs完成合同签章及批注
前端·vue.js
九月TTS7 小时前
TTS-Web-Vue系列:组件逻辑分离与模块化重构
前端·vue.js·重构
我是大头鸟8 小时前
SpringMVC 内容协商处理
前端
Humbunklung8 小时前
Visual Studio 2022 中添加“高级保存选项”及解决编码问题
前端·c++·webview·visual studio