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>

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

bash一行输入,多行回显demo脚本

效果图&#xff1a; 脚本&#xff1a; #!/bin/bash # 定义一个变量&#xff0c;用来存储输入的内容 input"" # 定义一个变量&#xff0c;用来存储输入的字符 char""# 为了让read能读到空格键 IFS_store$IFS IFS# 提示内容&#xff0c;在while循环中也有&a…

CSS 滚动驱动动画 animation-range

animation-range 语法 normallength-percentagetimeline-range-name 具名时间线范围 named timeline rangecovercontainentry 和 entry-crossingexit 和 exit-crossing 兼容性 animation-range 这个属性可同时对 scroll progress timeline 和 view progress timeline 这两种不…

数据结构与算法-(8)---队列(Queue)

&#x1f308;write in front&#x1f308; &#x1f9f8;大家好&#xff0c;我是Aileen&#x1f9f8;.希望你看完之后&#xff0c;能对你有所帮助&#xff0c;不足请指正&#xff01;共同学习交流. &#x1f194;本文由Aileen_0v0&#x1f9f8; 原创 CSDN首发&#x1f412; 如…

Python算法练习 10.15

leetcode 2130 链表的最大孪生和 在一个大小为 n 且 n 为 偶数 的链表中&#xff0c;对于 0 < i < (n / 2) - 1 的 i &#xff0c;第 i 个节点&#xff08;下标从 0 开始&#xff09;的孪生节点为第 (n-1-i) 个节点 。 比方说&#xff0c;n 4 那么节点 0 是节点 3 的孪…

LaunchView/启动页 的实现

1. 创建启动画板&#xff0c;LaunchScreen.storyboard 添加组件如图: 2. 项目中设置只支持竖屏&#xff0c;添加启动画板&#xff0c;如图: 3. 创建启动画面动画视图&#xff0c;LaunchView.swift import SwiftUI/// 启动视图 struct LaunchView: View {/// 字符串转换为字符串…

ES知识点全面整理

● 我们从很多年前就知道 ES6, 也就是官方发布的 ES2015 ● 从 2015 年开始, 官方觉得大家命名太乱了, 所以决定以年份命名 ● 但是大家还是习惯了叫做 ES6, 不过这不重要 ● 重要的是, ES6 关注的人非常多, 大家也会主动去关注 ● 但是从 2016 年以后, 每年官方都会出现新…

【TensorFlow2 之013】TensorFlow-Lite

一、说明 在这篇文章中&#xff0c;我们将展示如何构建计算机视觉模型并准备将其部署在移动和嵌入式设备上。有了这些知识&#xff0c;您就可以真正将脚本部署到日常使用或移动应用程序中。 教程概述&#xff1a; 介绍在 TensorFlow 中构建模型将模型转换为 TensorFlow Lite训练…

Mac 远程 Ubuntu

1. Iterm2 添加ssh 参考&#xff1a;https://www.javatang.com/archives/2021/11/29/13063392.html 2. Finder 添加远程文件管理 2.1 ubuntu 配置 安装samba sudo apt-get install samba配置 [share]path /home/USER_NAME/shared_directoryavailable yesbrowseable ye…

NewStarCTF2023week2-ez_sql

闭合之后尝试判断字段数&#xff0c;存在WAF&#xff0c;使用大小写绕过&#xff08;后面的sql语句也需要进行大小写绕过&#xff09; ?id1 Order by 5-- 测出有5列 ?id1 Order by 6-- 查一下数据库名、版本、用户等信息 ?id1Union Select database(),version(),user(),4,…

elementUI el-table+树形结构子节点选中后没有打勾?(element版本问题 已解决)

问题 1.不勾选父级CB111&#xff0c;直接去勾选子级&#xff08;ST2001…&#xff09;&#xff0c;子级选中后没有打勾显示 排查 一直以为是这个树形结构和表格不兼容产生的问题&#xff0c;到后来看官方demo都是可以勾选的&#xff0c;最后排查到了版本问题&#xff0c; 项…

数学建模——人工神经网络模型

