js实现鼠标拖拽多选功能

实现功能
在PC端的H5页面中,客户拖动鼠标可以连选多个选项

效果展示
在这里插入图片描述

具体代码如下

<!DOCTYPE html>
<html>

<head>
	<title>鼠标拖拽多选功能</title>
	<script src="https://cdn.bootcss.com/jquery/1.10.2/jquery.min.js"></script>
	<style type="text/css">
		* {
			box-sizing: border-box;
		}

		ul {
			width: 500px;
			height: auto;
			margin: 0;
			padding: 20px;
			font-size: 0;
			/*需设置定位*/
			position: relative;
		}

		li {
			width: 70px;
			height: 70px;
			padding: 0;
			display: inline-block;
			vertical-align: top;
			font-size: 13px;
			border: 1px solid #d9d9d9;
		}

		#moveSelected {
			position: absolute;
			background-color: blue;
			opacity: 0.3;
			border: 1px dashed #d9d9d9;
			top: 0;
			left: 0;
		}

		.selected {
			background-color: pink;
		}
	</style>
</head>

<body>
	<ul class="list">
		<li>1</li>
		<li>2</li>
		<li>3</li>
		<li>4</li>
		<li>5</li>
		<li>6</li>
		<li>7</li>
		<li>8</li>
		<li>9</li>
		<li>10</li>
		<li>11</li>
		<li>12</li>
		<li>13</li>
		<li>14</li>
		<li>15</li>
		<li>16</li>
		<li>17</li>
		<li>18</li>
		<li>19</li>
		<li>20</li>
		<li>21</li>
		<li>22</li>
		<!-- 鼠标拖拽出的遮罩 (定位为  position:absolute)-->
		<!-- 遮罩最好是在绑定了mouseover事件的元素内部,并且不要阻止遮罩的冒泡事件。这样鼠标移到了遮罩上面,依然可以利用冒泡执行父元素的mouseover事件,就不会出现遮罩只能扩大,不能缩小的情况了(亲自试过) -->
		<div id="moveSelected"></div>
	</ul>
</body>

</html>
<script type="text/javascript">
	$(document).ready(function () {
		let moveSelected = $('#moveSelected')[0];
        console.log(moveSelected);
		let flag = false; //是搜开启拖拽的标志
		let oldLeft = 0; //鼠标按下时的left,top
		let oldTop = 0;
		let selectedList = []; //拖拽多选选中的块集合

		// 鼠标按下时开启拖拽多选,将遮罩定位并展现
		$(".list").mousedown(function (event) {
			flag = true;
			moveSelected.style.top = event.pageY + 'px';
			moveSelected.style.left = event.pageX + 'px';
			oldLeft = event.pageX;
			oldTop = event.pageY;
			event.preventDefault(); // 阻止默认行为
			event.stopPropagation(); // 阻止事件冒泡
		});
		// 鼠标移动时计算遮罩的位置,宽 高
		$(".list").mousemove(function (event) {
			if (!flag) return; //只有开启了拖拽,才进行mouseover操作
			if (event.pageX < oldLeft) { //向左拖
				moveSelected.style.left = event.pageX + 'px';
				moveSelected.style.width = (oldLeft - event.pageX) + 'px';
			} else {
				moveSelected.style.width = (event.pageX - oldLeft) + 'px';
			}
			if (event.pageY < oldTop) { //向上
				moveSelected.style.top = event.pageY + 'px';
				moveSelected.style.height = (oldTop - event.pageY) + 'px';
			} else {
				moveSelected.style.height = (event.pageY - oldTop) + 'px';
			}
			event.preventDefault(); // 阻止默认行为
			event.stopPropagation(); // 阻止事件冒泡
		});
		//鼠标抬起时计算遮罩的right 和 bottom,找出遮罩覆盖的块,关闭拖拽选中开关,清除遮罩数据
		$(".list").mouseup(function (event) {
			moveSelected.style.bottom = Number(moveSelected.style.top.split('px')[0]) + Number(
				moveSelected.style.height.split('px')[0]) + 'px';
			moveSelected.style.right = Number(moveSelected.style.left.split('px')[0]) + Number(
				moveSelected.style.width.split('px')[0]) + 'px';
			findSelected();
			flag = false;
			clearDragData();
			event.preventDefault(); // 阻止默认行为
			event.stopPropagation(); // 阻止事件冒泡
		});
		$(".list").mouseleave(function (event) {
			flag = false;
			moveSelected.style.width = 0;
			moveSelected.style.height = 0;
			moveSelected.style.top = 0;
			moveSelected.style.left = 0;
			event.preventDefault(); // 阻止默认行为
			event.stopPropagation(); // 阻止事件冒泡
		});

		function findSelected() {
			let blockList = $('.list').find('li');
			for (let i = 0; i < blockList.length; i++) {
				//计算每个块的定位信息
				let left = $(blockList[i]).offset().left;
				let right = $(blockList[i]).width() + left;
				let top = $(blockList[i]).offset().top;
				let bottom = $(blockList[i]).height() + top;

				let leftTwo = moveSelected.style.left.split('px')[0]
				let rightTwo = moveSelected.style.right.split('px')[0]
				let topTwo = moveSelected.style.top.split('px')[0]
				let bottomTwo = moveSelected.style.bottom.split('px')[0]

				// 判断碰撞
				if (!(top > bottomTwo || right < leftTwo || bottom < topTwo || left> rightTwo)) {
					// 碰撞的情况
					selectedList.push(blockList[i]);
					$(blockList[i]).addClass('selected');
				}
			}
			console.log(selectedList);
		}

		function clearDragData() {
			moveSelected.style.width = 0;
			moveSelected.style.height = 0;
			moveSelected.style.top = 0;
			moveSelected.style.left = 0;
			moveSelected.style.bottom = 0;
			moveSelected.style.right = 0;
		}
	});
