You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.13 16:44:51