当前位置:首页 > React

react实现框选功能

2026-01-27 07:44:12React

实现框选功能的基本思路

框选功能通常涉及监听鼠标事件、绘制选框以及处理选框内的元素。React中可以通过结合原生DOM事件和状态管理来实现。

监听鼠标事件

在组件挂载时添加mousedownmousemovemouseup事件监听器,卸载时移除这些监听器。使用useEffect钩子来处理这些副作用。

useEffect(() => {
  const handleMouseDown = (e) => {
    // 记录起始坐标
  };

  const handleMouseMove = (e) => {
    // 计算选框尺寸并绘制
  };

  const handleMouseUp = (e) => {
    // 处理选框内的元素
  };

  document.addEventListener('mousedown', handleMouseDown);
  document.addEventListener('mousemove', handleMouseMove);
  document.addEventListener('mouseup', handleMouseUp);

  return () => {
    document.removeEventListener('mousedown', handleMouseDown);
    document.removeEventListener('mousemove', handleMouseMove);
    document.removeEventListener('mouseup', handleMouseUp);
  };
}, []);

绘制选框

使用useState存储选框的坐标和尺寸。在鼠标移动时更新这些值,并通过CSS绝对定位和transform来绘制选框。

const [selectionBox, setSelectionBox] = useState({
  startX: 0,
  startY: 0,
  width: 0,
  height: 0,
  isSelecting: false,
});

// 在handleMouseMove中更新选框
setSelectionBox({
  startX: Math.min(startX, currentX),
  startY: Math.min(startY, currentY),
  width: Math.abs(currentX - startX),
  height: Math.abs(currentY - startY),
  isSelecting: true,
});

处理选框内元素

使用Element.getBoundingClientRect()获取每个可选元素的边界框,检查是否与选框相交。可以通过intersects函数来判断两个矩形是否重叠。

const intersects = (rect1, rect2) => {
  return !(
    rect2.left > rect1.right ||
    rect2.right < rect1.left ||
    rect2.top > rect1.bottom ||
    rect2.bottom < rect1.top
  );
};

// 在handleMouseUp中筛选元素
const selectedElements = elements.filter(element => {
  const elementRect = element.getBoundingClientRect();
  const selectionRect = {
    left: selectionBox.startX,
    top: selectionBox.startY,
    right: selectionBox.startX + selectionBox.width,
    bottom: selectionBox.startY + selectionBox.height,
  };
  return intersects(elementRect, selectionRect);
});

完整组件示例

import React, { useState, useEffect, useRef } from 'react';