</script>

考虑方便使用Vue框架的情况,新增一个Vue 3的参考代码,且如果多选时处于已经选择状态也会取消选择

//
// MutiSelectPage
// mrs-console-ui
//
// Created by gaolailong on 2024/05/23.
// Copyright © 2024 上海复微迅捷数字科技股份有限公司. All rights reserved.
//

<template>
    <div class="page">
        <div class="list" @mousedown="handleMouseDown" @mousemove="handleMouseMove" @mouseup="handleMouseUp"
            @mouseleave="handleMouseLeave">
            <template v-for="(item, index) in list" :key="index">
                <div class="can-select-div" :class="item.selected ? 'selected' : ''" :data-index="index">{{ item.text }}
                </div>
            </template>
            <!-- 鼠标拖拽出的遮罩 (定位为  position:absolute)-->
            <!-- 遮罩最好是在绑定了mouseover事件的元素内部,并且不要阻止遮罩的冒泡事件。这样鼠标移到了遮罩上面,依然可以利用冒泡执行父元素的mouseover事件,就不会出现遮罩只能扩大,不能缩小的情况了(亲自试过) -->
            <div id="moveSelected"></div>
        </div>
    </div>
</template>

<script lang='ts'>
import { defineComponent, onMounted, reactive } from 'vue'
export default defineComponent({
    name: 'MutiSelectPage',
    setup() {

        let moveSelected: HTMLElement | null = null
        onMounted(() => {
            moveSelected = document.getElementById('moveSelected') as HTMLElement;
        })

        const list: Array<Record<string, unknown>> = reactive([])
        for (let index = 0; index < 10; index++) {
            const listItem = {
                text: index,
                selected: false
            }
            list.push(listItem)
        }

        let flag = false; //是搜开启拖拽的标志
        let oldLeft = 0; //鼠标按下时的left,top
        let oldTop = 0;
        const handleMouseDown = (event: any) => {
            // 处理鼠标按下事件
            console.log('鼠标按下');
            if (!moveSelected) return;
            flag = true;
            moveSelected.style.top = event.pageY + 'px';
            moveSelected.style.left = event.pageX + 'px';
            oldLeft = event.pageX;
            oldTop = event.pageY;
            event.preventDefault(); // 阻止默认行为
            event.stopPropagation(); // 阻止事件冒泡
        }
        const handleMouseMove = (event: any) => {
            // 处理鼠标移动事件的逻辑
            if (!moveSelected) return;
            if (!flag) return; //只有开启了拖拽,才进行mouseover操作
            console.log("鼠标移动了");
            if (event.pageX < oldLeft) { //向左拖
                moveSelected.style.left = event.pageX + 'px';
                moveSelected.style.width = (oldLeft - event.pageX) + 'px';
            } else {
                moveSelected.style.width = (event.pageX - oldLeft) + 'px';
            }
            if (event.pageY < oldTop) { //向上
                moveSelected.style.top = event.pageY + 'px';
                moveSelected.style.height = (oldTop - event.pageY) + 'px';
            } else {
                moveSelected.style.height = (event.pageY - oldTop) + 'px';
            }
            event.preventDefault(); // 阻止默认行为
            event.stopPropagation(); // 阻止事件冒泡
        }
        const handleMouseUp = (event: any) => {
            if (!moveSelected) return;
            // 处理鼠标弹起事件
            console.log('鼠标弹起');
            moveSelected.style.bottom = Number(moveSelected.style.top.split('px')[0]) + Number(
                moveSelected.style.height.split('px')[0]) + 'px';
            moveSelected.style.right = Number(moveSelected.style.left.split('px')[0]) + Number(
                moveSelected.style.width.split('px')[0]) + 'px';
            findSelected();
            flag = false;
            clearDragData();
            event.preventDefault(); // 阻止默认行为
            event.stopPropagation(); // 阻止事件冒泡
        }
        const handleMouseLeave = (event: any) => {
            console.log('鼠标离开元素');
            if (!moveSelected) return;
            flag = false;
            moveSelected.style.width = '0';
            moveSelected.style.height = '0';
            moveSelected.style.top = '0';
            moveSelected.style.left = '0';
            event.preventDefault(); // 阻止默认行为
            event.stopPropagation(); // 阻止事件冒泡
        }

        function findSelected() {
            if (!moveSelected) return;
            let leftTwo = Number(moveSelected.style.left.split('px')[0])
            let rightTwo = Number(moveSelected.style.right.split('px')[0])
            let topTwo = Number(moveSelected.style.top.split('px')[0])
            let bottomTwo = Number(moveSelected.style.bottom.split('px')[0])
            // 使用ref获取dom有一个奇怪的现象,手动切换按天/按周时,ref获取到的值不会丢失之前的数据。除非点击外部的“确认查询”按钮。
            const blockList = document.getElementsByClassName('can-select-div')
            // console.log(blockList) // 访问DOM元素
            for (let i = 0; i < blockList.length; i++) {
                //计算每个块的定位信息
                let left = (blockList[i] as HTMLDivElement).offsetLeft;
                let right = (blockList[i] as HTMLDivElement).offsetWidth + left;
                let top = (blockList[i] as HTMLDivElement).offsetTop;
                let bottom = (blockList[i] as HTMLDivElement).offsetHeight + top;

                // 通过比较两个矩形(用 aDiv 和 bDiv 表示)的位置信息来判断它们是否发生碰撞。以下是该碰撞函数的实现思路:
                // 首先,通过获取 aDiv 和 bDiv 的位置信息,分别计算它们的上边界(t1 和 t2)、右边界(r1 和 r2)、下边界(b1 和 b2)以及左边界(l1 和 l2)。
                // 接下来,通过比较这些边界信息,判断两个矩形是否发生碰撞。碰撞的情况可以通过以下四个条件中的任意一个来判断:
                // 如果矩形 aDiv 的上边界大于矩形 bDiv 的下边界,说明 aDiv 在 bDiv 的上方,没有碰撞。
                // 如果矩形 aDiv 的右边界小于矩形 bDiv 的左边界,说明 aDiv 在 bDiv 的左侧,没有碰撞。
                // 如果矩形 aDiv 的下边界小于矩形 bDiv 的上边界,说明 aDiv 在 bDiv 的下方,没有碰撞。
                // 如果矩形 aDiv 的左边界大于矩形 bDiv 的右边界,说明 aDiv 在 bDiv 的右侧,没有碰撞。
                // 如果上述条件中的任意一个不满足,那么矩形 aDiv 和 bDiv 就发生了碰撞,函数返回 true,否则返回 false 表示没有碰撞。
                // 判断碰撞
                if (!(top > bottomTwo || right < leftTwo || bottom < topTwo || left > rightTwo)) {
                    // 碰撞的情况
                    console.log('碰撞了');
                    const itemIndex = (blockList[i] as HTMLDivElement).dataset.index
                    if (!itemIndex) {
                        return
                    }
                    const item = list[Number(itemIndex)]
                    item.selected = !item.selected
                    list.splice(Number(itemIndex), 1, item)
                }
            }

        }

        const clearDragData = () => {
            if (!moveSelected) return;
            moveSelected.style.width = '0';
            moveSelected.style.height = '0';
            moveSelected.style.top = '0';
            moveSelected.style.left = '0';
            moveSelected.style.bottom = '0';
            moveSelected.style.right = '0';
        }

        return {
            handleMouseDown,
            handleMouseMove,
            handleMouseUp,
            handleMouseLeave,
            list,
        }
    }
})
</script>

