当前位置: 首页 > news >正文

Vue鼠标右键画矩形和Ctrl按键多选组件

效果图

效果图

说明

下面会贴出组件代码以及一个Demo,上面的效果图即为Demo的效果,建议直接将两份代码拷贝到自己的开发环境直接运行调试。

组件代码

<template><!-- 鼠标画矩形选择对象 --><div class="objects" ref="objectsRef" @mousedown="handleMouseDown"><!-- 矩形选择框 --><divclass="mask"ref="maskRef"v-show="maskPosition.show":style="'width:' +maskWidth +'left:' +maskLeft +'height:' +maskHeight +'top:' +maskTop"/><!-- 选择对象内容的目标插槽 --><slot name="selcetObject" /></div>
</template><script lang="ts" setup>
import { reactive, toRefs, ref, computed } from "vue";const props = withDefaults(defineProps<{objectClassName: string; // 选择对象的class name,用于定义如何获取对象objectIdName: string; // 选择对象的id name,用于定义如何获取对象的idselectObjectIds?: Array<string>; // 选中的对象IDselectObjects?: Array<HTMLElement>; // 选中的对象useCtrlSelect?: boolean; // 是否支持按住Ctrl多选}>(),{useCtrlSelect: true // 默认支持按住Ctrl多选}
);const objectsRef = ref();
const maskRef = ref();
const emits = defineEmits(["update:selectObjects", "update:selectObjectIds"]);
const state = reactive({maskPosition: {show: false,startX: 0,startY: 0,endX: 0,endY: 0}, // 矩形框位置isPressCtrlKey: false // 是否按下了Ctrl键
});
const { maskPosition, isPressCtrlKey } = toRefs(state);// 若支持按住Ctrl多选,监听Ctrl事件
if (props.useCtrlSelect) {// 释放document.addEventListener("keyup", event => {if (event.keyCode === 17) {isPressCtrlKey.value = false;}});// 按下document.addEventListener("keydown", event => {if (event.keyCode === 17) {isPressCtrlKey.value = true;}});
}/** 鼠标按下 */
const handleMouseDown = event => {// 展示矩形框,通过坐标位置来画出矩形maskPosition.value.show = true;maskPosition.value.startX = event.clientX;maskPosition.value.startY = event.clientY;maskPosition.value.endX = event.clientX;maskPosition.value.endY = event.clientY;// 监听鼠标移动事件和抬起离开事件objectsRef.value.addEventListener("mousemove", handleMouseMove);objectsRef.value.addEventListener("mouseup", handleMouseUp);
};/** 鼠标移动 */
const handleMouseMove = event => {maskPosition.value.endX = event.clientX;maskPosition.value.endY = event.clientY;
};/** 鼠标抬起离开 */
const handleMouseUp = () => {// 移除鼠标监听事件objectsRef.value.removeEventListener("mousemove", handleMouseMove);objectsRef.value.removeEventListener("mouseup", handleMouseUp);maskPosition.value.show = false;handleResetMaskPosition();handleGetSelectObject();
};/** 获取选择的对象 */
const handleGetSelectObject = () => {// 选中对象ID和对象元素let tempSelectObjectIds: Array<string> = [];let tempSelectObjects: Array<HTMLElement> = [];// 如果按下了Ctrl键,之前选择的数据不清空if (isPressCtrlKey.value) {tempSelectObjectIds =props.selectObjectIds === undefined ? [] : props.selectObjectIds;tempSelectObjects =props.selectObjects === undefined ? [] : props.selectObjects;}// 获取鼠标画出的矩形框位置const rectanglePosition = maskRef.value.getClientRects()[0];// 获取所有选择区域的对象; 这里获取的元素的方式定义于父组件的objectClassNameconst selectedObjects = objectsRef.value.querySelectorAll(`.${props.objectClassName}`);// 遍历对象,获取到每个对象的坐标位置,判断该位置是否在上面获取到的鼠标画矩形的框的位置中selectedObjects.forEach(item => {const objectPosition = item.getClientRects()[0];// 这里获取的id的方式定义于父组件的objectIdNameif (compareObjectPosition(objectPosition, rectanglePosition)) {const id = item.getAttribute(props.objectIdName);// 如果按下了Ctrl键if (isPressCtrlKey.value) {// 已被选中的需要被取消选中if (tempSelectObjectIds.includes(id)) {tempSelectObjectIds = tempSelectObjectIds.filter(a => a != id);tempSelectObjects = tempSelectObjects.filter(a => a != item);} else {tempSelectObjectIds.push(id);tempSelectObjects.push(item);}} else {tempSelectObjectIds.push(id);tempSelectObjects.push(item);}}});// 回传到父组件emits("update:selectObjects", tempSelectObjects);emits("update:selectObjectIds", tempSelectObjectIds);
};/*** 判断对象坐标是否在鼠标画出的矩形框坐标位置内* @param objectPosition 对象坐标位置* @param rectanglePosition 鼠标画出的矩形框坐标位置*/
const compareObjectPosition = (objectPosition, rectanglePosition) => {const maxX = Math.max(objectPosition.x + objectPosition.width,rectanglePosition.x + rectanglePosition.width);const maxY = Math.max(objectPosition.y + objectPosition.height,rectanglePosition.y + rectanglePosition.height);const minX = Math.min(objectPosition.x, rectanglePosition.x);const minY = Math.min(objectPosition.y, rectanglePosition.y);return (maxX - minX <= objectPosition.width + rectanglePosition.width &&maxY - minY <= objectPosition.height + rectanglePosition.height);
};/** 重置鼠标位置 */
const handleResetMaskPosition = () => {maskPosition.value.startX = 0;maskPosition.value.startY = 0;maskPosition.value.endX = 0;maskPosition.value.endY = 0;
};/** 通过鼠标位置实时计算矩形框大小 */
const maskWidth = computed(() => {return `${Math.abs(maskPosition.value.endX - maskPosition.value.startX)}px;`;
});
const maskHeight = computed(() => {return `${Math.abs(maskPosition.value.endY - maskPosition.value.startY)}px;`;
});
const maskLeft = computed(() => {return `${Math.min(maskPosition.value.startX, maskPosition.value.endX)}px;`;
});
const maskTop = computed(() => {return `${Math.min(maskPosition.value.startY, maskPosition.value.endY)}px;`;
});
</script><style scoped lang="scss">
.objects {height: 100%;width: 100%;overflow-y: auto;.mask {position: fixed;background: #409eff;opacity: 0.4;z-index: 100;}
}
</style>

Demo

建议直接将上面组件命名为 MouseDrawRectangle

<template><!------------- 鼠标画矩形选择对象组件DEMO,可以直接拷贝到你的页面去运行-----------------------><div class="content"><!-- MouseDrawRectangle说明:objectClassName绑定到下面对象class名称; objectIdName名称对应object_id;useCtrlSelect默认是打开的,用于按住Ctrl键进行多选,以及取消已选择的对象。selectObjectIds会实时从子组件更新过来,监听它的值来控制页面的选择状态即可。另外有参数selectObjects会实时从子组件传回被选中的对象Dom信息--><MouseDrawRectangleobjectClassName="select_object"objectIdName="object_id":useCtrlSelect="true"v-model:selectObjectIds="selectObjectIds"v-model:selectObjects="selectObjects"><!-- 这个是插槽,将业务内容的Dom限制在MouseDrawRectangle组件内,这样可以将后面组件所有的监听事件绑定到组件上而不是整个页面Dom上,鼠标滑动的区域也会限制死在组件内,而不是整个页面的范围 --><template #selcetObject><div class="objects_content"><!-- 每一个选择的目标对象 --><divv-for="item in 50":key="item"class="select_object":object_id="item":class="selectObjectIds.includes(item.toString()) ? 'is_selected' : ''">{{ item }}</div></div></template></MouseDrawRectangle></div>
</template><script lang="ts" setup>
import { reactive, toRefs, watch } from "vue";
import MouseDrawRectangle from "@/components/objectSelect/mouseDrawRectangle.vue";const state = reactive({selectObjectIds: [] as Array<string>, // 选中的对象IDselectObjects: [] as Array<HTMLElement> // 选中的对象DOM
});
const { selectObjectIds, selectObjects } = toRefs(state);watch(() => [selectObjectIds.value, selectObjects.value],() => {console.log("选中的ID=>", selectObjectIds);console.log("选中的Dom=>", selectObjects);}
);
</script><style scoped lang="scss">
.content {// 因为使用flex布局,最下面一行盒子换行只会出现一半的高度,这里最好减去下每个盒子的高度height: calc(100% - 50px);overflow-y: auto;padding: 20px;.objects_content {user-select: none;display: flex;flex-wrap: wrap;gap: 10px;margin-bottom: 10px;// 盒子样式> div {width: 200px;height: 100px;background-color: #999;}.is_selected {color: #fff;box-sizing: border-box;border: 3px #317aff solid;border-radius: 5px;}}
}
</style>

相关文章:

Vue鼠标右键画矩形和Ctrl按键多选组件

效果图 说明 下面会贴出组件代码以及一个Demo&#xff0c;上面的效果图即为Demo的效果&#xff0c;建议直接将两份代码拷贝到自己的开发环境直接运行调试。 组件代码 <template><!-- 鼠标画矩形选择对象 --><div class"objects" ref"objectsR…...

【MySQL JDBC】使用Java连接MySQL数据库

一、什么是JDBC&#xff1f; 理解API的概念 API&#xff1a;Application Programing Interface -- 应用程序编程接口写好一个程序&#xff0c;这个程序需要给别人提供哪些功能&#xff1f;这些功能就是通过一些 函数/类 这样的方式来提供的。例如 Random、Scanner、ArrayList..…...

字节码学习之常见java语句的底层原理

文章目录 前言1. if语句字节码的解析 2. for循环字节码的解析 3. while循环4. switch语句5. try-catch语句6. i 和i的字节码7. try-catch-finally8. 参考文档 前言 上一章我们聊了《JVM字节码指令详解》 。本章我们学以致用&#xff0c;聊一下我们常见的一些java语句的特性底层…...

Godot C#连接信号不能像GDScirpt一样自动添加代码

前言 我网上找了好久&#xff0c;发现Godot 对于C# 的支持还有待增强 使用c#脚本有办法像gds那样连接节点自带信号时自动生成信号吗&#xff1f; 百度贴吧 Godot C# How To, Episode 9. Signals With Parameters | Godot Mono 解决方案 把信号拉长&#xff0c;看他的属性 修…...

快速自动化处理JavaScript渲染页面

在进行网络数据抓取时&#xff0c;许多网站使用了JavaScript来动态加载内容&#xff0c;这给传统的网络爬虫带来了一定的挑战。本文将介绍如何使用Selenium和ChromeDriver来实现自动化处理JavaScript渲染页面&#xff0c;并实现有效的数据抓取。 1、Selenium和ChromeDriver简介…...

通过API接口进行商品价格监控,可以按照以下步骤进行操作

要实现通过API接口进行商品价格监控&#xff0c;可以按照以下步骤进行操作&#xff1a; 申请平台账号并选择API接口&#xff1a;根据需要的功能&#xff0c;选择相应的API接口&#xff0c;例如商品API接口、店铺API接口、订单API接口等&#xff0c;这一步骤通常需要我们在相应…...

(vue3)大事记管理系统 文章管理页

[element-plus进阶] 文章列表渲染&#xff08;带搜索&到分页&#xff09; 表单架设&#xff1a;当前el-form标签配置一个inline属性&#xff0c;里面的元素就会在一行显示了 中英国际化处理&#xff1a;App.vue中el-config-provider标签包裹组件&#xff0c;意味着整个组…...

springboot 使用RocketMQ客户端生产消费消息DEMO

创建springboot项目省略 项目依赖 注意&#xff1a;当前客户端版本是 5.1.3 &#xff0c;安装的rocketmq服务的版本要与其对应 <properties><java.version>11</java.version><rocketmq-client-java-version>5.1.3</rocketmq-client-java-version&…...

第三章 内存管理 四、连续分配管理方式

目录 一、内存空间的分配与回收 1、连续分配管理方式 &#xff08;1&#xff09;、单一连续分配 优点&#xff1a; 缺点&#xff1a; &#xff08;2&#xff09;、固定分区分配 分区大小相等&#xff1a; 分区大小不等&#xff1a; &#xff08;3&#xff09;、动态分区…...

npm install报--4048错误和ERR_SOCKET_TIMEOUT问题解决方法之一

一、问题描述 学习vue数字大屏加载动漫效果时&#xff0c;在项目终端页面输入全局下载指令 npm install -g json-server 问题1、报--4048错误 会报如下错误 operation not permitted......errno: -4048code:EPERMsyscall: mkdir......The operation was reiected by your op…...

合并两个有序数组

给你两个按 非递减顺序 排列的整数数组 nums1 和 nums2&#xff0c;另有两个整数 m 和 n &#xff0c;分别表示 nums1 和 nums2 中的元素数目。 请你 合并 nums2 到 nums1 中&#xff0c;使合并后的数组同样按 非递减顺序 排列。 注意&#xff1a;最终&#xff0c;合并后数组…...

自动泊车系统设计学习笔记

1 概述 1.1 自动泊车系统研究现状 目前对于自动泊车系统的研究方法通常有两种实现方式&#xff1a; 整个泊车操作可以分为四个阶段&#xff1a;第一阶段车辆向前行驶进行车位识别&#xff0c;第二阶段车辆行驶到准备泊车时的待泊车区域&#xff0c;第三阶段车辆按照规划好的…...

基于Java的家电销售网站管理系统设计与实现(源码+lw+部署文档+讲解等)

文章目录 前言具体实现截图论文参考论文参考详细视频演示为什么选择我自己的网站自己的小程序&#xff08;小蔡coding&#xff09;有保障的售后福利 代码参考源码获取 前言 &#x1f497;博主介绍&#xff1a;✌全网粉丝10W,CSDN特邀作者、博客专家、CSDN新星计划导师、全栈领域…...

设计模式~备忘录模式(memento)-22

目录  (1)优点&#xff1a; (2)缺点&#xff1a; (3)使用场景&#xff1a; (4)注意事项&#xff1a; (5)应用实例&#xff1a; 代码 备忘录模式(memento) 备忘录模式&#xff08;Memento Pattern&#xff09;保存一个对象的某个状态&#xff0c;以便在适当的时候恢复对…...

【Agora UID 踩坑记录 Java 数据类型】

目录 负数二进制表示Java中32位无符号数的取法项目踩坑记录Java 0xffffffff隐式类型转换的坑 负数二进制表示 由于计算机中数据都以二进制表示&#xff0c;而负数的二级制是根据正数二进制取补码&#xff08;补码就是先取反码&#xff0c;然后加1&#xff09;得到&#xff0c;…...

ESP8285 RTOS SDK OTA

一、官方资源说明 官方指南&#xff1a;空中升级 (OTA) - ESP32 - — ESP-IDF 编程指南 v4.3.6 文档&#xff0c;虽然是正对ESP32的&#xff0c;但是原理是一样的。 官方参考例程&#xff1a;esp-idf\ESP8266_RTOS_SDK\examples\system\ota\&#xff0c;其中包含两个例程&…...

Hadoop3教程(四):HDFS的读写流程及节点距离计算

文章目录 &#xff08;55&#xff09;HDFS 写数据流程&#xff08;56&#xff09; 节点距离计算&#xff08;57&#xff09;机架感知&#xff08;副本存储节点选择&#xff09;&#xff08;58&#xff09;HDFS 读数据流程参考文献 &#xff08;55&#xff09;HDFS 写数据流程 …...

[0xGameCTF 2023] web题解

文章目录 [Week 1]signinbaby_phphello_httprepo_leakping [Week 2]ez_sqli方法一&#xff08;十六进制绕过&#xff09;方法二&#xff08;字符串拼接&#xff09; ez_upload [Week 1] signin 打开题目&#xff0c;查看下js代码 在main.js里找到flag baby_php <?php /…...

Qt之submodule编译

工作中会遇到这样一种情况&#xff1a;qt应用程序在运行时提示找不到某个qt的动态库。我遇到的是缺少libQt5Websocket.so&#xff0c;因为应用程序是在x86平台银河麒麟v10上开发&#xff0c;能够正常编译运行&#xff0c;然后移植到rk3588&#xff08;aarch64架构&#xff09;上…...

Python实现带图形界面的计算器

Python实现带图形界面的计算器 在本文中&#xff0c;我们将使用Python编写一个带有图形用户界面的计算器程序。这个程序将允许用户通过点击按钮或键盘输入数字和操作符&#xff0c;并在显示屏上显示计算结果。 开发环境准备 要运行这个计算器程序&#xff0c;您需要安装Pyth…...

Lombok 的 @Data 注解失效,未生成 getter/setter 方法引发的HTTP 406 错误

HTTP 状态码 406 (Not Acceptable) 和 500 (Internal Server Error) 是两类完全不同的错误&#xff0c;它们的含义、原因和解决方法都有显著区别。以下是详细对比&#xff1a; 1. HTTP 406 (Not Acceptable) 含义&#xff1a; 客户端请求的内容类型与服务器支持的内容类型不匹…...

CMake基础:构建流程详解

目录 1.CMake构建过程的基本流程 2.CMake构建的具体步骤 2.1.创建构建目录 2.2.使用 CMake 生成构建文件 2.3.编译和构建 2.4.清理构建文件 2.5.重新配置和构建 3.跨平台构建示例 4.工具链与交叉编译 5.CMake构建后的项目结构解析 5.1.CMake构建后的目录结构 5.2.构…...

关于nvm与node.js

1 安装nvm 安装过程中手动修改 nvm的安装路径&#xff0c; 以及修改 通过nvm安装node后正在使用的node的存放目录【这句话可能难以理解&#xff0c;但接着往下看你就了然了】 2 修改nvm中settings.txt文件配置 nvm安装成功后&#xff0c;通常在该文件中会出现以下配置&…...

基于当前项目通过npm包形式暴露公共组件

1.package.sjon文件配置 其中xh-flowable就是暴露出去的npm包名 2.创建tpyes文件夹&#xff0c;并新增内容 3.创建package文件夹...

Keil 中设置 STM32 Flash 和 RAM 地址详解

文章目录 Keil 中设置 STM32 Flash 和 RAM 地址详解一、Flash 和 RAM 配置界面(Target 选项卡)1. IROM1(用于配置 Flash)2. IRAM1(用于配置 RAM)二、链接器设置界面(Linker 选项卡)1. 勾选“Use Memory Layout from Target Dialog”2. 查看链接器参数(如果没有勾选上面…...

Android Bitmap治理全解析:从加载优化到泄漏防控的全生命周期管理

引言 Bitmap&#xff08;位图&#xff09;是Android应用内存占用的“头号杀手”。一张1080P&#xff08;1920x1080&#xff09;的图片以ARGB_8888格式加载时&#xff0c;内存占用高达8MB&#xff08;192010804字节&#xff09;。据统计&#xff0c;超过60%的应用OOM崩溃与Bitm…...

selenium学习实战【Python爬虫】

selenium学习实战【Python爬虫】 文章目录 selenium学习实战【Python爬虫】一、声明二、学习目标三、安装依赖3.1 安装selenium库3.2 安装浏览器驱动3.2.1 查看Edge版本3.2.2 驱动安装 四、代码讲解4.1 配置浏览器4.2 加载更多4.3 寻找内容4.4 完整代码 五、报告文件爬取5.1 提…...

蓝桥杯 冶炼金属

原题目链接 &#x1f527; 冶炼金属转换率推测题解 &#x1f4dc; 原题描述 小蓝有一个神奇的炉子用于将普通金属 O O O 冶炼成为一种特殊金属 X X X。这个炉子有一个属性叫转换率 V V V&#xff0c;是一个正整数&#xff0c;表示每 V V V 个普通金属 O O O 可以冶炼出 …...

NXP S32K146 T-Box 携手 SD NAND(贴片式TF卡):驱动汽车智能革新的黄金组合

在汽车智能化的汹涌浪潮中&#xff0c;车辆不再仅仅是传统的交通工具&#xff0c;而是逐步演变为高度智能的移动终端。这一转变的核心支撑&#xff0c;来自于车内关键技术的深度融合与协同创新。车载远程信息处理盒&#xff08;T-Box&#xff09;方案&#xff1a;NXP S32K146 与…...

R语言速释制剂QBD解决方案之三

本文是《Quality by Design for ANDAs: An Example for Immediate-Release Dosage Forms》第一个处方的R语言解决方案。 第一个处方研究评估原料药粒径分布、MCC/Lactose比例、崩解剂用量对制剂CQAs的影响。 第二处方研究用于理解颗粒外加硬脂酸镁和滑石粉对片剂质量和可生产…...