vue3+ts 原生 js drag drop 实现

vue3+ts 原生 js drag drop 实现

一直以来没有涉及的一个领域就是 drag drop 拖动操作,研究了下,实现了,所以写个教程。

官方说明页面及实例:https://developer.mozilla.org/en-US/docs/Web/API/HTML_Drag_and_Drop_API

最终效果:

在这里插入图片描述

一、拖动的 html 结构

在这里插入图片描述
比如我要将右侧的元素拖动到左边,这里 html 有几个组成部分。

  1. 左边绿色的 .target 目标
  2. 右侧蓝色的 .source 源
  3. 右侧内部的 .source-item 待拖动元素

二、标记元素可被拖动

要想拖动某个元素,需要标记这个元素为可拖动元素,如果不标记,在拖动 某个元素的时候,鼠标上并不会实时跟随被拖动的元素到鼠标指针下。
要想实现这样,只需要添加 draggable 属性即可,这个例子里,上面的多个 .source-item 为需要被拖动的元素。

<div class="source-item" draggable="true"></div>                 

在这里插入图片描述

三、最终要实现的结果

这个例子里,我定义了两个数组,一个源数组,一个目标数组。

const sourceArray = ref<Array<number>>([1,2,3,4,5,6])
const targetArray = ref<Array<number>>([])

拖动要实现的功能是,拖动时,将元素从 源数组 中移动到 目标数组 中。

四、拖动过程、对应的事件

这个过程中被拖动元素释放区域 都需要绑定相应的事件,才能完成整个拖动过程。整个过程需要完成好多个事件的响应。

拖动事件是这样进行的:

  1. 拖动元素,(给事件添加绑定这个拖动行为的数据)
  2. 拖到目标区域上方,释放。(获取当前拖动事件的数据,进行下一步操作)

1. 被拖动元素需要响应的事件

被拖动元素需要响应的事件有:

  • ondragstart 拖动开始,在元素被拖动时触发。在这个事件里添加当前对应拖动行为的数据,比如被拖动元素的 Index 等需要的数据
  • ondragend 拖动结束,在元素被释放时触发。这个是取消拖动的操作,本例不作操作。

本例中我在被拖动元素中添加了 data="数据" 的属性,这个在 ondragstart 的时候取用里面的数据,并设置到 .dataTransfer 中。
被拖动元素和最终释放到的元素之间是通过这个 event.dataTransfer 传递数据的。

<div class="source-item"
     :data="item"
     :ondragstart="dragstart"
     :ondragend="dragend"
     draggable="true"
     v-for="item in sourceArray" :key="item">
    <span>item-{{item}}</span>
</div>
/**
 * Drag Item
 */
function dragstart(event: DragEvent){
    let data = (event.target as HTMLElement).getAttribute('data') as string  // 获取 html 里的 data 属性
    event.dataTransfer!.dropEffect = 'copy'   // copy | move | link 不知道干嘛的,好像也没什么效果
    event.dataTransfer!.setData('text/plain', data)  // 添加数据
    console.log('item-drag-start: ')
}
function dragend(event: DragEvent){
    console.log('item-drag-end')
}

这样,关于被拖动元素需要设置的东西就是这些了。
这里先了解一下这个 DragEvent 里都有什么:

在这里插入图片描述

上面在 dataTransfer 里添加的数据,在 console.log() 里是看不到的,但它是在里面的,后面会从这个事件里提取。先看一下是怎样的,这个后面会具体说:

// 设置数据
event.dataTransfer!.setData('text/plain', data)

// 提取数据
const originalData = Number(event.dataTransfer!.getData("text/plain"))

2. 释放区域的事件

拖动释放区域,也就是接收区域 div.target 需要实现的事件是:

  • ondragover 当拖动元素悬于释放区域时,这个事件是连续触发的,每动一个像素都会被触发。
  • ondragenter 当拖动元素进入释放区域时,触发一次,不会连续触发
  • ondragleave 当拖动元素离开释放区域时,触发一次,不会连续触发
  • ondrop 当拖动元素在释放区域释放时,触发一次
<div
    :class="['target', {'is-drag-entered': isDragEntered}]"
    :ondragover="onTargetDragover"
    :ondragenter="onTargetDragenter"
    :ondragleave="onTargetDragLeave"
    :ondrop="handleDrop"
>
    <div class="source-item"
         v-for="item in targetArray" :key="item">
        <span>item-{{item}}</span>
    </div>