<style scoped>
.page {
    box-sizing: border-box;
}

.can-select-div {
    width: 70px;
    height: 70px;
    padding: 0;
    display: inline-block;
    vertical-align: top;
    font-size: 13px;
    border: 1px solid #d9d9d9;
}

#moveSelected {
    position: absolute;
    background-color: blue;
    opacity: 0.3;
    border: 1px dashed #d9d9d9;
    top: 0;
    left: 0;
}

.selected {
    background-color: pink;
}
</style>

参考1、参考2

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

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

相关文章

【Text2SQL 经典模型】TypeSQL

论文&#xff1a;TypeSQL: Knowledge-Based Type-Aware Neural Text-to-SQL Generation ⭐⭐⭐ Code: TypeSQL | GitHub 一、论文速读 本论文是在 SQLNet 网络上做的改进&#xff0c;其思路也是先预先构建一个 SQL sketch&#xff0c;然后再填充 slots 从而生成 SQL。 论文发…

Epson推出多摄像头接口芯片S2D13P04

目前&#xff0c;汽车电子的发展日新月异&#xff0c;汽车显示控制器也更多地采用芯片集成化设计的趋势伴随着目前ADAS系统的发展&#xff0c;大量的摄像头和雷达传感器应用到汽车电子中。传统的系统集成方案通常采用多通道电路来处理多摄像头信号输入的方案&#xff0c;但是车…

