react-grid-layout 实现原理介绍

简介

React-grid-layout 是一个基于 React 的网格布局库,它可以帮助我们轻松地在网格中管理和排列组件。它提供了一个直观的 API,可以通过配置网格的大小、列数和组件的位置来实现复杂的布局,还支持对网格中组件的拖拽和重新排列。

实现

诉求

  • 绘制:编排之后,可以根据配置对页面进行正确的绘制

  • 拖拽:支持位置调整,拖拽之后,调整对应配置参数

  • 缩放:支持大小调整,缩放之后,调整对应配置参数

  • 自动调整:当配置有错误、拖拽时有碰撞时,进行自动调整;布局空间的自动优化

示例:

https://react-grid-layout.github.io/react-grid-layout/examples/0-showcase.html

使用方式

const layout = [
  { i: 'a', x: 0, y: 1, w: 1, h: 1, static: true },
  { i: 'b', x: 1, y: 0, w: 3, h: 2 },
  { i: 'c', x: 4, y: 0, w: 1, h: 2 },
];


<GridLayout
  layouts={layout}
  margin={[10, 10]}
  containerPadding={[10,10]}
  cols=12
  rowHeight=150
  width=1200
  style={
  {}}
>
  {layout.map((item) => (
        <div key={item.i} className="item-style">
          <div>{item.i}</div>
          <div>
            xy{item.x}/{item.y}
          </div>
          <div>
            wh{item.w}/{item.h}
          </div>
        </div>
      ))}
</GridLayout>

绘制

容器

高度计算

const containerHeight = () => {
  // autoSize的情况下,不计算容器高度    
  if (!this.props.autoSize) { return; }
  // 底部坐标, 获取布局中y+h的最大值,即为容器的最大坐标h值    
  const nbRow = bottom(this.state.layout); 
  const containerPaddingY = this.props.containerPadding ? this.props.containerPadding[1] : this.props.margin[1];
  // 计算包含margin/padding的真实高度  
  return ${nbRow * this.props.rowHeight + (nbRow - 1) * this.props.margin[1] + containerPaddingY * 2}px;
}

compact布局

如果直接安装上述的计算方式绘制,可能由于配置的问题,会带来页面的浪费。因此react-grid-layout会默认对纵向空间进行压缩,以避免空间的浪费,也支持设置为横向压缩,或者不压缩。在压缩过程中,很关键的一部分在于对模块间碰撞的处理。

function compact(layout, compactType, cols) {
  // 对静态模块不进行压缩
  const compareWith = getStatics(layout); 
  // 排序 horizontal | vertical
  const sorted = sortLayoutItems(layout, compactType); 
  const out = Array(layout.length); 
  for (let i = 0, len = sorted.length; i < len; i++) {
    let l = cloneLayoutItem(sorted[i]);
    if (!l.static) {
      l = compactItem(compareWith, l, compactType, cols, sorted);
      compareWith.push(l);
    }
    // 放回正确的位置
    out[layout.indexOf(sorted[i])] = l;
    // 在处理冲突的时候设置flag
    l.moved = false;
  }

  return out;
}


// 压缩方法
function compactItem(
  compareWith: Layout,
  l: LayoutItem,
  compactType: CompactType,
  cols: number,
  fullLayout: Layout
): LayoutItem {
  const compactV = compactType === "vertical";
  const compactH = compactType === "horizontal";
  if (compactV) {
     // 垂直方向压缩,静态元素所需容器的高度 与 当前元素的纵向起始位置
    l.y = Math.min(bottom(compareWith), l.y);
    while (l.y > 0 && !getFirstCollision(compareWith, l)) {
      l.y--;
    }
  } else if (compactH) {
    while (l.x > 0 && !getFirstCollision(compareWith, l)) {
      l.x--;
    }
  }

  // 处理碰撞
  let collides;
  while ((collides = getFirstCollision(compareWith, l))) {
    if (compactH) {
      resolveCompactionCollision(fullLayout, l, collides.x + collides.w, "x");
    } else {
      resolveCompactionCollision(fullLayout, l, collides.y + collides.h, "y");
    }
    // 水平方向不超过最大值
    if (compactH && l.x + l.w > cols) {
      l.x = cols - l.w;
      l.y++;
    }
  }
  
  // 对上述的y--,x--做容错处理,确保没有负值
  l.y = Math.max(l.y, 0);
  l.x = Math.max(l.x, 0);
  
  return l;
}

function getFirstCollision(
  layout: Layout,
  layoutItem: LayoutItem
): ?LayoutItem {
  for (let i = 0, len = layout.length; i < len; i++) {
    // collides方法: 
    // 当前节点 或者节点出现在另一个节点的上方(下边界 > 其他节点的起始位置)/下方/右侧/左侧
    if (collides(layout[i], layoutItem)) return layout[i];
  }
}

function resolveCompactionCollision(
  layout: Layout,
  item: LayoutItem,
  moveToCoord: number,
  axis: "x" | "y"
) {
  const sizeProp = heightWidth[axis]; // x: w, y: h
  item[axis] += 1;
  const itemIndex = l
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值