</div>
const refTargetZone = ref()
onMounted(()=>{
    nextTick(()=>{
        refTargetZone.value.addEventListener('drop', handleDrop)
        // ondrop 的事件需要以这样的方式添加,直接写到 html 中不生效,不知道为什么
    })
})


/**
 * Drag Target
 */

const isDragEntered = ref(false)  // 实现拖动元素进入释放区域时,改变释放区域的样式,就是为了给个操作反馈

function onTargetDragover(event: DragEvent){
    event.preventDefault()  // 这里就特别注意,这行很关键
}
function onTargetDragenter(event: DragEvent){
    console.log('drag-enter: ')
    isDragEntered.value = true
}
function onTargetDragLeave(event: DragEvent){
    console.log('drag-enter: ',)
    isDragEntered.value = false
}

// 释放拖动的元素到目标区域时
function handleDrop(event: DragEvent){
    isDragEntered.value = false

    event.preventDefault() // 这里就特别注意,这行很关键
    const originalData = Number(event.dataTransfer!.getData("text/plain")) // 取事件中的数据,这个数据是拖动开始时设置的。

    // 数据变化
    // 因为 vue 是数据驱动的,这里只需要操作 源、目标 数据,即可实现页面上界面的变化,
    // 不需要像原生 dom 那样去操作 dom 来实现拖动的变化。
    targetArray.value.push(originalData) // 目标数组添加对应值
    sourceArray.value = sourceArray.value.filter(item => item !== originalData) // 源数组删除对应值

    console.log('--- on drop:', originalData)
}

五、更进一步

上面的例子里传递的是普通字符串,它也可以传递文件什么的,看官方具体是如何操作的。
拖动到某个序列的某个位置,可能就需要对事件的坐标位置进行进一步判断了。

官方说明页面及实例:https://developer.mozilla.org/en-US/docs/Web/API/HTML_Drag_and_Drop_API

六、完整代码

Drag.vue

<template>
    <div class="drag-container">
        <el-row :gutter="50">
            <el-col :span="12">
                <div
                    :class="['target', {'is-drag-entered': isDragEntered}]"
                    :ondragover="onTargetDragover"
                    :ondragenter="onTargetDragenter"
                    :ondragleave="onTargetDragLeave"
                    :ondrop="handleDrop"
                >
                    <div class="source-item"
                         v-for="item in targetArray" :key="item">
                        <span>item-{{item}}</span>
                    </div>
                </div>
            </el-col>
            <el-col :span="12">
                <div class="source">
                    <div class="source-item"
                         :data="item"
                        :ondragstart="dragstart"
                        :ondragend="dragend"
                        draggable="true"
                        v-for="item in sourceArray" :key="item">
                        <span>item-{{item}}</span>
                    </div>
                </div>
            </el-col>
        </el-row>
    </div>
</template>
<script setup lang="ts">
import {ref} from "vue";

const isDragEntered = ref(false)

const sourceArray = ref<Array<number>>([1,2,3,4,5,6])
const targetArray = ref<Array<number>>([])

/**
 * Drag Item
 */
function dragstart(event: DragEvent){
    let data = (event.target as HTMLElement).getAttribute('data') as string
    event.dataTransfer!.dropEffect = 'move'
    event.dataTransfer!.setData('text/plain', data)
    console.log('item-drag-start:' ,event)
}
function dragend(event: DragEvent){
    console.log('item-drag-end')
}

function handleDrop(event: DragEvent){
    isDragEntered.value = false

    event.preventDefault()
    const originalData = Number(event.dataTransfer!.getData("text/plain"))

    // 数据变化
    targetArray.value.push(originalData)
    sourceArray.value = sourceArray.value.filter(item => item !== originalData)

    console.log('--- on drop:', originalData)
}



/**
 * Drag Target
 */
function onTargetDragover(event: DragEvent){
    event.preventDefault()
    // console.log('drag-over: ', event)
}
function onTargetDragenter(event: DragEvent){
    console.log('drag-enter: ')
    isDragEntered.value = true
}

function onTargetDragLeave(event: DragEvent){
    console.log('drag-enter: ',)
    isDragEntered.value = false
}




</script>

<style scoped lang="scss">
.drag-container{
    padding: 30px;
}
.source, .target{
    padding: 20px;
    height: 400px;
    display: flex;
    flex-flow: row wrap;
    -webkit-border-radius: 20px;
    -moz-border-radius: 20px;
    border-radius: 20px;
    border: 2px solid #007AFF;
    background-color: white;
    &:hover{
        border-style: dashed;
    }
    .source-item{
        background-color: white;
        display: flex;
        align-items: center;
        justify-content: center;

        text-transform: uppercase;
        height: 60px;
        width: 100px;
        margin-bottom: 5px;
        margin-right: 5px;
        padding: 10px;
        text-align: center;
        border: 2px solid black;
        &:hover{
            background-color: #4CD964;
            cursor: pointer;
            user-select: none;
        }
    }
}

