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-04
vue路由篇之router-view內(nèi)容無法渲染出來問題
這篇文章主要介紹了vue路由篇之router-view內(nèi)容無法渲染出來問題及解決,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-04-04

