React+TypeScript实现面板元素自适应放置、边框拖拽调整功能
实现方案
一、修复基础布局
原有代码存在Grid属性配置错误的问题,先修正布局结构:
- 明确三个面板的初始宽度参数,移除多余嵌套的Grid节点
- 保证每个面板的
height:100%和overflow:auto属性生效 - 面板之间添加可拖拽的分隔条,默认宽度4px,悬浮时光标改为
col-resize
二、实现拖拽调整尺寸功能
用TypeScript实现拖拽逻辑的核心步骤如下:
- 定义状态和ref存储拖拽相关数据,添加完整TS类型约束
- 绑定拖拽事件:
- 分隔条触发
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
相关产品推荐
相关产品推荐

