React中实现鼠标拖拽多选方块功能的技术问询
实现React方块的拖拽多选功能
现有代码仅支持单选单个方块,要实现类似文件资源管理器的拖拽多选(支持框选、Ctrl点击切换单个选中),可按以下方式修改:
核心调整点
- 将单个选中状态改为选中项集合(用
Set更便捷实现增删操作) - 添加拖拽相关状态:拖拽激活状态、起始坐标、选框位置尺寸
- 绑定鼠标事件处理拖拽逻辑:按下触发拖拽起始、移动更新选框、松开完成选中
- 区分普通点击与Ctrl点击行为:普通点击清空重选,Ctrl点击切换单个选中状态
完整实现代码
import React, { useState, useRef, useEffect } from 'react'; const App = () => { // 存储选中的方块索引,Set结构方便快速增删 const [selectedSquares, setSelectedSquares] = useState(new Set()); // 拖拽状态:是否激活、起始坐标、选框参数 const [isDragging, setIsDragging] = useState(false); const [dragStart, setDragStart] = useState({ x: 0, y: 0 }); const [dragRect, setDragRect] = useState({ top: 0, left: 0, width: 0, height: 0 }); // 容器ref,用于计算相对坐标 const containerRef = useRef(null); // 处理方块点击:区分普通点击和Ctrl/Meta点击 const handleSquareClick = (e, squareIndex) => { setSelectedSquares(prev => { const newSet = new Set(prev); if (e.ctrlKey || e.metaKey) { // Ctrl/Meta点击:切换当前项选中状态 newSet.has(squareIndex) ? newSet.delete(squareIndex) : newSet.add(squareIndex); } else { // 普通点击:清空所有选中项,仅选中当前 newSet.clear(); newSet.add(squareIndex); } return newSet; }); }; // 鼠标按下:启动拖拽,记录起始坐标(点击方块时不触发) const handleMouseDown = (e) => { if (e.target.classList.contains('square')) return; const containerRect = containerRef.current.getBoundingClientRect(); setIsDragging(true); setDragStart({ x: e.clientX - containerRect.left, y: e.clientY - containerRect.top }); }; // 鼠标移动:更新选框位置,实时选中范围内的方块 const handleMouseMove = (e) => { if (!isDragging) return; const containerRect = containerRef.current.getBoundingClientRect(); const currentX = e.clientX - containerRect.left; const currentY = e.clientY - containerRect.top; // 计算选框参数,确保宽高为正 const rect = { top: Math.min(dragStart.y, currentY), left: Math.min(dragStart.x, currentX), width: Math.abs(currentX - dragStart.x), height: Math.abs(currentY - dragStart.y) }; setDragRect(rect); // 筛选出选框内的方块 const squares = containerRef.current.querySelectorAll('.square'); const inRangeSquares = []; squares.forEach((square, index) => { const squareRect = square.getBoundingClientRect(); const relativeRect = { top: squareRect.top - containerRect.top, left: squareRect.left - containerRect.left, right: squareRect.right - containerRect.left, bottom: squareRect.bottom - containerRect.top }; // 判断方块与选框是否有交集 if ( relativeRect.right > rect.left && relativeRect.left < rect.left + rect.width && relativeRect.bottom > rect.top && relativeRect.top < rect.top + rect.height ) { inRangeSquares.push(index + 1); // 方块索引从1开始 } }); // 更新选中集合 setSelectedSquares(new Set(inRangeSquares)); }; // 鼠标松开:结束拖拽 const handleMouseUp = () => { setIsDragging(false); }; // 监听全局鼠标松开事件,避免移出容器后无法结束拖拽 useEffect(() => { if (isDragging) { window.addEventListener('mouseup', handleMouseUp); return () => window.removeEventListener('mouseup', handleMouseUp); } }, [isDragging]); return ( <div ref={containerRef} onMouseDown={handleMouseDown} onMouseMove={handleMouseMove} style={{ position: 'relative', padding: '20px' }} > <style> {` .square-row { display: flex; margin-bottom: 10px; } .square { width: 100px; height: 100px; background-color: #ccc; margin-right: 10px; cursor: pointer; transition: background-color 0.2s; } .selected { background-color: orange; } .drag-selection { position: absolute; border: 2px solid rgba(0, 122, 255, 0.7); background-color: rgba(0, 122, 255, 0.2); pointer-events: none; z-index: 10; } `} </style> {/* 拖拽选框元素 */} {isDragging && ( <div className="drag-selection" style={{ top: `${dragRect.top}px`, left: `${dragRect.left}px`, width: `${dragRect.width}px`, height: `${dragRect.height}px` }} /> )} <div className="square-row"> {[1,2,3].map(index => ( <div key={index} className={`square ${selectedSquares.has(index) ? 'selected' : ''}`} onClick={(e) => handleSquareClick(e, index)} ></div> ))} </div> <div className="square-row"> {[4,5,6].map(index => ( <div key={index} className={`square ${selectedSquares.has(index) ? 'selected' : ''}`} onClick={(e) => handleSquareClick(e, index)} ></div> ))} </div> </div> ); }; export default App;
功能说明
- 普通点击:清空所有选中项,仅选中当前点击的方块
- Ctrl/Meta点击:切换当前方块的选中状态(已选中则取消,未选中则添加)
- 拖拽选框:在空白区域按住鼠标拖动,框选范围内的方块会被实时选中
内容的提问来源于stack exchange,提问作者rax-hacks
相关产品推荐
相关产品推荐

