学学拖拽 - draggable(二)排序

76 阅读2分钟

学接上一回,基础API看完了,想实现一个排序功能,再有功能也能实现,就是从box1 拿到 box2 里,然后按照想要的顺序再拿回来。

屏幕录制2023-09-2515.13.31.gif

想了想如果只有一个 box 咋办?琢磨出以下思路:

Pastedimage20230926105657.png

实现效果,代码在最后

屏幕录制2023-09-2611.06.56.gif

加上拖拽时的效果,就是在 dragenterdragleave 上添加和删除类名。

屏幕录制2023-09-2614.55.52.gif

这里有个问题记录下,dragenterdragleave 读不到 dataTransferdragstart 中设置的数据。

HTML5 拖放规范 规定了一个 drag data store mode。这可能会导致预期外的结果,即 DataTransfer.getData() 没有返回预期值。

实现代码

<!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 {
            margin: 5px;
        }

        .block-item-content {
            text-align: center;
            background: blueviolet;
            height: 25px;
            line-height: 25px;
            pointer-events: none;
        }

        .block-item-over::before {
            content: '';
            position: relative;
            display: block;
            width: 100%;
            height: 100%;
        }

        .shadow-child::before {
            content: '';
            display: block;
            height: 20px;
            border: 1px dashed blueviolet;
            background-color: #fff;
            margin-bottom: 5px;
            pointer-events: none;
        }
    </style>
</head>

<body>
    <div id="app">
        <!-- 放置目标 -->
        <div v-for="(v, k) in boxList" class="box" :id="k" :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, index) in boxList[k]" class="block-item" draggable="true" :key="block.name"
                :data-index="index" @dragstart="blockDrapStart($event, block, k)">
                <div class="block-item-content">
                    {{block.name}}
                </div>
            </div>

        </div>
    </div>

    <script>
        const { createApp, reactive, onMounted } = Vue

        createApp({
            setup() {
                // 俩个box,分别包含一个水果
                const boxList = reactive({
                    box1: [{ name: '🍎1' }, { name: '🍎2' }, { name: '🍎3' }],
                    box2: [{ name: '🍐1' }, { name: '🍐2' }, { name: '🍐3' }]
                });

                // 该事件在放置目标上触发
                // 拖动的元素进入一个有效的放置目标时触发
                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");
                    }
                    // 放置虚线框
                    if (!isBox) {
                        event.target.classList.add("shadow-child");
                    }
                }

                // 该事件在放置目标上触发
                // 拖动的元素离开一个有效的放置目标时触发
                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");
                    }
                    // 放置虚线框
                    if (!isBox) {
                        event.target.classList.remove("shadow-child");
                    }
                }

                // 该事件在放置目标上触发
                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.target)
                    const isBox = event.target.classList.contains("box")
                    // 读取数据
                    const { sourceBox, data } = JSON.parse(event.dataTransfer.getData('text/plain'));
                    const sourceIndex = boxList[sourceBox].findIndex(item => item.name === data.name);

                    // 放到了box上,直接放到最后面
                    if (isBox && sourceBox !== box) {
                        boxList[box].push(data);
                        boxList[sourceBox].splice(sourceIndex, 1)
                    }

                    // 放到了水果上
                    if (!isBox) {
                        // 放到哪个水果上了
                        const { index } = event.target.dataset;
                        // 不是放到自己原来的位置 || 不是同一个box
                        if (sourceIndex != index || sourceBox !== box) {
                            // 先放进去
                            boxList[box].splice(index, 0, data);

                            // 删掉原来位置上的
                            if (sourceBox === box) {
                                boxList[box].splice(sourceIndex > index ? sourceIndex + 1 : sourceIndex, 1)
                            } else {
                                boxList[sourceBox].splice(sourceIndex, 1)
                            }
                        }
                        event.target.classList.remove("shadow-child");
                    }

                    if (event.target.classList.contains("dragover")) {
                        event.target.classList.remove("dragover");
                    }
                }

                // 开始拖动元素时调用,在拖拽元素上触发
                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>