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

如何基于鼠标移动自动调整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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.11 10:56:01