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

如何实现两个联动的react grid layout?支持跨布局拖拽等交互

实现双联动React Grid Layout方案

核心思路

基于react-grid-layout的基础布局能力,结合HTML5原生拖拽API实现跨布局元素迁移,通过状态管理维护左右布局的元素数据,同时添加可拖动分隔线实现布局宽度自由调整。

关键实现步骤

1. 状态管理

  • 分别维护leftItems和rightItems数组,存储左右布局的元素数据(包含唯一ID、内容、宽高参数)
  • 用leftWidth变量控制左侧布局宽度,支持分隔线拖动调整

2. 左侧布局配置

  • 设置resizeHandles={[]},完全禁止元素调整尺寸
  • 给每个元素添加draggable="true"属性,在onDragStart事件中将元素数据存入dataTransfer,供右侧布局接收
  • 布局项的data-grid中设置y: Infinity,让元素自动向下排列,无需手动管理位置

3. 右侧布局配置

  • 开启resizeHandles={['se', 'sw', 'ne', 'nw']},允许元素从四个方向调整尺寸
  • 监听onDragOver和onDrop事件处理跨布局拖拽:
    • onDragOver需阻止默认行为,设置dropEffect为move以激活放置状态
    • onDrop中解析拖拽数据,从左侧数组移除对应元素,同时添加到右侧数组(注意给右侧元素ID加前缀避免冲突)
  • 通过onLayoutChange监听右侧元素的位置/尺寸变化,可用于持久化布局状态(如存入本地存储或后端)

4. 可拖动分隔线

  • 用窄div作为分隔线,设置cursor: col-resize提示可拖动
  • 监听mousedown事件,在鼠标移动时计算并更新左侧布局宽度,同时限制宽度范围(10%-90%)避免布局异常

完整代码示例

import React, { useState, useRef } from 'react';
import { Responsive, WidthProvider } from 'react-grid-layout';

const ResponsiveGridLayout = WidthProvider(Responsive);

