react-grid-layout如何限制单元格拖拽仅在10x1单行互换不新增行
react-grid-layout 10x1单行网格拖拽互换位置实现方案
问题背景
基于react-grid-layout实现10列1行(10x1)的网格布局,所有单元格元素均支持拖拽移动,但存在以下问题:
- 拖拽单元格到其他列位置时,布局会自动新增额外行
- 无法将所有拖拽位移行为限制在初始的10x1单行范围内
- 默认推挤逻辑不符合预期,期望拖拽单元格时仅在单行范围内与目标位置的单元格直接互换位置,不会挤压其他元素生成新行
原有实现代码
import '/node_modules/react-grid-layout/css/styles.css' import '/node_modules/react-resizable/css/styles.css' import GridLayout from "react-grid-layout"; export function MyFirstGrid () { const widthCell=1; const heightCell=1; const layout = [ { i: "0000", x: 0, y: 0, w: widthCell, h: heightCell,isResizable:false}, { i: "0100", x: 1, y: 0, w: widthCell, h: heightCell,isResizable:false}, { i: "0200", x: 2, y: 0, w: widthCell, h: heightCell,isResizable:false}, { i: "0300", x: 3, y: 0, w: widthCell, h: heightCell,isResizable:false}, { i: "0400", x: 4, y: 0, w: widthCell, h: heightCell,isResizable:false}, { i: "0500", x: 5, y: 0, w: widthCell, h: heightCell,isResizable:false}, { i: "0600", x: 6, y: 0, w: widthCell, h: heightCell,isResizable:false}, { i: "0700", x: 7, y: 0, w: widthCell, h: heightCell,isResizable:false}, { i: "0800", x: 8, y: 0, w: widthCell, h: heightCell,isResizable:false}, { i: "0900", x: 9, y: 0, w: widthCell, h: heightCell,isResizable:false} ]; const getLayouts = () => { const savedLayouts = localStorage.getItem("grid-layout"); return savedLayouts ? JSON.parse(savedLayouts) : layout; }; const handleLayoutChange = (layout) => { localStorage.setItem("grid-layout", JSON.stringify(layout)); }; const borderAdditive="border" return (<div> <GridLayout layout={getLayouts()} className="layout" cols={10} rowHeight={50} width={1200} onLayoutChange={handleLayoutChange} margin={[1,1]} > <div className={borderAdditive} key="0000">0000</div> <div className={borderAdditive} key="0100">0100</div> <div className={borderAdditive} key="0200">0200</div> <div className={borderAdditive} key="0300">0300</div> <div className={borderAdditive} key="0400">0400</div> <div className={borderAdditive} key="0500">0500</div> <div className={borderAdditive} key="0600">0600</div> <div className={borderAdditive} key="0700">0700</div> <div className={borderAdditive} key="0800">0800</div> <div className={borderAdditive} key="0900">0900</div> </GridLayout> </div> ); }
问题原因
react-grid-layout默认开启自动紧凑排列、垂直方向无行数限制,拖拽时的碰撞逻辑为推挤其他元素而非互换位置,当水平方向空间被占满时,被推挤的元素会自动换行到下一行,就会出现新增额外行的问题。
实现方案
需要通过组件属性配置+自定义拖拽逻辑两个层面实现需求:
- 配置核心属性限制布局范围
- 设置
maxRows={1}:强制布局最大行数为1,从根源禁止新增行 - 设置
compactType={null}:关闭自动紧凑重排逻辑,避免元素被自动推挤移位 - 设置
isBounded={true}:限制拖拽边界不超出网格容器范围 - 设置
preventCollision={true}:禁止元素碰撞时自动推挤其他元素 - 保留所有元素
isResizable:false配置,禁止尺寸调整
- 设置
- 自定义拖拽逻辑实现位置互换
用useState将layout改为受控状态,在拖拽回调中强制所有元素y坐标始终为0,拖拽结束时检测目标位置是否存在其他元素,若存在则直接交换两个元素的x坐标,实现互换效果。
修正后完整代码
import '/node_modules/react-grid-layout/css/styles.css' import '/node_modules/react-resizable/css/styles.css' import GridLayout from "react-grid-layout"; import { useState, useEffect } from "react"; export function MyFirstGrid () { const widthCell=1; const heightCell=1; const defaultLayout = [ { i: "0000", x: 0, y: 0, w: widthCell, h: heightCell,isResizable:false}, { i: "0100", x: 1, y: 0, w: widthCell, h: heightCell,isResizable:false}, { i: "0200", x: 2, y: 0, w: widthCell, h: heightCell,isResizable:false}, { i: "0300", x: 3, y: 0, w: widthCell, h: heightCell,isResizable:false}, { i: "0400", x: 4, y: 0, w: widthCell, h: heightCell,isResizable:false}, { i: "0500", x: 5, y: 0, w: widthCell, h: heightCell,isResizable:false}, { i: "0600", x: 6, y: 0, w: widthCell, h: heightCell,isResizable:false}, { i: "0700", x: 7, y: 0, w: widthCell, h: heightCell,isResizable:false}, { i: "0800", x: 8, y: 0, w: widthCell, h: heightCell,isResizable:false}, { i: "0900", x: 9, y: 0, w: widthCell, h: heightCell,isResizable:false} ]; const [layout, setLayout] = useState(defaultLayout); // 初始化读取本地存储布局 useEffect(() => { const savedLayouts = localStorage.getItem("grid-layout"); if (savedLayouts) { setLayout(JSON.parse(savedLayouts)) } }, []) // 拖拽过程中强制所有元素y坐标为0,禁止垂直方向移动 const handleDrag = (_, oldItem, newItem) => { // 修正拖拽元素y坐标始终为0,x坐标限制在0-9范围内 const correctedNewItem = { ...newItem, y: 0, x: Math.max(0, Math.min(9, newItem.x)) } setLayout(prev => prev.map(item => { if (item.i === correctedNewItem.i) return correctedNewItem // 其余元素保持y=0,不做推挤 return {...item, y:0} })) } // 拖拽结束实现位置互换 const handleDragStop = (_, _, newItem) => { const targetX = Math.max(0, Math.min(9, newItem.x)) setLayout(prev => { const draggedItem = prev.find(item => item.i === newItem.i) const targetItem = prev.find(item => item.x === targetX && item.i !== newItem.i) let newLayout = [...prev] if (targetItem) { // 存在目标位置元素,交换x坐标 newLayout = newLayout.map(item => { if (item.i === draggedItem.i) { return {...item, x: targetX, y:0} } if (item.i === targetItem.i) { return {...item, x: draggedItem.x, y:0} } return {...item, y:0} }) } else { // 无目标元素直接落到对应位置 newLayout = newLayout.map(item => { if (item.i === draggedItem.i) { return {...item, x: targetX, y:0} } return {...item, y:0} }) } // 持久化存储 localStorage.setItem("grid-layout", JSON.stringify(newLayout)); return newLayout }) } const borderAdditive="border" return ( <div> <GridLayout layout={layout} className="layout" cols={10} rowHeight={50} width={1200} maxRows={1} compactType={null} isBounded={true} preventCollision={true} margin={[1,1]} onDrag={handleDrag} onDragStop={handleDragStop} > <div className={borderAdditive} key="0000">0000</div> <div className={borderAdditive} key="0100">0100</div> <div className={borderAdditive} key="0200">0200</div> <div className={borderAdditive} key="0300">0300</div> <div className={borderAdditive} key="0400">0400</div> <div className={borderAdditive} key="0500">0500</div> <div className={borderAdditive} key="0600">0600</div> <div className={borderAdditive} key="0700">0700</div> <div className={borderAdditive} key="0800">0800</div> <div className={borderAdditive} key="0900">0900</div> </GridLayout> </div> ); }
内容的提问来源于stack exchange,提问作者grojas123
相关产品推荐
相关产品推荐

