JS 实现拖拽元素的功能

news2024/12/22 20:58:51

JS 实现拖拽元素的功能

这篇笔记比较短,主要过一遍 draggable 的事件。

首先简单看一下 HTML 实现:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
    <style>
      ul {
        list-style: none;
        border: 1px solid #333;
        padding: 0.5em;
        margin: 1em auto;
        max-width: 720px;
        width: 80%;
      }
      li {
        cursor: grab;
      }
      .card {
        border-radius: 10px;
        border: 1px solid #bbb;
        padding: 0.5em;
        margin: 0.8em;
        box-shadow: 1px 1px 0.5em #ccc;
      }
      .dragging {
        cursor: grabbing;
      }
      .droppable {
        background-color: #ffe0ec;
      }
    </style>
  </head>
  <body>
    <ul>
      <li id="p1" class="card" draggable="true">
        <h2>Title for p1</h2>
        <p>Paragraph body for p1</p>
      </li>
      <li id="p2" class="card" draggable="true">
        <h2>Title for p2</h2>
        <p>Paragraph body for p2</p>
      </li>
    </ul>
    <ul>
      <li id="p3" class="card" draggable="true">
        <h2>Title for p3</h2>
        <p>Paragraph body for p3</p>
      </li>
      <li id="p4" class="card" draggable="true">
        <h2>Title for p4</h2>
        <p>Paragraph body for p4</p>
      </li>
    </ul>
    <script src="./draggable.js"></script>
  </body>
</html>

效果如下:

在这里插入图片描述

这里简单的加了一点 CSS(list 和 card 的部分),其他抓取元素后会出现的悬浮效果全都是浏览器的实现,而实现抓取的功能也挺简单的,就是在想要抓取的元素上添加 draggable="true" 这一属性。

所以接下来要做的,就是绑定正确的事件,并且实现 draggable 中对应的事件。

首先修改一下 css:

<style>
  li {
    cursor: grab;
  }
</style>

这样鼠标在进入的时候就是抓取的样式,这样也提示用户当前元素可以被抓取:

在这里插入图片描述

随后是在 js 里面添加最初的设定,获取所有的 list item,并且绑定事件。绑定事件的部分会在后面一个个实现,所以现在先加一个 placeholder:

const listItems = document.querySelectorAll('li');

listItems.forEach((li) => {});

dragstart

这是一个单独的事件处理,所有的内容会被绑定到 dragstart 的事件下面:

// callback 可以拉出来单独做一个 function
li.addEventListener('dragstart', (e) => {
  // code here
});

dragstart 是开始抓取的这一部分,实现的功能包括:

  1. 更改鼠标光标现实正在抓取的状态

    这一部分依旧通过 js 实现,通过 style 这一属性改变 cursor 的状态

    在这里插入图片描述

    代码为:

    li.style.cursor = 'grabbing';
    

    或者通过 class 名称修改,这需要添加对应的 css:

    li.classList.add('dragging');
    
  2. 更新传输数据

    这里使用的是 DataTransfer 对象,根据 MDN 所说 DataTransfer 是在实现 drag 功能中,用来保存被拖拽的数据的一个对象。

    所以这里主要实现的有两个部分:

    1. 传输当前被选中的 li 的 id
    2. 限定 drag 的操作只有 move
    e.dataTransfer.setData('text/plain', li.id);
    e.dataTransfer.effectAllowed = 'move';
    

drop

drop 需要实现 4 个部分:dragenterdragover, dragleavedrop 。另外,drop 的这个操作也是作用于 ul 之上,而不是 li,这部分的基础代码为:

// drop
const lists = document.querySelectorAll('ul');

lists.forEach((list) => {
  // code here
});

dragenter & dragover

这里实现的功能主要是当被拖拽的部分进入到可被 drop 的地方,那么可被 drop 的部分应该会有一个颜色改变的提示。

list.addEventListener('dragenter', (e) => {
  // check only accept the correct type of data
  if (e.dataTransfer.types[0] === 'text/plain') {
    e.preventDefault();
    list.classList.add('droppable');
  }
});

这里加了一个 check e.dataTransfer.types[0] === 'text/plain',主要也是因为在真实的案例中,很可能会有抓取一些 html、DOM 结点的操作,这里想要确认被拖拽的只是字符串部分。

如果想要实现 drop 的功能,dragover 是个必须要调用 preventDefault 去阻止默认功能的实现:

list.addEventListener('dragover', (e) => {
  // prevent default to allow drop
  if (e.dataTransfer.types[0] === 'text/plain') {
    e.preventDefault();
  }
});

实现后效果如下:

在这里插入图片描述

dragleave

当 item 进入可被 drop 区域时添加的 class,同样也需要在 item 离开该区域时被移除,这一部分就可以在 dragleave 中实现:

list.addEventListener('dragleave', (e) => {
  list.classList.remove('droppable');
});

这时候实现完了会有一个小麻烦,那就是当被拖拽的对象进入另一个子结点的时候,它也算离开了当前结点:

在这里插入图片描述

这一部分的修改具体还是需要依赖实现去完成,这里主要通过寻找最近的 ul,并与当前的 ul 进行判断,如果不是同一个的话就会进行删除:

