如何基于鼠标移动自动调整React网格布局中Div的宽高?
实现可拖拽调整的网格布局
需求说明
布局结构为:左侧是占据整高的column1,右侧分为上下排列的column2和column3。需要实现两个交互:
- 鼠标悬停在column1与column2的分隔线时,显示左右箭头,拖动可调整两栏的宽度比例
- 鼠标悬停在column2与column3的分隔线时,拖动可调整两者的高度比例
现有代码
React组件
const Test = () => { return ( <div className="container"> <div className="column1"> <div className="content"> <h2>Column 1</h2> {/* Add content for Column 1 here */} </div> </div> <div className="column2"> <div className="content"> <h2>Column 2</h2> {/* Add content for Column 2 here */} </div> </div> <div className="column3"> <div className="content"> <h2>Column 3</h2> {/* Add content for Column 3 here */} </div> </div> </div> ); };
CSS样式
.container { display: grid; grid-template-columns: 1fr 2fr; grid-template-rows: 2fr 1fr; gap: 10px; height: 100vh; } .column1 { grid-column: 1; grid-row: 1 / span 2; border: 1px solid #ccc; padding: 10px; overflow: auto; } .column2 { grid-column: 2; grid-row: 1; border: 1px solid #ccc; padding: 10px; } .column3 { grid-column: 2; grid-row: 2; border: 1px solid #ccc; padding: 10px; }
实现方案
通过添加拖拽手柄、监听鼠标事件并动态更新Grid布局参数来实现需求,具体代码如下:
完整React组件代码
import { useState, useRef, useEffect } from 'react'; const Test = () => { // 列宽比例:[column1占比, column2/3占比] const [colRatio, setColRatio] = useState([1, 2]); // 行高比例:[column2占比, column3占比] const [rowRatio, setRowRatio] = useState([2, 1]); const isResizingCol = useRef(false); const isResizingRow = useRef(false); const startX = useRef(0); const startY = useRef(0); const startColRatio = useRef([1, 2]); const startRowRatio = useRef([2, 1]); // 开始左右拖拽 const startColResize = (e) => { isResizingCol.current = true; startX.current = e.clientX; startColRatio.current = [...colRatio]; document.addEventListener('mousemove', handleColResize); document.addEventListener('mouseup', stopColResize); e.preventDefault(); }; // 处理左右拖拽 const handleColResize = (e) => { if (!isResizingCol.current) return; const container = document.querySelector('.container'); const containerWidth = container.offsetWidth; const deltaX = e.clientX - startX.current; const deltaRatio = (deltaX / containerWidth) * (colRatio[0] + colRatio[1]); const newCol1Ratio = Math.max(0.5, startColRatio.current[0] + deltaRatio); const newCol2Ratio = Math.max(0.5, (colRatio[0] + colRatio[1]) - newCol1Ratio); setColRatio([newCol1Ratio, newCol2Ratio]); }; // 停止左右拖拽 const stopColResize = () => { isResizingCol.current = false; document.removeEventListener('mousemove', handleColResize); document.removeEventListener('mouseup', stopColResize); }; // 开始上下拖拽 const startRowResize = (e) => { isResizingRow.current = true; startY.current = e.clientY; startRowRatio.current = [...rowRatio]; document.addEventListener('mousemove', handleRowResize); document.addEventListener('mouseup', stopRowResize); e.preventDefault(); }; // 处理上下拖拽 const handleRowResize = (e) => { if (!isResizingRow.current) return; const container = document.querySelector('.container'); const containerHeight = container.offsetHeight; const deltaY = e.clientY - startY.current; const deltaRatio = (deltaY / containerHeight) * (rowRatio[0] + rowRatio[1]); const newRow1Ratio = Math.max(0.5, startRowRatio.current[0] + deltaRatio); const newRow2Ratio = Math.max(0.5, (rowRatio[0] + rowRatio[1]) - newRow1Ratio); setRowRatio([newRow1Ratio, newRow2Ratio]); }; // 停止上下拖拽 const stopRowResize = () => { isResizingRow.current = false; document.removeEventListener('mousemove', handleRowResize); document.removeEventListener('mouseup', stopRowResize); }; return ( <div className="container" style={{ gridTemplateColumns: `${colRatio[0]}fr ${colRatio[1]}fr`, gridTemplateRows: `${rowRatio[0]}fr ${rowRatio[1]}fr` }}> <div className="column1"> <div className="content"> <h2>Column 1</h2> </div> </div> {/* 左右拖拽手柄 */} <div className="resizer resizer-col" onMouseDown={(e) => startColResize(e)} /> <div className="column2"> <div className="content"> <h2>Column 2</h2> </div> </div> {/* 上下拖拽手柄 */} <div className="resizer resizer-row" onMouseDown={(e) => startRowResize(e)} /> <div className="column3"> <div className="content"> <h2>Column 3</h2> </div> </div> </div> ); }; export default Test;
更新后的CSS样式
.container { display: grid; gap: 10px; height: 100vh; position: relative; } .column1 { grid-column: 1; grid-row: 1 / span 2; border: 1px solid #ccc; padding: 10px; overflow: auto; } .column2 { grid-column: 2; grid-row: 1; border: 1px solid #ccc; padding: 10px; } .column3 { grid-column: 2; grid-row: 2; border: 1px solid #ccc; padding: 10px; } /* 拖拽手柄通用样式 */ .resizer { position: absolute; background-color: transparent; z-index: 10; transition: background-color 0.2s; } /* 左右拖拽手柄 */ .resizer-col { width: 6px; left: calc((1 / 3) * 100% + 5px); top: 0; bottom: 0; cursor: col-resize; } .resizer-col:hover { background-color: #999; } /* 上下拖拽手柄 */ .resizer-row { height: 6px; left: calc((1 / 3) * 100% + 10px); right: 10px; top: calc((2 / 3) * 100% + 5px); cursor: row-resize; } .resizer-row:hover { background-color: #999; }
关键说明
- 用
useRef保存拖拽状态与初始值,避免状态更新引发的重复渲染问题 - 拖拽时通过计算鼠标位移与容器尺寸的比例,动态更新Grid的
fr单位比例 - 设置最小比例(0.5fr)防止栏位被拖至过小
- 拖拽手柄采用绝对定位,精准对应Grid分隔线位置,hover时显示背景提示提升交互感知
内容的提问来源于stack exchange,提问作者boyenec
相关产品推荐
相关产品推荐