Oracle体系结构初探:数据库启动与停止

往期内容 参数管理 控制文件添加 启动 在启动Oracle数据库时&#xff0c;我们一般会使用如下命令&#xff1a; startup 虽然命令只有一个&#xff0c;但其中却是经历了3个阶段&#xff0c;从下面执行 startup 命令返回也可以看出来。 总结为3个阶段&#xff1a; nomount&…

正点原子[第二期]Linux之ARM(MX6U)裸机篇学习笔记-22讲 RTC 时钟设置

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

GPT‑4o普通账户也可以免费用

网址 https://chatgpt.com/ 试了一下&#xff0c;免费的确实显示GPT‑4o的模型&#xff0c;问了一下可以联网&#xff0c;不知道能不能通过插件出图 有兴趣的可以试试

测试环境KDE组件漏洞修复

yarn白名单方案 解决漏洞: 方案: 此方式主要使用iptables的功能,对yarn的resourceManager服务的8088端口进行访问ip限制,目的限制8088端口只允许集群内ip访问。 #分别在两台resourceManager节点执行以下步骤ssh kde-offline1 #安装iptables服务,并启动设置为开机自启 yum …

【Android】WorkManager(章二)

剩余的三部分 官方文档 案例 实现下载器&#xff0c;并监听下载进度 界面 定义Worker 在官方案例的前提下&#xff0c;进行完善 下载download 下载进度 授予权限 开始工作并监听 完整代码 MainActivity.java package com.test.downloadworkerapplication;import static…

键盘盲打是练出来的

键盘盲打是练出来的&#xff0c;那该如何练习呢&#xff1f;很简单&#xff0c;看着屏幕提示跟着练。屏幕上哪里有提示呢&#xff1f;请看我的截屏&#xff1a; 截屏下方有8个带字母的方块按钮&#xff0c;这个就是提示&#xff0c;也就是我们常说的8个基准键位&#xff0c;我…

Redis学习篇2:Redis在IEDA中的应用

本文继上文开始讲述了Redis在IDEA中如何应用以及集成进入spring开发环境&#xff0c;以及如何使用Redis客户端。上一个文章&#xff1a;Redis学习篇1&#xff1a;初识Redishttps://blog.csdn.net/jialuosi/article/details/139057088 一、Redis在java中的客户端 二、SpringDat…

通联支付API集成(适用于SpringBoot)

