Vue鼠標(biāo)右鍵畫矩形和Ctrl按鍵多選組件方式
更新時間:2024年12月26日 09:02:40 作者:-小龍人
文章介紹了一個Vue組件,該組件允許用戶通過鼠標(biāo)右鍵在畫布上繪制矩形,并且支持通過Ctrl鍵進(jìn)行多選,文章附帶了組件代碼和一個示例,建議讀者將代碼復(fù)制到自己的開發(fā)環(huán)境中進(jìn)行調(diào)試
Vue鼠標(biāo)右鍵畫矩形和Ctrl按鍵多選組件
效果圖
說明
下面會貼出組件代碼以及一個Demo,上面的效果圖即為Demo的效果,建議直接將兩份代碼拷貝到自己的開發(fā)環(huán)境直接運行調(diào)試。
組件代碼
<template> <!-- 鼠標(biāo)畫矩形選擇對象 --> <div class="objects" ref="objectsRef" @mousedown="handleMouseDown"> <!-- 矩形選擇框 --> <div class="mask" ref="maskRef" v-show="maskPosition.show" :style=" 'width:' + maskWidth + 'left:' + maskLeft + 'height:' + maskHeight + 'top:' + maskTop " /> <!-- 選擇對象內(nèi)容的目標(biāo)插槽 --> <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,用于定義如何獲取對象的id selectObjectIds?: Array<string>; // 選中的對象ID selectObjects?: Array<HTMLElement>; // 選中的對象 useCtrlSelect?: boolean; // 是否支持按住Ctrl多選 }>(), { useCtrlSelect: true // 默認(rèn)支持按住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多選,監(jiān)聽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; } }); } /** 鼠標(biāo)按下 */ const handleMouseDown = event => { // 展示矩形框,通過坐標(biāo)位置來畫出矩形 maskPosition.value.show = true; maskPosition.value.startX = event.clientX; maskPosition.value.startY = event.clientY; maskPosition.value.endX = event.clientX; maskPosition.value.endY = event.clientY; // 監(jiān)聽鼠標(biāo)移動事件和抬起離開事件 objectsRef.value.addEventListener("mousemove", handleMouseMove); objectsRef.value.addEventListener("mouseup", handleMouseUp); }; /** 鼠標(biāo)移動 */ const handleMouseMove = event => { maskPosition.value.endX = event.clientX; maskPosition.value.endY = event.clientY; }; /** 鼠標(biāo)抬起離開 */ const handleMouseUp = () => { // 移除鼠標(biāo)監(jiān)聽事件 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鍵,之前選擇的數(shù)據(jù)不清空 if (isPressCtrlKey.value) { tempSelectObjectIds = props.selectObjectIds === undefined ? [] : props.selectObjectIds; tempSelectObjects = props.selectObjects === undefined ? [] : props.selectObjects; } // 獲取鼠標(biāo)畫出的矩形框位置 const rectanglePosition = maskRef.value.getClientRects()[0]; // 獲取所有選擇區(qū)域的對象; 這里獲取的元素的方式定義于父組件的objectClassName const selectedObjects = objectsRef.value.querySelectorAll( `.${props.objectClassName}` ); // 遍歷對象,獲取到每個對象的坐標(biāo)位置,判斷該位置是否在上面獲取到的鼠標(biāo)畫矩形的框的位置中 selectedObjects.forEach(item => { const objectPosition = item.getClientRects()[0]; // 這里獲取的id的方式定義于父組件的objectIdName if (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); }; /** * 判斷對象坐標(biāo)是否在鼠標(biāo)畫出的矩形框坐標(biāo)位置內(nèi) * @param objectPosition 對象坐標(biāo)位置 * @param rectanglePosition 鼠標(biāo)畫出的矩形框坐標(biāo)位置 */ 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 ); }; /** 重置鼠標(biāo)位置 */ const handleResetMaskPosition = () => { maskPosition.value.startX = 0; maskPosition.value.startY = 0; maskPosition.value.endX = 0; maskPosition.value.endY = 0; }; /** 通過鼠標(biāo)位置實時計算矩形框大小 */ 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> <!------------- 鼠標(biāo)畫矩形選擇對象組件DEMO,可以直接拷貝到你的頁面去運行-----------------------> <div class="content"> <!-- MouseDrawRectangle說明: objectClassName綁定到下面對象class名稱; objectIdName名稱對應(yīng)object_id; useCtrlSelect默認(rèn)是打開的,用于按住Ctrl鍵進(jìn)行多選,以及取消已選擇的對象。 selectObjectIds會實時從子組件更新過來,監(jiān)聽它的值來控制頁面的選擇狀態(tài)即可。 另外有參數(shù)selectObjects會實時從子組件傳回被選中的對象Dom信息 --> <MouseDrawRectangle objectClassName="select_object" objectIdName="object_id" :useCtrlSelect="true" v-model:selectObjectIds="selectObjectIds" v-model:selectObjects="selectObjects" > <!-- 這個是插槽,將業(yè)務(wù)內(nèi)容的Dom限制在MouseDrawRectangle組件內(nèi), 這樣可以將后面組件所有的監(jiān)聽事件綁定到組件上而不是整個頁面Dom上, 鼠標(biāo)滑動的區(qū)域也會限制死在組件內(nèi),而不是整個頁面的范圍 --> <template #selcetObject> <div class="objects_content"> <!-- 每一個選擇的目標(biāo)對象 --> <div v-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>, // 選中的對象ID selectObjects: [] 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布局,最下面一行盒子換行只會出現(xiàn)一半的高度,這里最好減去下每個盒子的高度 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>
總結(jié)
以上為個人經(jīng)驗,希望能給大家一個參考,也希望大家多多支持腳本之家。
相關(guān)文章
vue mintui-Loadmore結(jié)合實現(xiàn)下拉刷新和上拉加載示例
本篇文章主要介紹了vue mintui-Loadmore結(jié)合實現(xiàn)下拉刷新和上拉加載示例,具有一定的參考價值,感興趣的小伙伴們可以參考一下2017-10-10基于vue-cli、elementUI的Vue超簡單入門小例子(推薦)
這篇文章主要介紹了基于vue-cli、elementUI的Vue超簡單入門小例子,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2019-04-04詳解vue中使用vue-quill-editor富文本小結(jié)(圖片上傳)
這篇文章主要介紹了詳解vue中使用vue-quill-editor富文本小結(jié)(圖片上傳),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2019-04-04vue路由篇之router-view內(nèi)容無法渲染出來問題
這篇文章主要介紹了vue路由篇之router-view內(nèi)容無法渲染出來問題及解決,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-04-04