基于Vue3+TypeScript實現(xiàn)鼠標框選功能
實現(xiàn)功能:
- 選擇、取消選擇單個元素、同時選擇多個元素。
- 鼠標懸浮變色。
- 框選、滾動框選區(qū)域。
- 同時選擇多個區(qū)域。
最終實現(xiàn)效果
1. HTML代碼
<div class="right-bottom"> <<img src="div" alt="" width="70%" /> class="page" ref="pageRef" @scroll="handleScroll" @mouseup="handleMouseUp" > <table class="table" @mousemove="handleMouseMove"> <tr v-for="(row, rowIndex) in tableData" :key="rowIndex"> <td id="td" v-for="(col, colIndex) in row" :key="colIndex" :class="{ 'table-item-active': selectedCells.has(rowIndex + '-' + colIndex), }" :style="{ backgroundColor: selectedCells.has(rowIndex + '-' + colIndex) ? 'skyblue' : col.color, }" @mousedown.prevent="startSelect(rowIndex, colIndex)" > <div class="table-item" :class="{ 'item-hover': hoverPosition == `${rowIndex}-${colIndex}`, }" @click="handleItemClick(rowIndex, colIndex)" @mouseenter="handleMouseEnter(rowIndex, colIndex)" @mouseleave="handleMouseLeave(rowIndex, colIndex)" > {{ col.hole }} </div> </td> </tr> </table> </div> </div>
2. 定義全局變量
// 已選擇的單元格。因為可能出現(xiàn)重復框選單元格的情況,所以這里最好使用Set類型。 const selectedCells = reactive(new Set([])); // 矩陣起始坐標位置??蜻x矩陣在頁面上的位置,使用時請根據(jù)實際情況計算 let tableStartX = 0; let tableStartY = 0; // 單元格寬高 let cellWidth = 63; let cellHeight = 63; // 是否按住ctrl let isClickCtrl = false; // 當前懸浮單元格坐標 let hoverPosition = ref('');
3. 實現(xiàn)單個元素選擇、取消選擇、按ctrl同時選擇多個元素
/** * @description: 點擊單元格事件 * @param row 當前單元格X坐標 * @param column 當前單元格Y坐標 */ const handleItemClick = (row: number, column: number) => { // 按下ctrl document.onkeydown = (e: KeyboardEvent) => { if (e.key === 'Control') isClickCtrl = true; }; // 松開ctrl document.onkeyup = (e: KeyboardEvent) => { if (e.key === 'Control') isClickCtrl = false; }; // 如果按住ctrl鍵 if (isClickCtrl) { // 點擊了已經(jīng)選中的元素,取消選中; 否則選中 if (selectedCells.has(`${row}-${column}`)) { selectedCells.delete(`${row}-${column}`); }else { selectedCells.add(`${row}-${column}`); } } // 如果沒有點擊ctrl鍵,則先清空所有選中的元素,然后選中當前元素 else { selectedCells.clear(); selectedCells.add(`${row}-${column}`); } };
4. 鼠標懸浮變色
/** * @description: 鼠標懸浮單元格變色 * @param row 當前單元格X坐標 * @param column 當前單元格Y坐標 */ // 鼠標進入單元格,使用防抖降低代碼重復執(zhí)行次數(shù),提高性能 const handleMouseEnter = _.debounce((row: number, column: number) => { hoverPosition = `${row}-${column}`; }, 100); // 鼠標離開單元格 const handleMouseLeave = _.debounce(() => { hoverPosition = ''; }, 50);
5. 框選、滾動框選元素功能實現(xiàn)
5.1 獲取框選區(qū)域內的所有元素
實現(xiàn)步驟:
- 拿到起始單元格坐標以及結束單元格坐標。
- 根據(jù)起始結束坐標計算出框選區(qū)域內的所有單元格。
實現(xiàn)原理:例1. 如起始坐標為 startCell = [0, 0],結束坐標 endCell = [2, 2]
則當前區(qū)域的元素包括:
0,0 | 0,1 | 0,2 |
---|---|---|
1,0 | 1,1 | 1,2 |
2,0 | 2,1 | 2,2 |
例2. 如起始坐標為 startCell = [3, 4],結束坐標 endCell = [1, 2]
則當前區(qū)域的元素包括:
1,2 | 1,3 | 1,4 |
---|---|---|
2,2 | 2,3 | 2,4 |
3,2 | 3,3 | 3,4 |
以此類推。。。
結論: 根據(jù)以上實例可得,我們只需要拿到起止位置X坐標和Y坐標,然后用雙重for循環(huán)即可得到當前框選區(qū)域內的所有元素
獲取框選區(qū)域內的所有元素方法:
/** * @description: 根據(jù)起始坐標和結束坐標,設置選中區(qū)域 * @param startX 起始坐標x * @param startY 起始坐標y * @param endX 結束坐標x * @param endY 結束坐標y */ const selectRange = ( startX: number, startY: number, endX: number, endY: number ) => { for ( let rowIndex = Math.min(startX, endX); rowIndex <= Math.max(startX, endX); rowIndex++ ) { for ( let cellIndex = Math.min(startY, endY); cellIndex <= Math.max(startY, endY); cellIndex++ ) { selectedCells.add(`${rowIndex}-${cellIndex}`); } } };
5.2 計算框選結束坐標
實現(xiàn)原理:首先分別計算出鼠標距離矩陣原點left距離和top距離,然后再用這個距離除單個單元格的寬高就能得到結束坐標。
如果矩陣框選有滾動操作,只要再加上滾動距離即可。
5.3 鼠標移動事件
// 起始單元格坐標 let startCell: [number, number] | null = null; // 鼠標點擊事件,設置起始坐標 function startSelect(rowIndex: number, cellIndex: number) { startCell = [rowIndex, cellIndex]; } // 鼠標抬起事件,將起始坐標設置為null const handleMouseUp = () => { startCell = null; }; // 鼠標移動事件 const handleMouseMove = (e: MouseEvent) => { debounceFun1(e); }; const debounceFun1 = _.debounce((e: MouseEvent) => { // 如果沒有起始坐標,則不執(zhí)行 if (!startCell) return; // 按下ctrl鍵 document.onkeydown = (e: KeyboardEvent) => { if (e.key === 'Control') isClickCtrl = true; }; // 松開ctrl鍵 document.onkeyup = (e: KeyboardEvent) => { if (e.key === 'Control') isClickCtrl = false; }; endXY.x = e.clientX; endXY.y = e.clientY; // 獲取鼠標移動的當前單元格坐標,并設置為框選結束坐標 let endCell: [number, number] | null = null; endCell = [ Math.floor((e.clientY + scrollXY.y - tableStartY) / cellHeight), Math.floor((e.clientX + scrollXY.x - tableStartX) / cellWidth), ]; // 如果鼠標位置發(fā)生變化,則清空已選擇的單元格,并重新設置選中區(qū)域 if (!isClickCtrl) { selectedCells.clear() } // 根據(jù)起始坐標和結束坐標,設置選中區(qū)域 selectRange(...startCell, ...endCell); }, 50);
5.4 滾輪滾動事件
const scrollXY = reactive({ x: 0, y: 0 }); const handleScroll = (e: any) => { scrollXY.x = e.target.scrollLeft; scrollXY.y = e.target.scrollTop; if (!startCell) return; const endCell: [number, number] = [ Math.floor((endXY.y + e.target.scrollTop - tableStartY) / cellHeight), Math.floor((endXY.x + e.target.scrollLeft - tableStartX) / cellWidth), ]; selectRange(...startCell, ...endCell); };
6. 樣式相關代碼
.item-hover { background-color: #bae0ff !important; } th, td { border: 1px solid black; border-collapse: collapse; } .right-bottom { margin-left: 200px; height: 800px; width: 800px; .page { width: 100%; height: 100%; overflow: scroll; margin: 0 auto; .table { overflow-x: auto; overflow-y: auto; .table-item { position: relative; font-size: 13px; display: flex; justify-content: center; align-items: center; width: 60px; height: 60px; .coordinates { position: absolute; left: 0; top: 0; font-size: 10px; } } .table-item-active { background-color: skyblue; } } } .page ::selection { background-color: transparent; } }
7. 總結
實際開發(fā)中,矩陣原點坐標可能會因為各種情況發(fā)生改變(這里默認是[0,0]),需要根據(jù)實際情況計算,否則框選區(qū)域會出現(xiàn)錯位的情況。最后希望這個組件能幫助到有需要的人,歡迎大家提出建議!
以上就是基于Vue3+TypeScript實現(xiàn)鼠標框選功能的詳細內容,更多關于Vue3 TypeScript鼠標框選的資料請關注腳本之家其它相關文章!
相關文章
vue組件 keep-alive 和 transition 使用詳解
這篇文章主要介紹了vue組件 keep-alive 和 transition 使用詳解,需要的朋友可以參考下2019-10-10element-ui?el-upload實現(xiàn)上傳文件及簡單的上傳文件格式驗證功能
前端上傳文件后,后端接受文件進行處理后直接返回處理后的文件,前端直接再將文件下載下來,下面這篇文章主要給大家介紹了關于element-ui?el-upload實現(xiàn)上傳文件及簡單的上傳文件格式驗證功能的相關資料,需要的朋友可以參考下2022-11-11