目标&#xff1a; 学习如何使用Java与通联支付API进行交互 实现一个简单的支付下单和查询订单状态的示例 所需材料&#xff1a; 通联支付API文档 官方文档https://aipboss.allinpay.com/know/devhelp/main.php?pid38#mid313 通联支付加签代码SybUtil package com.allin…

从GPT-4o和Google I/O看AI技术变革与未来趋势

引言 在科技界的激烈竞争中&#xff0c;OpenAI 和 Google 这两大巨头不断推出新技术&#xff0c;引领着人工智能的发展。最近&#xff0c;OpenAI 发布了 GPT-4o&#xff0c;恰逢 Google I/O 大会前一天&#xff0c;这一发布会似乎有意“截胡” Google I/O。今天&#xff0c;我…

python实现520表白图案

今天是520哦&#xff0c;作为程序员有必要通过自己的专业知识来向你的爱人表达下你的爱意。那么python中怎么实现绘制520表白图案呢&#xff1f;这里给出方法&#xff1a; 1、使用图形库&#xff08;如turtle&#xff09; 使用turtle模块&#xff0c;你可以绘制各种形状和图案…

go 爬虫之 colly 简单示例

1. 背景 colly 是 Go 实现的比较有名的一款爬虫框架&#xff0c;而且 Go 在高并发和分布式场景的优势也正是爬虫技术所需要的。它的主要特点是轻量、快速&#xff0c;设计非常优雅&#xff0c;并且分布式的支持也非常简单&#xff0c;易于扩展。 2. 官方文档 https://go-col…

大模型的实践应用24-LLaMA-Factory微调通义千问qwen1.5-1.8B模型的实例

大家好,我是微学AI,今天给大家介绍一下大模型的实践应用24-LLaMA-Factory微调通义千问qwen1.5-1.8B模型的实例, LLaMA-Factory是一个专门用于大语言模型微调的框架,它支持多种微调方法,如LoRA、QLoRA等,并提供了丰富的数据集和预训练模型,便于用户进行模型微调。通义千问…

15:00面试,15:08就出来了,问的问题有点变态。。。

从小厂出来&#xff0c;没想到在另一家公司又寄了。 到这家公司开始上班&#xff0c;加班是每天必不可少的&#xff0c;看在钱给的比较多的份上&#xff0c;就不太计较了。没想到8月一纸通知&#xff0c;所有人不准加班&#xff0c;加班费不仅没有了&#xff0c;薪资还要降40%…

mysql 慢查询

一、开启mysql自己的慢查询日志 1、查看慢日志参数设置 show VARIABLES like %query% 2、修改配置文件 vim /etc/my.cnf [mysqld] slow_query_log 1 #是否开启慢查询日志记录 long_query_time 1 #慢查询时间&#xff0c;单位秒 #log_outputtable,file #慢查询…

学习Thymeleaf时遇到的问题

使用idea创建web项目&#xff0c;启动服务器后无法访问页面 原因是tomcat 新版本引用包名改变 由javax变为jakarta 解决办法1 把项目的poe.xml文件由 改为 解决办法2 新建项目时选择新版本&#xff0c;但是新版本不支持thymeleaf

基础2 JAVA图形编程桌面:探索图形程序的抽象实现

嘿&#xff0c;大家好&#xff01;我非常高兴又一次有机会与大家相聚&#xff0c;分享新的知识和经验。对于热爱编程和探索新技术的朋友们来说&#xff0c;今天的内容绝对不容错过。我为大家准备了一个详尽的视频教程&#xff1a;《基础2 JAVA 图形编程&#xff1a;主程序调用…

word-表格疑难杂症诊治

一、用表格进行排版图片、制作公文头 可以在插入图片时固定列宽 二、表格中的疑难杂症 问题一&#xff1a;表格超过页面&#xff0c;右侧文字看不见 解决&#xff1a;表格窗口-布局-自动调整-根据窗口自动调整表格 问题二&#xff1a;表格底部文字被遮挡 解决&#xff1a;布…

sheng的学习笔记-AI-EM算法

AI学习笔记目录&#xff1a;sheng的学习笔记-AI目录-CSDN博客 目录 基础知识 什么是EM算法 EM算法简介 数学知识 极大似然估计 问题描述 用数学知识解决现实问题 最大似然函数估计值的求解步骤 Jensen不等式 定义 EM算法详解 问题描述 EM算法推导流程 EM算法流程…