js实现鼠标拖拽多选功能

news2024/12/30 3:15:04

实现功能
在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

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

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

相关文章

ArcGIS基础操作-ArcGIS插值交叉验证方法与流程

ArcGIS基础操作-ArcGIS插值交叉验证方法与流程 交叉验证(Cross-validation)主要用于建模应用中&#xff0c;例如PCR、PLS回归建模中。在给定的建模样本中&#xff0c;拿出大部分样本进行建模型&#xff0c;留小部分样本用刚建立的模型进行预报&#xff0c;并求这小部分样本的预…

自己手写一个字符串【C风格】

//字符串的常见操作 #include <iostream>#define MAX_SIZE 15 #define OK 1 #define ERROR 0 #define TRUE 1 #define FALSE 0 typedef int Status;//状态类型 typedef char ElemType;//元素类型typedef ElemType String[MAX_SIZE 1];//第一个字节记录长度//***tring是数…

华为设备WLAN配置之AP上线

WLAN基础配置之AP上线 配置WLAN无线网络的第一阶段&#xff0c;AP上线技术&#xff1a; 实验目标&#xff1a;使得AP能够获得来自AC的DHCP地址服务的地址&#xff0c;且是该网段地址池中的IP。 实验步骤&#xff1a; 1.把AC当作三层交换机配置虚拟网关 sys Enter system view,…

2024年艺术鉴赏与文化传播国际会议(AACC 2024)

2024年艺术鉴赏与文化传播国际会议&#xff08;AACC 2024&#xff09; 2024 International Conference on Art Appreciation and Cultural Communication 【重要信息】 大会地点&#xff1a;贵阳 大会官网&#xff1a;http://www.icaacc.com 投稿邮箱&#xff1a;icaaccsub-co…

webpack5零基础入门-16封装cssloader函数

1.背景 我们发现配的cssloader中有很多重复性代码&#xff0c;所以应该对其进行封装&#xff0c;减少冗余的代码 2.定义函数getCssLoader function getCssLoader(pre) {return [MiniCssExtractPlugin.loader, css-loader,{loader: postcss-loader,options: {postcssOptions:…

【笔记】树(Tree)

一、树的基本概念 1、树的简介 之前我们都是在谈论一对一的线性数据结构&#xff0c;可现实中也有很多一对多的情况需要处理&#xff0c;所以我们就需要一种能实现一对多的数据结构--“树”。 2、树的定义 树&#xff08;Tree&#xff09;是一种非线性的数据结构&#xff0…

Excel/WPS《超级处理器》同类项处理,合并同类项与拆分同类项目

在工作中处理表格数据&#xff0c;经常会遇到同类项处理的问题&#xff0c;合并同类项或者拆分同类项&#xff0c;接下来介绍使用超级处理器工具如何完成。 合并同类项 将同一列中的相同内容合并为一个单元格。 1&#xff09;用分隔符号隔开 将AB列表格&#xff0c;合并后为…

UML中的图-13中UML图详解

图是一组元素的图形表示&#xff0c;大多数情况下把图画成顶点和弧的联通图。 UML提供了13种图&#xff0c;分别是类图、对象图、用例图、序列图、通信图、状态图、活动图、构建图、组合结构图、部署图、包图、交互概览图和计时图。序列图、通信图、交互概览图和计时图均被称为…

vue3的核心API功能:computed()API使用

常规使用方法: 这样是常规使用方法. 另一种使用方法: 这样分别定义computed的get回调函数和set回调函数, 上面例子定义了plusOne.value的值为1, 那么这时候就走了computed的set回调函数,而没有走get回调函数. 当我们打印plusOne.value的值的时候,走的是get的回调函数而不是…

ubuntu20.04 10分钟搭建无延迟大疆无人机多线程流媒体服务器