list.addEventListener('dragleave', (e) => {
  if (e.relatedTarget.closest('ul') !== list)
    list.classList.remove('droppable');
});

drop

drop 的部分就需要获取被拉动的 id,判断当前 list 是否包含对应 id:

  • 是的话停止继续执行
  • 否的话先删除当前元素,并且在对应的 list 中添加被删除的元素

实现如下:

list.addEventListener('drop', (e) => {
  const id = e.dataTransfer.getData('text/plain');
  const listArr = Array.from(list.children);
  if (listArr.find((li) => li.id === id)) return;

  const listItem = document.querySelector(`#${id}`);
  listItem.remove();
  list.appendChild(listItem);
  list.classList.remove('droppable');
});

效果如下:

在这里插入图片描述

完整 JS 代码

// drag
const listItems = document.querySelectorAll('li');

const connectDrag = (e, li) => {
  li.classList.add('dragging');
  e.dataTransfer.setData('text/plain', li.id);
  e.dataTransfer.effectAllowed = 'move';
};

listItems.forEach((li) =>
  li.addEventListener('dragstart', (e) => connectDrag(e, li))
);

// drop
const lists = document.querySelectorAll('ul');

lists.forEach((list) => {
  list.addEventListener('dragenter', (e) => {
    // check only accept the correct type of data
    if (e.dataTransfer.types[0] === 'text/plain') {
      e.preventDefault();
      list.classList.add('droppable');
    }
  });

  list.addEventListener('dragleave', (e) => {
    if (e.relatedTarget.closest('ul') !== list)
      list.classList.remove('droppable');
  });

  list.addEventListener('dragover', (e) => {
    // prevent default to allow drop
    if (e.dataTransfer.types[0] === 'text/plain') {
      e.preventDefault();
    }
  });

  list.addEventListener('drop', (e) => {
    const id = e.dataTransfer.getData('text/plain');
    const listArr = Array.from(list.children);
    if (listArr.find((li) => li.id === id)) return;

    const listItem = document.querySelector(`#${id}`);
    listItem.remove();
    list.appendChild(listItem);
    list.classList.remove('droppable');
  });
});

reference

  • HTMLElement: dragover event
  • DataTransfer: setData() method
  • DataTransfer: effectAllowed property

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【小程序修改说明】分享朋友的,请忽略!

一、把js文件夹复制到根目录&#xff0c;跟pages同一目录 二、把下面的代码放到app.wxss最下面 代码 import "/js/font-awesome.wxss"; import "/js/font-awesome_small.wxss"; 三、ywym.wxml修改 1、ywym.wxml的最上边加上如下代码 <navigator url&…

【Linux】线程详解之线程控制

文章目录 POSIX线程库创建线程线程ID及进程地址空间布局线程等待pthread_join 线程终止pthread_exit函数pthread_cancel函数 线程分离理解pthread库 POSIX线程库 POSIX线程&#xff08;英语&#xff1a;POSIX Threads&#xff0c;常被缩写为Pthreads&#xff09;是POSIX的线程标…

若依源码解析:DataScopeAspect实现数据范围的控制

