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

React+TypeScript实现面板元素自适应放置、边框拖拽调整功能

实现方案

一、修复基础布局

原有代码存在Grid属性配置错误的问题,先修正布局结构:

  • 明确三个面板的初始宽度参数,移除多余嵌套的Grid节点
  • 保证每个面板的height:100%和overflow:auto属性生效
  • 面板之间添加可拖拽的分隔条,默认宽度4px,悬浮时光标改为col-resize

二、实现拖拽调整尺寸功能

用TypeScript实现拖拽逻辑的核心步骤如下:

  1. 定义状态和ref存储拖拽相关数据,添加完整TS类型约束
  2. 绑定拖拽事件:
  • 分隔条触发mousedown事件时,记录当前鼠标位置和对应面板的初始宽度
  • 全局监听mousemove事件,根据鼠标水平偏移量动态更新面板宽度,设置最小宽度阈值避免面板被缩到不可见
  • 全局监听mouseup事件,结束拖拽并移除临时事件绑定

三、实现拖放功能

给每个面板添加拖放相关事件处理,明确拖放数据的类型:

  • dragOver事件阻止默认行为,允许元素放入
  • drop事件读取拖拽数据,可自定义放入后的业务逻辑

完整修改后代码

import React, { useCallback, useRef, useState } from "react";
import { Box, Grid } from "@material-ui/core";
import { makeStyles, Theme } from "@material-ui/core/styles";

const useStyles = makeStyles((theme: Theme) => ({
  leftPanel: {
    backgroundColor: "green",
    border: "1px solid black",
    overflow: "auto"
  },
  middlePanel: {
    backgroundColor: "mediumvioletred",
    border: "1px solid black",
    overflow: "auto"
  },
  rightPanel: {
    backgroundColor: "orange",
    border: "1px solid black",
    overflow: "auto"
  },
  divider: {
    width: "4px",
    backgroundColor: "#ccc",
    cursor: "col-resize",
    userSelect: "none",
    "&:hover": {
      backgroundColor: "#888"
    }
  }
}));

export default function Hello() {
  const classes = useStyles();
  const containerRef = useRef<HTMLDivElement>(null);
  const [panelWidths, setPanelWidths] = useState({
    left: 240,
    middle: 200
  });
  const dragState = useRef<{
    isDragging: boolean;
    activeBar: "left-middle" | "middle-right" | null;
    startX: number;
    startLeftWidth: number;
    startMiddleWidth: number;
  }>({
    isDragging: false,
    activeBar: null,
    startX: 0,
    startLeftWidth: 0,
    startMiddleWidth: 0
  });

  // 分隔条按下事件
  const handleDividerMouseDown = useCallback(
    (e: React.MouseEvent<HTMLDivElement>, barType: "left-middle" | "middle-right") => {
      e.preventDefault();
      dragState.current = {
        isDragging: true,
        activeBar: barType,
        startX: e.clientX,
        startLeftWidth: panelWidths.left,
        startMiddleWidth: panelWidths.middle
      };
      document.addEventListener("mousemove", handleMouseMove);
      document.addEventListener("mouseup", handleMouseUp);
    },
    [panelWidths]
  );

  // 鼠标移动事件
  const handleMouseMove = useCallback((e: MouseEvent) => {
    if (!dragState.current.isDragging) return;
    const deltaX = e.clientX - dragState.current.startX;
    if (dragState.current.activeBar === "left-middle") {
      const newLeftWidth = Math.max(100, dragState.current.startLeftWidth + deltaX);
      setPanelWidths(prev => ({ ...prev, left: newLeftWidth }));
    } else if (dragState.current.activeBar === "middle-right") {
      const newMiddleWidth = Math.max(100, dragState.current.startMiddleWidth + deltaX);
      setPanelWidths(prev => ({ ...prev, middle: newMiddleWidth }));
    }
  }, []);

  // 鼠标抬起事件
  const handleMouseUp = useCallback(() => {
    dragState.current.isDragging = false;
    dragState.current.activeBar = null;
    document.removeEventListener("mousemove", handleMouseMove);
    document.removeEventListener("mouseup", handleMouseUp);
  }, []);

  // 拖放相关处理
  const handleDragOver = useCallback((e: React.DragEvent<HTMLDivElement>) => {
    e.preventDefault();
  }, []);

  const handleDrop = useCallback((e: React.DragEvent<HTMLDivElement>, panel: string) => {
    e.preventDefault();
    const data = e.dataTransfer.getData("text/plain");
    // 此处添加元素放入后的业务处理逻辑
  }, []);

  return (
    <Grid
      ref={containerRef}
      spacing={0}
      container
      direction="row"
      style={{ height: "100vh", overflow: "hidden", flexGrow: 1 }}
    >
      {/* 左侧绿色面板 */}
      <Grid
        item
        style={{ width: `${panelWidths.left}px`, height: "100%" }}
        className={classes.leftPanel}
        onDragOver={handleDragOver}
        onDrop={(e) => handleDrop(e, "left")}
      >
        <Box m={2}>item link 1</Box>
        <Box m={2}>item link 2</Box>
        <Box m={2}>item link 3</Box>
        <Box m={2}>item link 4</Box>
      </Grid>

      {/* 左侧和中间面板的分隔条 */}
      <div
        className={classes.divider}
        onMouseDown={(e) => handleDividerMouseDown(e, "left-middle")}
      />

      {/* 中间粉色面板 */}
      <Grid
        item
        style={{ width: `${panelWidths.middle}px`, height: "100%" }}
        className={classes.middlePanel}
        onDragOver={handleDragOver}
        onDrop={(e) => handleDrop(e, "middle")}
      >
        <Box m={10}>item 11</Box>
        <Box m={10}>item 11</Box>
        <Box m={10}>item 13</Box>
        <Box m={10}>item 14</Box>
        <Box m={10}>item 15</Box>
        <Box m={10}>item 16</Box>
      </Grid>

      {/* 中间和右侧面板的分隔条 */}
      <div
        className={classes.divider}
        onMouseDown={(e) => handleDividerMouseDown(e, "middle-right")}
      />

      {/* 右侧橙色面板 */}
      <Grid
        item
        style={{ flexGrow: 1, height: "100%" }}
        className={classes.rightPanel}
        onDragOver={handleDragOver}
        onDrop={(e) => handleDrop(e, "right")}
      >
        <Box m={2}>item area 1</Box>
        <Box m={2}>item area 2</Box>
      </Grid>
    </Grid>
  );
}

功能说明

  • 拖拽分隔条即可调整对应面板的宽度,最小宽度限制为100px
  • 三个面板都支持拖放元素放入,可自行在handleDrop方法中扩展业务逻辑
  • 所有变量和事件都添加了TypeScript类型约束,符合TS语法规范
  • 右侧面板自动占满剩余空间,适配不同屏幕尺寸

内容的提问来源于stack exchange,提问作者Peter Penzov

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.07 12:12:03