React中用.map()与内联事件实现可调整大小组件的问题
解决循环渲染可调整大小组件的平滑缩放问题
核心问题分析
你之前的实现存在两个关键问题导致无法平滑调整:
- 内联
onMouseMove仅在resizer元素范围内触发,鼠标移出后就停止响应 - 没有维护每个独立item的缩放状态(初始宽度、鼠标起始位置),无法精准计算宽度变化
解决方案代码
React 逻辑代码
const App = () => { const data = ['item1', 'item2', 'item3', 'item4', 'item5']; // 存储每个item的宽度,初始值300px const [itemWidths, setItemWidths] = React.useState( data.reduce((acc, _, idx) => ({ ...acc, [idx]: 300 }), {}) ); // 记录当前正在调整的item索引 const [resizingIdx, setResizingIdx] = React.useState(null); // 记录鼠标按下时的初始位置和对应item的宽度 const [resizeStart, setResizeStart] = React.useState({ x: 0, width: 0 }); // 鼠标按下时初始化缩放参数并绑定全局事件 const handleMouseDown = (e, idx) => { setResizingIdx(idx); setResizeStart({ x: e.clientX, width: itemWidths[idx] }); document.addEventListener('mousemove', handleMouseMove); document.addEventListener('mouseup', handleMouseUp); }; // 鼠标移动时计算宽度变化 const handleMouseMove = (e) => { if (resizingIdx === null) return; // 计算鼠标偏移量,限制最小宽度避免组件过小 const dx = e.clientX - resizeStart.x; const newWidth = Math.max(100, resizeStart.width + dx); setItemWidths(prev => ({ ...prev, [resizingIdx]: newWidth })); }; // 鼠标松开时清理事件监听并重置状态 const handleMouseUp = () => { setResizingIdx(null); document.removeEventListener('mousemove', handleMouseMove); document.removeEventListener('mouseup', handleMouseUp); }; return ( <div className="App"> {data.map((x, idx) => ( <div className="item" key={idx} style={{ width: `${itemWidths[idx]}px` }} > {x} <div className="resizer" onMouseDown={(e) => handleMouseDown(e, idx)} ></div> </div> ))} </div> ); }; ReactDOM.render( <App />, document.getElementById("root") );
CSS 样式代码
.App { text-align: center; padding: 20px; } .item{ position: relative; border: 1px solid black; margin: 10px auto; border-radius: 3px; padding: 10px; box-sizing: border-box; /* 保证padding不影响宽度计算 */ } .resizer { position: absolute; background: #333; cursor: col-resize; height: 100%; right: 0; top: 0; width: 8px; border-radius: 0 3px 3px 0; } .resizer:hover { background: #555; }
HTML 基础结构
<div id="root"></div> <script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.3/umd/react.production.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.3/umd/react-dom.production.min.js"></script>
关键实现要点
- 状态管理:用
itemWidths对象存储每个item的宽度,通过索引关联,规避循环中使用ref的React规范问题 - 全局事件绑定:在
mousedown时绑定document级别的mousemove和mouseup,确保鼠标移出resizer后仍能持续响应,实现平滑拖动 - 初始参数记录:按下鼠标时记录当前鼠标位置和item宽度,后续通过偏移量计算新宽度,避免直接读取DOM样式导致的延迟
- 边界限制:用
Math.max设置最小宽度,防止组件缩至不可用状态
内容的提问来源于stack exchange,提问作者Sebastian Meckovski
相关产品推荐
相关产品推荐

