vue.js

关注公众号 jb51net

关闭
首页 > 网络编程 > JavaScript > javascript类库 > vue.js > Vue3 TypeScript鼠标框选

基于Vue3+TypeScript实现鼠标框选功能

作者:广岛原子

这篇文章主要介绍了基于Vue3+TypeScript实现鼠标框选功能,文中通过代码示例给大家讲解的非常纤细,对大家的学习或工作有一定的帮助,需要的朋友可以参考下

实现功能:

最终实现效果

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. 定义全局变量

// 已选择的单元格。因为可能出现重复框选单元格的情况,所以这里最好使用Set类型。
const selectedCells = reactive(new Set([]));
// 矩阵起始坐标位置。框选矩阵在页面上的位置,使用时请根据实际情况计算
let tableStartX = 0;
let tableStartY = 0;
// 单元格宽高
let cellWidth = 63;
let cellHeight = 63;
// 是否按住ctrl
let isClickCtrl = false;
// 当前悬浮单元格坐标
let hoverPosition = ref('');

3. 实现单个元素选择、取消选择、按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) {
    // 点击了已经选中的元素,取消选中; 否则选中
    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坐标
 */
// 鼠标进入单元格,使用防抖降低代码重复执行次数,提高性能
const handleMouseEnter = _.debounce((row: number, column: number) => {
  hoverPosition = `${row}-${column}`;
}, 100);
// 鼠标离开单元格
const handleMouseLeave = _.debounce(() => {
  hoverPosition = '';
}, 50);

5. 框选、滚动框选元素功能实现

5.1 获取框选区域内的所有元素

实现步骤:

实现原理:例1. 如起始坐标为 startCell = [0, 0],结束坐标 endCell = [2, 2]
则当前区域的元素包括:

0,00,10,2
1,01,11,2
2,02,12,2

例2. 如起始坐标为 startCell = [3, 4],结束坐标 endCell = [1, 2]
则当前区域的元素包括:

1,21,31,4
2,22,32,4
3,23,33,4

以此类推。。。

结论: 根据以上实例可得,我们只需要拿到起止位置X坐标和Y坐标,然后用双重for循环即可得到当前框选区域内的所有元素

获取框选区域内的所有元素方法:

/**
 * @description: 根据起始坐标和结束坐标,设置选中区域
 * @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 计算框选结束坐标

实现原理:首先分别计算出鼠标距离矩阵原点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) => {
  // 如果没有起始坐标,则不执行
  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),
  ];
  // 如果鼠标位置发生变化,则清空已选择的单元格,并重新设置选中区域
  if (!isClickCtrl) {
    selectedCells.clear()
  }
  // 根据起始坐标和结束坐标,设置选中区域
  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. 总结

实际开发中,矩阵原点坐标可能会因为各种情况发生改变(这里默认是[0,0]),需要根据实际情况计算,否则框选区域会出现错位的情况。最后希望这个组件能帮助到有需要的人,欢迎大家提出建议!

以上就是基于Vue3+TypeScript实现鼠标框选功能的详细内容,更多关于Vue3 TypeScript鼠标框选的资料请关注脚本之家其它相关文章!

您可能感兴趣的文章:
阅读全文