.source{

}
.target{
    border-color: #4CD964;
    &.is-drag-entered{
        background-color: #4CD964;
    }

}
</style>

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/588501.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

企业计算机服务器中了lockbit勒索病毒如何处理,lockbit勒索病毒解密流程建议

在虚拟的网络世界里&#xff0c;人们利用网络获取信息的方式有很多&#xff0c;网络为众多企业提供了极大便利性&#xff0c;也大大提高了企业生产运营效率&#xff0c;方便企业开展各项工作业务。但随着网络技术的不断发展与应用&#xff0c;越来越多的企业开始关注企业网络数…

Flutter笔记:Widgets Easier组件库(8)使用图片

Flutter笔记 Widgets Easier组件库&#xff08;8&#xff09;&#xff1a;使用图片 - 文章信息 - Author: 李俊才 (jcLee95) Visit me at CSDN: https://jclee95.blog.csdn.netMy WebSite&#xff1a;http://thispage.tech/Email: 291148484163.com. Shenzhen ChinaAddress o…

使用递归函数,将一串数字每位数相加求和

代码结果&#xff1a; #include<stdio.h> int DigitSum(unsigned int n) {if (n > 9)return DigitSum(n / 10) (n % 10);elsereturn n; } int main() {unsigned int n;scanf("%u", &n);int sum DigitSum(n);printf("%d\n", sum);return 0; …

C语言/数据结构——每日一题(合并两个有序链表)

一.前言 嗨嗨嗨&#xff0c;大家好久不见&#xff01;今天我在LeetCode看到了一道单链表题&#xff1a;https://leetcode.cn/problems/merge-two-sorted-lists想着和大家分享一下&#xff0c;废话不多说&#xff0c;让我们开始今天的题目分享吧。 二.正文 1.1题目描述 1.2题…

Javascript:Web APIs(二)

JavaScript&#xff1a;Web APIs&#xff08;一&#xff09; 在上篇文章&#xff0c;我们学习了对BOM对象的一些基本操作&#xff0c;但即使这样&#xff0c;我们也只是能通过js改变元素属性&#xff0c;而不能进行网页的交互效果和动态效果&#xff0c;这时我们就不得不提到事…

Spring Cloud——LoadBalancer

Spring Cloud——LoadBalancer 一、负载均衡&#xff08;LoadBalance&#xff09;1.LoadBalancer本地负载均衡客户端 VS Nginx服务端负载均衡区别 二、LoadBalancer1.Spring RestTemplate as a LoadBalancer Client2.编码使用DiscoveryClient动态获取所有上线的服务列表3.从默认…

QT5之lambda+内存回收机制

使用lambda需要 配置c11 所以在点.pro文件里面配置添加如下 CONFIG c11 使用到qDebug 打印包含头文件 #include<QDebug> lambda 表达式使用 代替槽如下 #include "mainwidget.h" #include<QPushButton> #include<QDebug> mainWidget::mainWid…

探索AI工具的巅峰:个人体验与深度剖析

✨✨ 欢迎大家来访Srlua的博文&#xff08;づ&#xffe3;3&#xffe3;&#xff09;づ╭❤&#xff5e;✨✨ &#x1f31f;&#x1f31f; 欢迎各位亲爱的读者&#xff0c;感谢你们抽出宝贵的时间来阅读我的文章。 我是Srlua小谢&#xff0c;在这里我会分享我的知识和经验。&am…

正点原子[第二期]Linux之ARM(MX6U)裸机篇学习笔记-6.4--汇编LED驱动程序

前言&#xff1a; 本文是根据哔哩哔哩网站上“正点原子[第二期]Linux之ARM&#xff08;MX6U&#xff09;裸机篇”视频的学习笔记&#xff0c;在这里会记录下正点原子 I.MX6ULL 开发板的配套视频教程所作的实验和学习笔记内容。本文大量引用了正点原子教学视频和链接中的内容。…

省级财政收入、支出、第一、二、三产业增加值、工业增加值、金融业增加值占GDP比重数据(1978-2022年)

