React实现列宽拖拽调整及多区域布局适配技术问题
React 实现列拖拽调整宽度功能
需求与问题背景
需要在React应用中实现列间拖拽条调整宽度的交互,效果为鼠标左右/上下移动时对应列宽/高度随之变化。
初始两列布局JSX代码:
... return( <> <Container fluid> <Row> <Col xs={3} style={{ backgroundColor: "red" }}> Column 1 </Col> {/* 希望在两列间添加拖拽条,实现鼠标移动增减列宽的功能 */} <Col style={{ backgroundColor: "black" }}>column 2</Col> </Row> </Container> </> )
随后尝试的React代码未生效:
const Admin = () => { const [leftColumnWidth, setLeftColumnWidth] = useState(3); // 左侧列初始宽度 const [isResizing, setIsResizing] = useState(false); const handleMouseDown = () => { setIsResizing(true); }; const handleMouseUp = () => { setIsResizing(false); }; const handleMouseMove = (e) => { if (!isResizing) return; const containerWidth = e.currentTarget.offsetWidth; const mouseX = e.clientX; const newLeftColumnWidth = Math.min( Math.max((mouseX / containerWidth) * 100, 10), // 最小宽度10% 90 // 最大宽度90% ); setLeftColumnWidth(newLeftColumnWidth); }; return ( <> <Container fluid onMouseMove={handleMouseMove} onMouseUp={handleMouseUp}> <Row> <Col xs={leftColumnWidth} style={{ backgroundColor: "red", borderRight: "1px solid black", height: "100vh", overflow: "hidden", }} > 1 of 3 <div style={{ width: "10px", cursor: "col-resize", position: "absolute", right: 0, top: 0, bottom: 0, }} onMouseDown={handleMouseDown} ></div> </Col> <Col style={{ backgroundColor: "black", height: "100vh" }}> 2 of 3 (wider) </Col> </Row> </Container> </> ); };
采用Khurram Shafique的方案后,两列水平拖拽功能生效,但还需在A、B、C区域布局中实现B、C区域间的垂直拖拽调整功能,当前A、B、C布局代码:
<div ref={containerRef} style={{ display: "flex", height: "100vh", width: "100%" }} > <div style={{ width: `${columnWidth}%`, background: "lightgrey", overflow: "auto", // 内容超出高度时显示滚动条 }} > <div style={{ height: "500px", padding: "16px" }}>Column A</div> </div> <div ref={handleRef} onMouseDown={onMouseDown} style={{ cursor: "ew-resize", width: "10px", background: "grey" }} /> <div style={{ width: `${100 - columnWidth}%`, display: "flex", flexDirection: "column", }} > <div style={{ background: "lightblue", overflow: "auto", // 内容超出高度时显示滚动条 flex: "70%", // 占可用空间的70% }} > <div style={{ height: "300px", padding: "16px" }}>Section B</div> </div> <div style={{ background: "lightgreen", height: "30%", // 占可用空间的30% overflow: "auto", // 内容超出高度时显示滚动条 }} > Section C </div> </div> </div>
完整解决方案
1. 修复两列水平拖拽问题
原代码问题在于误用Bootstrap栅格xs属性(仅接受0-12整数,非百分比),改为直接用百分比控制宽度,并将拖拽事件绑定到document避免鼠标移出容器失效:
import { useState } from 'react'; const Admin = () => { const [leftWidth, setLeftWidth] = useState(25); // 初始占比25% const [isResizing, setIsResizing] = useState(false); const startResize = () => { setIsResizing(true); document.addEventListener('mousemove', handleResize); document.addEventListener('mouseup', stopResize); }; const handleResize = (e) => { if (!isResizing) return; const container = document.querySelector('.resize-container'); const newWidth = (e.clientX / container.offsetWidth) * 100; // 限制宽度范围10%-90%,防止内容被过度压缩 setLeftWidth(Math.max(10, Math.min(90, newWidth))); }; const stopResize = () => { setIsResizing(false); document.removeEventListener('mousemove', handleResize); document.removeEventListener('mouseup', stopResize); }; return ( <div className="resize-container" style={{ display: 'flex', height: '100vh', width: '100%' }}> <div style={{ width: `${leftWidth}%`, backgroundColor: 'red', position: 'relative', overflow: 'auto' }}> Column 1 <div style={{ position: 'absolute', right: 0, top: 0, bottom: 0, width: '10px', cursor: 'col-resize', backgroundColor: 'black', opacity: 0.5 }} onMouseDown={startResize} /> </div> <div style={{ width: `${100 - leftWidth}%`, backgroundColor: 'black', color: 'white', overflow: 'auto' }}> Column 2 </div> </div> ); }; export default Admin;
2. 实现A、B、C布局的垂直+水平拖拽
在水平拖拽基础上,新增垂直拖拽逻辑,控制B、C区域的高度占比:
import { useState, useRef } from 'react'; const Layout = () => { // 水平拖拽:控制A列宽度 const [columnWidth, setColumnWidth] = useState(25); const [isHorizontalResizing, setIsHorizontalResizing] = useState(false); // 垂直拖拽:控制B区域高度占比 const [bHeightRatio, setBHeightRatio] = useState(70); const [isVerticalResizing, setIsVerticalResizing] = useState(false); const rightContainerRef = useRef(null); // 水平拖拽逻辑 const startHorizontalResize = () => { setIsHorizontalResizing(true); document.addEventListener('mousemove', handleHorizontalResize); document.addEventListener('mouseup', stopHorizontalResize); }; const handleHorizontalResize = (e) => { if (!isHorizontalResizing) return; const container = document.querySelector('.main-container'); const newWidth = (e.clientX / container.offsetWidth) * 100; setColumnWidth(Math.max(10, Math.min(80, newWidth))); }; const stopHorizontalResize = () => { setIsHorizontalResizing(false); document.removeEventListener('mousemove', handleHorizontalResize); document.removeEventListener('mouseup', stopHorizontalResize); }; // 垂直拖拽逻辑 const startVerticalResize = () => { setIsVerticalResizing(true); document.addEventListener('mousemove', handleVerticalResize); document.addEventListener('mouseup', stopVerticalResize); }; const handleVerticalResize = (e) => { if (!isVerticalResizing || !rightContainerRef.current) return; const containerRect = rightContainerRef.current.getBoundingClientRect(); const containerHeight = containerRect.height; const mouseY = e.clientY - containerRect.top; const newRatio = (mouseY / containerHeight) * 100; // 限制高度范围20%-80% setBHeightRatio(Math.max(20, Math.min(80, newRatio))); }; const stopVerticalResize = () => { setIsVerticalResizing(false); document.removeEventListener('mousemove', handleVerticalResize); document.removeEventListener('mouseup', stopVerticalResize); }; return ( <div className="main-container" style={{ display: "flex", height: "100vh", width: "100%" }}> {/* 列A */} <div style={{ width: `${columnWidth}%`, background: "lightgrey", overflow: "auto", }} > <div style={{ height: "500px", padding: "16px" }}>Column A</div> </div> {/* 水平拖拽条 */} <div onMouseDown={startHorizontalResize} style={{ cursor: "ew-resize", width: "10px", background: "grey" }} /> {/* 右侧容器:包含B和C */} <div ref={rightContainerRef} style={{ width: `${100 - columnWidth}%`, display: "flex", flexDirection: "column", }} > {/* 区域B */} <div style={{ background: "lightblue", overflow: "auto", height: `${bHeightRatio}%`, }} > <div style={{ height: "300px", padding: "16px" }}>Section B</div> </div> {/* 垂直拖拽条 */} <div onMouseDown={startVerticalResize} style={{ cursor: "ns-resize", height: "10px", background: "grey" }} /> {/* 区域C */} <div style={{ background: "lightgreen", height: `${100 - bHeightRatio}%`, overflow: "auto", }} > Section C </div> </div> </div> ); }; export default Layout;
核心优化点
- 将拖拽事件绑定到
document,避免鼠标移出容器后失去响应 - 为宽度/高度设置范围限制,防止列内容被过度压缩
- 使用
useRef获取容器位置信息,精准计算垂直拖拽的高度比例 - 为拖拽条设置对应光标样式(
col-resize/ns-resize),提升交互直观性
内容的提问来源于stack exchange,提问作者boyenec
相关产品推荐
相关产品推荐