const DualGrid = () => {
  // 左侧初始元素数据
  const [leftItems, setLeftItems] = useState([
    { id: 'item1', content: '左侧元素1', w: 2, h: 1 },
    { id: 'item2', content: '左侧元素2', w: 2, h: 1 },
    { id: 'item3', content: '左侧元素3', w: 2, h: 1 },
  ]);

  // 右侧初始元素数据
  const [rightItems, setRightItems] = useState([
    { id: 'right-item4', content: '右侧元素1', w: 3, h: 2 },
  ]);

  // 左侧布局宽度
  const [leftWidth, setLeftWidth] = useState('40%');
  const separatorRef = useRef(null);
  const containerRef = useRef(null);

  // 左侧元素拖拽开始事件
  const handleDragStart = (e, item) => {
    e.dataTransfer.setData('text/plain', JSON.stringify(item));
    e.dataTransfer.effectAllowed = 'move';
  };

  // 右侧布局拖拽进入事件
  const handleDragOver = (e) => {
    e.preventDefault();
    e.dataTransfer.dropEffect = 'move';
  };

  // 右侧布局放置事件
  const handleDrop = (e) => {
    e.preventDefault();
    const draggedItem = JSON.parse(e.dataTransfer.getData('text/plain'));
    // 从左侧移除元素
    setLeftItems(prev => prev.filter(item => item.id !== draggedItem.id));
    // 添加到右侧,ID加前缀避免冲突
    setRightItems(prev => [...prev, { ...draggedItem, id: `right-${draggedItem.id}` }]);
  };

  // 分隔线拖动调整宽度
  const handleSeparatorMouseDown = (e) => {
    e.preventDefault();
    const containerRect = containerRef.current.getBoundingClientRect();
    const startX = e.clientX;
    const startLeftWidth = parseFloat(leftWidth);

    const handleMouseMove = (moveEvent) => {
      const deltaX = moveEvent.clientX - startX;
      const newWidth = ((startLeftWidth / 100) * containerRect.width + deltaX) / containerRect.width * 100;
      // 限制宽度范围,避免布局异常
      if (newWidth > 10 && newWidth < 90) {
        setLeftWidth(`${newWidth}%`);
      }
    };

    const handleMouseUp = () => {
      document.removeEventListener('mousemove', handleMouseMove);
      document.removeEventListener('mouseup', handleMouseUp);
    };

    document.addEventListener('mousemove', handleMouseMove);
    document.addEventListener('mouseup', handleMouseUp);
  };

  // 右侧布局变化回调
  const handleRightLayoutChange = (newLayout) => {
    // 可在此处将布局状态存入localStorage或后端
    console.log('右侧布局更新:', newLayout);
  };

  return (
    <div ref={containerRef} style={{ display: 'flex', height: '100vh', gap: '0' }}>
      {/* 左侧布局容器 */}
      <div style={{ width: leftWidth, overflow: 'auto' }}>
        <ResponsiveGridLayout
          className="layout"
          breakpoints={{ lg: 1200, md: 996, sm: 768, xs: 480, xxs: 0 }}
          cols={{ lg: 4, md: 3, sm: 2, xs: 1, xxs: 1 }}
          rowHeight={60}
          resizeHandles={[]} // 禁止调整尺寸
        >
          {leftItems.map(item => (
            <div
              key={item.id}
              data-grid={{ w: item.w, h: item.h, x: 0, y: Infinity }}
              className="grid-item"
              draggable="true"
              onDragStart={(e) => handleDragStart(e, item)}
              style={{ backgroundColor: '#f0f0f0' }}
            >
              <div className="grid-item-content">{item.content}</div>
            </div>
          ))}
        </ResponsiveGridLayout>
      </div>

      {/* 分隔线 */}
      <div
        ref={separatorRef}
        style={{
          width: '2px',
          backgroundColor: '#ccc',
          cursor: 'col-resize',
          alignSelf: 'stretch',
        }}
        onMouseDown={handleSeparatorMouseDown}
      />

      {/* 右侧布局容器 */}
      <div style={{ flex: 1, overflow: 'auto' }} onDragOver={handleDragOver} onDrop={handleDrop}>
        <ResponsiveGridLayout
          className="layout"
          breakpoints={{ lg: 1200, md: 996, sm: 768, xs: 480, xxs: 0 }}
          cols={{ lg: 6, md: 4, sm: 3, xs: 2, xxs: 1 }}
          rowHeight={60}
          resizeHandles={['se', 'sw', 'ne', 'nw']} // 允许四向调整尺寸
          onLayoutChange={handleRightLayoutChange}
        >
          {rightItems.map(item => (
            <div
              key={item.id}
              data-grid={{ w: item.w, h: item.h, x: 0, y: Infinity }}
              className="grid-item"
              style={{ backgroundColor: '#e0e0e0' }}
            >
              <div className="grid-item-content">{item.content}</div>
            </div>
          ))}
        </ResponsiveGridLayout>
      </div>
    </div>
  );
};

// 基础样式注入
const styles = `
.layout {
  padding: 10px;
}
.grid-item {
  padding: 10px;
  border-radius: 4px;
  box-shadow: 0 1px 3px rgba(0,0,0,0.12), 0 1px 2px rgba(0,0,0,0.24);
}
.grid-item-content {
  height: 100%;
  display: flex;
  align-items: center;
  justify-content: center;
}
`;

const styleSheet = document.createElement("style");
styleSheet.textContent = styles;
document.head.appendChild(styleSheet);

export default DualGrid;

注意事项

  • 元素ID必须唯一,拖拽到右侧后建议添加前缀避免冲突
  • 若需要精确控制元素在右侧布局的放置位置,可在onDrop中计算鼠标位置对应的网格坐标,修改元素的x和y值
  • 可根据需求调整布局的breakpoints和cols配置,适配不同屏幕尺寸
  • 若需要持久化布局状态,可在onLayoutChange中将布局数据存入本地存储或后端

内容的提问来源于stack exchange,提问作者change world

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.13 08:46:00