01、数据介绍 财政收支作为国家治理的基础&#xff0c;越来越受到社会各界的关注。同时&#xff0c;产业结构的优化与升级也是中国经济持续增长的关键因素。本数据对中国省级财政收入、支出占GDP的比重以及第一、二、三产业的增加值占GDP的比重和工业增加值占GDP的比重、金融业…

Python量化炒股的财务因子选股—质量因子选股

Python量化炒股的财务因子选股—质量因子选股 在Python财务因子量化选股中&#xff0c;质量类因子有2个&#xff0c;分别是净资产收益率和总资产净利率。需要注意的是&#xff0c;质量类因子在财务指标数据表indicator中。 净资产收益率&#xff08;roe&#xff09;选股 净资…

创建codereview

创建codereview流程 一、开始创建二、选择分支三、添加细节 一、开始创建 点击codereivew按钮 为新的codereview选择一个工程后点击create review 二、选择分支 选择目标分支和要比对的分支&#xff0c;比如develop 三、添加细节 Add branch后&#xff0c;可以继续Edit …

如何反向查看某个命令所属的rpm包的2个方法?(rpm -qf `which xxx`和yum provides和 rpm -ql xxx.rpm)

文章目录 快速回忆方法1&#xff1a; rpm -qf方法2&#xff1a;yum provides 其他rpm如何查看某个rpm包里面包含哪些命令: rpm -ql主推方法1&#xff1a; rpm -ql方法2&#xff1a;yum info 其他查看rdma-core中包含哪些cmd&#xff1a;一些其他命令所在包探索 快速回忆 rpm -…

C++_set和map的学习

1. 关联式容器 STL中的容器有序列式容器和关联式容器。 其中 vector 、 list 、 deque 、 forward_list(C11)就是序列式容器&#xff0c; 因为其底层为线性序列的数据结构&#xff0c;里面 存储的是元素本身 关联式容器 也是用来存储数据的&#xff0c;与序列式容器不同的是&am…

Word域代码学习(简单使用)-【SEQ】

Word域代码学习(简单使用)-【SEQ】 快捷键 序号快捷键操作1 Ctrl F9 插入域代码花括号2 F9 显示域代码结果3 Shift F9 切换为域代码4 Windows Alt F9 切换全部域代码 域代码说明 域代码不区分大小写在word中&#xff0c;依次选择插入➡文档部件➡域即可选择插入…

2.4Java全栈开发前端+后端(全栈工程师进阶之路)-前端框架VUE3-基础-Vue组件

初识Vue组件 Vue中的组件是页面中的一部分&#xff0c;通过层层拼装&#xff0c;最终形成了一个完整的组件。这也是目前前端最流行的开发方 式。下面是Vue3官方给出的一张图&#xff0c;通过图片能清楚的了解到什么是Vue中的组件。 图的左边是一个网页&#xff0c;网页分为了…

初识Vue-组件化开发(应用实例)

目录 一、任务管理应用 1.介绍 2.代码 1. 任务列表组件 (TaskList.vue) 2. 添加任务组件 (AddTask.vue) 3. 应用入口组件 (App.vue) 4. 主入口文件 (main.js) 3.效果 4.总结 二、购物车 1.介绍 2.代码 1. 商品列表组件 (ProductList.vue) 2. 购物车组件 (Cart.vue…

医疗大模型华佗GPT-2:医学问答超越GPT-4,通过2023年国家执业药师考试

前言 随着人工智能技术的快速发展&#xff0c;特别是在自然语言处理(NLP)领域&#xff0c;大型预训练模型如GPT系列已经显示出在多个领域的强大应用潜力。最近&#xff0c;华佗GPT-2医疗大模型的发布&#xff0c;不仅标志着人工智能在医学领域的一大进步&#xff0c;更是在202…

国产服务器操作系统部署NTP服务 _ 统信UOS _ 麒麟 _ 中科方德

原文链接&#xff1a;国产服务器操作系统部署NTP服务 | 统信UOS | 麒麟 | 中科方德 Hello&#xff0c;大家好啊&#xff01;在保持服务器时间的精确同步方面&#xff0c;时间同步服务器&#xff08;NTP服务器&#xff09;扮演着至关重要的角色&#xff0c;它能确保系统操作的时…

小程序商城|基于Spring Boot的智能小程序商城的设计与实现(源码+数据库+文档)

小程序商城目录 目录 基于Spring Boot的智能小程序商城 一、前言 二、系统设计 三、系统功能设计 1用户信息管理 2 商品信息管理 3公告信息管理 4论坛信息管理 四、数据库设计 五、核心代码 六、论文参考 七、最新计算机毕设选题推荐 八、源码获取&#xff1a; …
最新文章