文章目录 源代码使用场景界面操作SysDeptServiceImplSysUserServiceImplSysUserMapperDataScope定义 代码解析Aspect和Component不同的数据权限类型Before通知处理数据范围的方法 源代码 Aspect Component public class DataScopeAspect {/*** 全部数据权限*/public static fi…

Python潮流周刊#2:Rust让Python再次伟大

△点击上方“Python猫”关注 &#xff0c;回复“1”领取电子书 这里记录每周值得分享的 Python 及通用技术内容&#xff0c;部分为英文&#xff0c;已在小标题注明。&#xff08;本期标题取自其中一则分享&#xff0c;不代表全部内容都是该主题&#xff0c;特此声明。&#xff…

【Linux Network】I/O多路转接之select

目录 1. 初识select 1.1 select函数原型 1.2 理解select执行过程 1.3 socket就绪条件 1.4 select的特点 1.5 select优缺点 2. 基于select的多人聊天程序 server源代码&#xff1a; client的登录&#xff1a; 结果演示&#xff1a; Linux Network&#x1f337; 1. 初识select 系…

C++初阶--C++入门之基础学习

0.前言 C是一门非常好的编程语言&#xff0c;但可能在学习C的过程中会遇到很多困难。人们常说 “一个人走得很快&#xff0c;一群人会走的更远”&#xff0c; 所以就让我们一起攻坚克难&#xff0c;一起征服C吧&#xff01;从本章开始&#xff0c;我们将开始C的基础学习&#x…

Linux简介及基础操作

1.Linux的作用&#xff1a; 商业服务器基本都是linux的、开源软件都先支持linux、大数据分析&#xff0c;机器学习首选linux、整个互联网地基基本由linux支撑起来。如&#xff1a; 生活中的手机是基于linux二次开发的&#xff0c;还有路由器也是基于linux开发的。 2.Linux是什…

acwing提高--多源BFS+最小步数模型+双端队列广搜

多源BFS 1.矩阵距离 题目https://www.acwing.com/problem/content/description/175/ #include<bits/stdc.h> using namespace std; #define x first #define y second typedef pair<int,int> PII; const int N1010; char g[N][N]; int dist[N][N]; PII q[N*N];…

【轻量化网络系列(2)】MobileNetV2论文超详细解读(翻译 +学习笔记+代码实现)

前言 上一篇我们介绍了MobileNetV1&#xff0c;主要是将普通Conv转换为dw和pw&#xff0c;但是在dw中训练出来可能会很多0&#xff0c;也就是depthwise部分得到卷积核会废掉&#xff0c;即卷积核参数大部分为0&#xff0c;因为权重数量可能过少&#xff0c;再加上Relu激活函数…

稳定币是个好生意

* * * 原创&#xff1a;刘教链 * * * 本月早些时候&#xff0c;市值第一的稳定币发行商Tether公布了其一季度的储备和盈利数据[1]。不能说是亮眼&#xff0c;只能说是非常亮眼。就看几个亮点吧&#xff1a; 1. 一季度净利润14.8亿美元&#xff0c;是2022年四季度的两倍多&…

关于Java中的抽象类注意事项

文章目录 &#x1f3c6;文章导读&#x1f342;抽象类的定义&#x1f342;抽象类的特性&#x1f342;总结&#xff1a;面试题普通类和抽象类有哪些区别&#xff1f;抽象类能使用final继承吗&#xff1f; &#x1f3c6;文章导读 在本篇文章中&#xff0c;对抽象类进行了一个详细的…

c++学习——c与c++const修饰的变量的区别

c语言下const修饰的变量 1、c语言下const修饰的变量都有空间 2. c语言的const修饰的全局变量具有外部链接属性 07 const修饰的变量.c #define _CRT_SECURE_NO_WARNINGS #include <stdio.h> #include <string.h> #include <stdlib.h>const int a 10;//常…

1. Linux环境搭建及问题解决方案

本文介绍了Linux环境搭建的过程以及遇到的问题和解决方案&#xff0c;并且介绍了常用的Linux命令. 一、Linux环境搭建 整体所需的环节 安装VMware安装Linux &#xff08;这边我选的是Server版本&#xff09;安装配置Samba&#xff08;Samba是一种Linux和Windows之间进行文件共…

二层环路详解:交换机环路产生的过程和原因

前言&#xff1a; 在了解环路之前得先了解交换机的工作原理&#xff0c;当然交换机的基本工作原理其实非常简单&#xff0c;只有“单播转发与泛洪转发”、“交换机MAC地址表”这两个&#xff01;其他的如vlan&#xff0c;生成树等也是在此基础上增加的&#xff0c;弥补交换机基…

初始Linux的基本操作

上篇博客中&#xff0c;我介绍了关于Linux的相关概念&#xff0c;让我们初步的了解到Linux的重要性&#xff0c;在这篇博客中我会再讲一些Linux操作系统的理解。 一.操作系统 我们知道Linux是一个操作系统&#xff0c;而操作系统操作系统(英语&#xff1a;Operating System&…

[深度好文]10张图带你轻松理解关系型数据库系统的工作原理

[深度好文]10张图带你轻松理解关系型数据库系统的工作原理 原文(欢迎关注)&#xff1a;https://mp.weixin.qq.com/s/CNCfWRpv8QlICGvZkLG4Jw 尽管数据库在我们应用程序中扮演着储存几乎所有状态的关键角色&#xff0c;但人们对其运行原理的了解通常仅停留在较为浅显的层面&…

跟我一起使用 compose 做一个跨平台的黑白棋游戏(4)移植到compose-jb实现跨平台

前言 在上一篇文章中&#xff0c;我们已经实现了游戏的所有界面和逻辑代码&#xff0c;并且在 Android 上已经可以正常运行。 这篇文章我们将讲解如何将其从使用 jetpack compose 修改为使用 compose-jb 从而实现跨平台。 老规矩&#xff0c;先看效果图&#xff1a; 可以看到…

063:cesium设置带边界线材质(material-7)

第063个 点击查看专栏目录 本示例的目的是介绍如何在vue+cesium中设置带边界折线材质,请参考源代码,了解PolylineOutlineMaterialProperty的应用。 直接复制下面的 vue+cesium源代码,操作2分钟即可运行实现效果. 文章目录 示例效果配置方式示例源代码(共89行)相关API参考…

Python-matplotlib中的pie(饼)图

Python-matplotlib中的pie&#xff08;饼&#xff09;图 %matplotlib inline import matplotlib.pyplot as pltm 51212 f 40742 m_perc m/(mf) f_perc f/(mf)colors [navy,lightcoral] labels ["Male","Female"]plt.figure(figsize(8,8)) paches,te…

为什么不胜任的人,反而获得晋升?

作者| Mr.K 编辑| Emma 来源| 技术领导力(ID&#xff1a;jishulingdaoli) 也许你有过这样的经历&#xff0c;自己勤勤恳恳地干活&#xff0c;每个月却只拿着微薄的薪水&#xff0c;有些人明明无法胜任工作&#xff0c;却像坐了火箭一样飞速晋升。这种现象在现实生活中无处不在…