const SelectionBox = () => {
  const [selectionBox, setSelectionBox] = useState({
    startX: 0,
    startY: 0,
    width: 0,
    height: 0,
    isSelecting: false,
  });
  const [selectedItems, setSelectedItems] = useState([]);
  const itemsRef = useRef([]);

  useEffect(() => {
    const handleMouseDown = (e) => {
      setSelectionBox({
        startX: e.clientX,
        startY: e.clientY,
        width: 0,
        height: 0,
        isSelecting: true,
      });
    };

    const handleMouseMove = (e) => {
      if (!selectionBox.isSelecting) return;

      setSelectionBox(prev => ({
        ...prev,
        width: e.clientX - prev.startX,
        height: e.clientY - prev.startY,
      }));
    };

    const handleMouseUp = () => {
      if (!selectionBox.isSelecting) return;

      const selectionRect = {
        left: selectionBox.width > 0 
          ? selectionBox.startX 
          : selectionBox.startX + selectionBox.width,
        top: selectionBox.height > 0 
          ? selectionBox.startY 
          : selectionBox.startY + selectionBox.height,
        right: selectionBox.width > 0 
          ? selectionBox.startX + selectionBox.width 
          : selectionBox.startX,
        bottom: selectionBox.height > 0 
          ? selectionBox.startY + selectionBox.height 
          : selectionBox.startY,
      };

      const selected = itemsRef.current.filter(item => {
        const itemRect = item.getBoundingClientRect();
        return !(
          selectionRect.right < itemRect.left ||
          selectionRect.left > itemRect.right ||
          selectionRect.bottom < itemRect.top ||
          selectionRect.top > itemRect.bottom
        );
      });

      setSelectedItems(selected.map(el => el.id));
      setSelectionBox({
        startX: 0,
        startY: 0,
        width: 0,
        height: 0,
        isSelecting: false,
      });
    };

    document.addEventListener('mousedown', handleMouseDown);
    document.addEventListener('mousemove', handleMouseMove);
    document.addEventListener('mouseup', handleMouseUp);

    return () => {
      document.removeEventListener('mousedown', handleMouseDown);
      document.removeEventListener('mousemove', handleMouseMove);
      document.removeEventListener('mouseup', handleMouseUp);
    };
  }, [selectionBox.isSelecting]);

  return (
    <div style={{ position: 'relative', height: '100vh' }}>
      {selectionBox.isSelecting && (
        <div
          style={{
            position: 'absolute',
            left: selectionBox.width > 0 
              ? selectionBox.startX 
              : selectionBox.startX + selectionBox.width,
            top: selectionBox.height > 0 
              ? selectionBox.startY 
              : selectionBox.startY + selectionBox.height,
            width: Math.abs(selectionBox.width),
            height: Math.abs(selectionBox.height),
            backgroundColor: 'rgba(0, 0, 255, 0.2)',
            border: '1px solid blue',
          }}
        />
      )}

      {[1, 2, 3, 4, 5].map(id => (
        <div
          key={id}
          ref={el => itemsRef.current[id] = el}
          style={{
            position: 'absolute',
            left: `${id * 100}px`,
            top: `${id * 100}px`,
            width: '50px',
            height: '50px',
            backgroundColor: selectedItems.includes(id) 
              ? 'red' 
              : 'gray',
          }}
        />
      ))}
    </div>
  );
};

export default SelectionBox;

性能优化建议

对于大量可选项的场景,可以考虑以下优化:

react实现框选功能

  • 使用虚拟滚动只渲染可视区域内的元素
  • 对选框检测使用四叉树等空间分区技术
  • 节流鼠标移动事件处理

注意事项

  • 确保选框坐标计算考虑了页面滚动位置
  • 处理鼠标事件时注意阻止默认行为以避免冲突
  • 在严格模式下注意事件监听器的清理

标签: 功能react
分享给朋友:

相关文章

vue实现倒计时功能

vue实现倒计时功能

vue实现倒计时功能 在Vue中实现倒计时功能可以通过多种方式完成,以下是几种常见的方法: 方法一:使用setInterval和clearInterval 创建一个倒计时组件,利用setInter…

vue实现分页功能

vue实现分页功能

Vue 分页功能实现 在 Vue 中实现分页功能通常需要结合后端 API 或前端数据分页逻辑。以下是两种常见的实现方式: 前端分页实现 适用于数据量较小的情况,直接在 Vue 组件中处理分页逻辑。…

如何优化react

如何优化react

优化 React 性能的方法 使用 React.memo 或 PureComponent 对于函数组件,使用 React.memo 进行记忆化,避免不必要的重新渲染。类组件可以使用 PureCompo…

react 如何启动

react 如何启动

创建 React 项目 使用官方工具 create-react-app 快速初始化项目,需提前安装 Node.js(版本 ≥ 14.0.0)和 npm/yarn: npx create-react-…

react如何调度

react如何调度

React 调度机制概述 React 的调度机制通过 Fiber 架构 和 Scheduler 模块实现任务优先级管理与时间切片(Time Slicing),确保高优先级更新(如用户交互)能快速响应,…

原生实现vue功能

原生实现vue功能

原生 JavaScript 实现 Vue 核心功能 使用原生 JavaScript 模拟 Vue 的核心功能(如数据绑定、虚拟 DOM、响应式更新等),可以通过以下方式实现: 数据响应式系统 通过…