1.使用效果 无人机画面 2.服务器视频端口 3.使用教程 3.1.下载ubuntu对应软件包&#xff1a;系统要求ubuntu16以上 3.2修改端口&#xff08;config.xml文件&#xff09; 3.3启动服务 目录下输入&#xff1a;终端启动&#xff1a;./smart_rtmpd 后台启动&#xff1a;nohup ./…

大语言模型的工程技巧(一)——GPU计算

相关说明 这篇文章的大部分内容参考自我的新书《解构大语言模型&#xff1a;从线性回归到通用人工智能》&#xff0c;欢迎有兴趣的读者多多支持。 本文涉及到的代码链接如下&#xff1a;regression2chatgpt/ch07_autograd/gpu.ipynb 本文将讨论如何利用PyTorch实现GPU计算。本…

dubbo复习: (6)和springboot集成时的条件路由

根据指定的条件&#xff0c;对不满足条件的请求进行拦截。 比如拦截ip地址为192.168.31.227的请求。只需要在dubbo admin中的条件路由菜单创建相应的规则 enabled: true force: true runtime: true conditions:- host ! 192.168.31.227

单例模式介绍,及其应用场景?

单例模式(Singleton Pattern)是 Java中最简单的设计模式之一,此模式保证某个类在运行期间,只有一个实例对外提供服务&#xff0c;而这个类被称为单例类。 单例模式也比较好理解&#xff0c;比如一个人一生当中只能有一个真实的身份证号&#xff0c;一个国家只有一个政府&#x…

【linux】深入了解线程池:基本概念与代码实例(C++)

文章目录 1. 前言1.1 概念1.2 应用场景1.3 线程池的种类1.4 线程池的通常组成 2. 代码示例2.1 log.hpp2.2 lockGuard.hpp① pthread_mutex_t 2.3 Task.hpp2.4 thread.hpp2.5 threadPool.hpp① 基本框架② 成员变量③ 构造函数④ 其余功能函数&#xff1a; main.cc结果演示 完整…

[Redis]常见数据和内部编码

相关命令 type (key) type 命令实际返回的就是当前键的数据结构类型&#xff0c;它们分别是&#xff1a;string&#xff08;字符串&#xff09;、list&#xff08;列 表&#xff09;、hash&#xff08;哈希&#xff09;、set&#xff08;集合&#xff09;、zset&#xff08;有…

Cloneable 接口和深拷贝,浅拷贝

目录 一.Cloneable 接口 二.浅拷贝 三.深拷贝 四.comparable接口、 五.comparator接口 1.Java 中内置了一些很有用的接口 , Cloneable 就是其中之一 . Object 类中存在一个 clone 方法 , 调用这个方法可以创建一个对象的 " 拷贝 ". 2.来说说调用 clone 方法…

基于大型语言模型的游戏智能体

在人工智能领域&#xff0c;游戏代理的发展对于实现通用人工智能&#xff08;AGI&#xff09;至关重要。近年来&#xff0c;大型语言模型&#xff08;LLMs&#xff09;及其多模态版本&#xff08;MLLMs&#xff09;的进展为游戏代理的进化和能力提升提供了前所未有的机遇。这些…

Django自定义模板标签与过滤器

title: Django自定义模板标签与过滤器 date: 2024/5/17 18:00:02 updated: 2024/5/17 18:00:02 categories: 后端开发 tags: Django模版自定义标签过滤器开发模板语法Python后端前端集成Web组件 Django模板系统基础 1. Django模板语言概述 Django模板语言&#xff08;DTL&…

Elasticsearch 分析器的高级用法二(停用词,拼音搜索)

Elasticsearch 分析器的高级用法二&#xff08;停用词&#xff0c;拼音搜索&#xff09; 停用词简介停用词分词过滤器自定义停用词分词过滤器内置分析器的停用词过滤器注意&#xff0c;有一个细节 拼音搜索安装使用相关配置 停用词 简介 停用词是指&#xff0c;在被分词后的词…