一、人工神经网络简介 1、神经网络起源与应用 1943年心理学家McCulloch和数学家Pitts提出神经元生物数学模型&#xff08;M-P模型&#xff09;&#xff0c;后来人工神经网络(Artifical Neural Network,ANN)是在生物神经网络(Biological Neural Network,BNN)基础上发展起来的&a…

SystemC入门学习-第8章 测试平台的编写

之前的章节&#xff0c;一直把重点放在用SystemC来描述硬件电路上&#xff0c;即如何编写SystemC 的RTL。本章的注意力集中在验证和编写测试平台上。 重点包括&#xff1a; 如何生成时钟信号和激励波形如何编写有响应能力的测试平台如何记录仿真结果 8.1 编写测试平台 测试平…

IO流:java中解码和编码出现乱码说明及代码实现

IO流&#xff1a;java中解码和编码的代码实现 一、UTF-8和GBK编码方式二、idea和eclipse的默认编码方式三、解码和编码方法四、代码实现编码解码 五、额外知识扩展 一、UTF-8和GBK编码方式 如果采用的是UTF-8的编码方式&#xff0c;那么1个英文字母 占 1个字节&#xff0c;1个…

使用Swift开发Framework遇到的问题及解决方法

文章目录 一、Swift 旧版本Xcode 打出来的framework 新版本不兼容问题 一、Swift 旧版本Xcode 打出来的framework 新版本不兼容问题 Cannot load module xxx built with SDK ihphoneos16.4 when using SDK iphoneos17.0:XXX/xxx.framework/Modules/xxx.swiftmodule/arm64-appl…

java swing实现点击按钮切换图片(简单实现)

本文教程&#xff0c;主要提供一个简单的例子&#xff0c;使用java swing完成点击按钮能够切换图片。 目录 一、程序预览 二、程序代码 一、程序预览 二、程序代码 package learnProject.csdn;import java.awt.BorderLayout; import java.awt.FlowLayout; import java.awt.ev…

webpack 解决:Cannot use import statement outside a module 的问题

1、问题描述&#xff1a; 其一、报错为&#xff1a; Uncaught SyntaxError: Cannot use import statement outside a module; 中文为&#xff1a; 未捕获的语法错误&#xff1a;无法在模块外部使用 import 语句; 其二、问题描述为&#xff1a; 在项目打包的时候 npm run …

【Vue面试题二十一】、Vue中的过滤器了解吗?过滤器的应用场景有哪些?

文章底部有个人公众号&#xff1a;热爱技术的小郑。主要分享开发知识、学习资料、毕业设计指导等。有兴趣的可以关注一下。为何分享&#xff1f; 踩过的坑没必要让别人在再踩&#xff0c;自己复盘也能加深记忆。利己利人、所谓双赢。 面试官&#xff1a;Vue中的过滤器了解吗&am…

Android组件通信——PendingIntent(二十八)

1. PendingIntent 1.1 知识点 &#xff08;1&#xff09;了解PendingIntent与Intent的区别&#xff1b; &#xff08;2&#xff09;可以完成Notification功能的开发&#xff1b; &#xff08;3&#xff09;可以使用PendingIntent进行短信的发送&#xff1b; 1.2 具体内容 …

asp.net老年大学信息VS开发sqlserver数据库web结构c#编程Microsoft Visual Studio计算机毕业设计

一、源码特点 asp.net老年大学信息管理系统是一套完善的web设计管理系统&#xff0c;系统具有完整的源代码和数据库&#xff0c;系统主要采用B/S模式开发。开发环境为vs2010&#xff0c;数据库为sqlserver2008&#xff0c;使用c# 语言开发 asp.net老年大学信息管理系统…

[论文笔记]SimCSE

引言 今天带来一篇当时引起轰动的论文SimCSE笔记,论文题目是 语句嵌入的简单对比学习。 SimCSE是一个简单的对比学习框架,它可以通过无监督和有监督的方式来训练。 对于无监督方式,输入一个句子然后在一个对比目标中预测它自己,仅需要标准的Dropout作为噪声。这种简单的…