react-grid-layout 栅格布局

11,397 阅读9分钟

前言

最近接触了一个 react-grid-layout 栅格布局系统,在使用过程中发现它的功能很多,但是对于其中的属性的使用和事件有很多不理解的地方,所以这里归纳(翻译)一下它的属性及用途

react-grid-layout

React-Grid-Layout 是只提供给 React 组件并且不需要使用 JQuery 的栅格布局系统,类似的像:Packery Gridster,但 RGL 是响应式的,并且支持 breakpoints 断点,用户可以自定义设置断点,也可以由 RGL 自动生成。来自官网的强大动图:

RGL

Grid Layout Props

RGL 支持下面这些属性:

基础属性

// 基础属性
// 在服务器端设置的初始宽度,除非你使用了 HOC  <WidthProvider> 或者其他类似的组件,那么 width 属性是必须的。
width: number,

// 当为 true 的时候,容器高度自适应以适应内容
autoSize: ?boolean = true,

// 将当前布局分为多少列
cols: ?number = 12|{},

// 属性值为标签的 css 选择器,被标记后标签就不可拖动了
// 比如说:draggableCancel:'.MyNonDraggableAreaClassName'
draggableCancel: ?string = '',

// 属性值为标签的 css 选择器,被标记后标签就可以被拖动
// 比如说: draggableHandle:'.MyDragHandleClassName'
draggableHandle: ?string = '',

// 为 true 时,会在竖直方向上紧凑排列
verticalCompact: ?boolean = true,

// 紧凑排列的方向
compactType: ?('vertical' | 'horizontal') = 'vertical';

// layouts 属性值是由这样格式{x: number, y: number, w: number, h: number}的对象组成的数组
// 需要注意的是,layout在数组中的下标必须和他们作用的组件的 key 值匹配
// 如果希望在组件中使用自定义的 key 值,那么你可以在 layout 中指定那个 key 值
// 最后这个对象数组可能像是这样: {i: string, x: number, y: number, w: number, h: number}
// 也可以在子组件中单独设置
layout: ?array = null, 

// 每个包含块之间 margin:[x,y] 单位为 px
margin: ?[number, number] = [10, 10],

//包含块内部的 padding:[x,y] 单位为 px
// Padding inside the container [x, y] in px
containerPadding: ?[number, number] = margin,

// 每行的静态高度,但是如果需要的话你可以基于 breakpoints 来改变它,
rowHeight: ?number = 150,

// droping 元素的配置,它是一个"虚拟的"元素,当您从外部拖动某个元素时,它会出现
// 它可以通过传递特定参数被改变:
//  i - 元素的 id
//  w - 元素的宽度
//  h - 元素的高度
droppingItem?: { i: string, w: number, h: number }

//
// Flags
//
isDraggable: ?boolean = true,
isResizable: ?boolean = true,
// 使用css3的translate() 代替定位中的 top 和 left,能使绘制过程的性能提升 6 倍
useCSSTransforms: ?boolean = true,
// 如果父节点:ResponsiveReactGridLayout 或者 ReactGridLayout 有 transform: scale(n) 的 css 属性
// 我们应该设置比例系数,以避免在拖动的过程中 render artefacts
transformScale: ?number = 1,

// 如果设置true,在拖拽过程当中,被拖拽的组件不会改变位置
preventCollision: ?boolean = false;

// 如果设置为 true,可拖动的元素(带有 draggable={true} 属性)可以在网格上被拖动,他会引发 "onDrop" 回调函数,位置信息和事件对象 作为参数会被传到回调函数中,

// 注意:假如使用的是 Firefox 你应该添加 onDragStart={e => e.dataTransfer.setData('text/plain', '')} 属性和 draggable={true},否则这个特性不会正确的工作
// Firefox 需 要onDragStart 属性来进行拖放初始化
// @see https://bugzilla.mozilla.org/show_bug.cgi?id=568313
isDroppable: ?boolean = false

//
// Callbacks
//

// 用来保存布局的回调,每次 drag 或者 resize 结束之后返回当前的布局
onLayoutChange: (layout: Layout) => void,

//下面所有的回调都有这些参数(layout, oldItem, newItem, placeholder, e, element)
//开始和结束的回调函数中参数:placeholder为undefined
//
type ItemCallback = (layout: Layout, oldItem: LayoutItem, newItem: LayoutItem,
                     placeholder: LayoutItem, e: MouseEvent, element: HTMLElement) => void;


onDragStart: ItemCallback,
onDrag: ItemCallback,
onDragStop: ItemCallback,
onResizeStart: ItemCallback,
onResize: ItemCallback,
onResizeStop: ItemCallback,
onDrop: (elemParams: { x: number, y: number, e: Event }) => void

响应式栅格布局属性

响应式栅格布局是可以被使用的,它支持👆除 layout 之外所有的基本属性,新属性和改变的部分如下:

// 断点名是任意的,但必须与cols和layouts对象匹配。
breakpoints: ?Object = {lg: 1200, md: 996, sm: 768, xs: 480, xxs: 0},

// 针对 cols 的断点
cols: ?Object = {lg: 12, md: 10, sm: 6, xs: 4, xxs: 2},

// 固定间隔或者响应式间隔 e.g.[10,10]| `{lg: [10, 10], md: [10, 10], ...}.
margin: [number, number] | {[breakpoint: $Keys<breakpoints>]: [number, number]}


// 同上,固定内边距或者响应式内边距:e.g. [10,10]|`{lg: [10, 10], md: [10, 10], ...}
containerPadding: [number, number] | {[breakpoint: $Keys<breakpoints>]: [number, number]}


// 由断点查找的 layouts 对象 e.g. {lg: Layout, md: Layout, ...}
layouts: {[key: $Keys<breakpoints>]: Layout}

//
// Callbacks
//

onBreakpointChange: (newBreakpoint: string, newCols: number) => void,

onLayoutChange: (currentLayout: Layout, allLayouts: {[key: $Keys<breakpoints>]: Layout}) => void,

// 当页面宽度变化时的回调,这样你就可以根据需要修改 layout 啦
onWidthChange: (containerWidth: number, margin: [number, number], cols: number, containerPadding: [number, number]) => void;

栅格元素的属性

请注意,如果提供了一个网格项,但不完整(缺少x、y、w或h中的一个)的时候将抛出一个错误,以便您可以更正布局。

如果没有为网格项提供属性,那么将生成一个宽度和高度为1的属性。

您可以为每个维度设置最小值和最大值。这是用来调整大小的;当然,如果禁用了调整大小的功能,则不会产生任何影响。如果你的最小值和最大值互相重叠,或者你的初始尺寸超出范围将会抛出错误。

任何直接定义在<GridItem>中的属性都将优先于全局中的属性。例如,如果布局的属性是isDraggable: false,但是网格项 props 对象中的 isDraggable: true,那么这个网格项就是draggable,即使它被标记为static: true。

{

  // 组件的 key 值
  i: string,

  // 这些都是网格单元,而不是像素
  x: number, // 占几列
  y: number, // rowHeight 的倍数
  w: number, // 同 x 计算方法
  h: number, // 同 y 计算方法
  minW: ?number = 0,
  maxW: ?number = Infinity,
  minH: ?number = 0,
  maxH: ?number = Infinity,

  // 为 true 的时候相当于设置 "isDraggable: false, isResizable: false"
  static: ?boolean = false,
  // 为 false 的时候,不能被拖动,覆盖掉 static 属性
  isDraggable: ?boolean = true,
  // 为 false 的时候,不能重新设置大小,覆盖掉 static 属性
  isResizable: ?boolean = true
}

使用

组件的选取

非响应式直接引用 GridLayout 组件,如下:

import GridLayout from 'react-grid-layout';

GridLayout 不支持cols、layouts 等支持基于断点调整值的属性,这些属性的属性值为断点调整的对象(eg:{lg: 1200, md: 996, sm: 768, xs: 480, xxs: 0}),强行使用会使页面展示效果与预想的不一致,也就是出错了,当想使用响应式的布局的时候提倡使用下面的组件。

响应式的使用方法:

当使用响应式模式时,您应该使 layouts 属性值至少提供一个断点下的布局,并且暂时不支持为单个 grid-item 设置断点布局。

1、使用 ResponsiveGridLayout 组件

import { Responsive as ResponsiveGridLayout } from 'react-grid-layout';

2、使用 WidthProvider 函数包装后的组件

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

const ResponsiveGridLayout = WidthProvider(Responsive);

区别: <ResponsiveGridLayout> 和 <ReactGridLayout> 组件在拖动事件中用 width 去计算位置,因此当使用这两个组件的时候 width 是必须的。现在有更简单的组件:WidthProvider ,width 属性对它不是必须的,当用 WidthProvider 包裹一个组件后会在初始化(initialization)的时候和 resize 事件中自动计算宽度

断点的使用

上面提到的很多属性,那些属性值可以为对象的属性都支持根据断点来控制元素的布局,比如说:cols、layouts、containerPadding、margin,它们的使用方法大致相同,以 margin 为例:

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

const ResponsiveGridLayout = WidthProvider(Responsive);
export default class MyFirstGrid extends React.Component {
  render() {
    const layout = [
      {i: 'a', x: 0, y: 0, w: 1, h: 2, static: true},
      {i: 'b', x: 1, y: 0, w: 2, h: 2, minW: 2, maxW: 4},
      {i: 'c', x: 0, y: 2, w: 1, h: 2}
    ];
    return (
      <ResponsiveGridLayout className="layout" 
        layouts={{lg:layout} } 
        rowHeight={30}
        breakpoints={{lg: 1200, md: 996, sm: 768, xs: 480, xxs: 0}}
        margin={{lg: [30,30], md: [20,20], sm: [10,10], xs: [5,5]}}
      >
        <div key="a" style={{"background": "red"}}>a</div>
        <div key="b" style={{"background": "red"}}>b</div>
        <div key="c" style={{"background": "red"}}>c</div>
      </ResponsiveGridLayout>
    )
  }
}

效果如下:

可以看见在断点处会出现元素间隔的变化。

复杂一点的,如果想控制元素的位置即每个元素的 x,y 坐标,则可以使用 layouts 属性,针对屏幕不同的尺寸更改元素的位置。

一些可以辅助响应式变化的的事件函数

  • onBreakpointChange 页面大小从一个断点区间过渡到另一个区间的时候触发,当想响应式的修改其他原生不支持响应式的属性的时候(eg: rowHeight)可以在这里面修改
  • onLayoutChange 类似于 onBreakpointChange 会在断点的交界处被触发,回调函数会传入当前 layout 和 layouts 的属性值,可以在里面修改对应 key 的布局
  • onWidthChange 页面尺寸变化的回调,传入容器宽度、当前 margin 的值,cols 值、containerPadding 值

巧用紧凑型对齐方式

整个页面以左上角为 (0,0)点。

在基本的属性中注意到有两个属性:verticalCompact 和 compactType,分别取值为 boolean 类型和 string 类型,表示是否采用垂直方向的紧凑对齐或者在什么方向上紧凑对齐。当 compactType 和 verticalCompact 同时存在的时候,compactType 优先级比 verticalCompact 的优先级高。

使用不同的对齐方式会让页面的排布有不一样的神奇效果: 使用垂直方向上的对齐,当横坐标相同的位置上如果这个元素的前面已经有元素占据了前面的位置(即纵坐标也相同),那么这个元素排在相同横坐标下最后一个元素的下方,否则排在(x,0)的位置;

特别的是:当水平方向上的空间不足时,谁的 w 值越大谁越靠近 y = 0;

使用水平方向上的对齐,横纵都坐标相同时,从左到右后来者居左,标记为 static 的除外,会保持在自己的 x 坐标上,纵坐标相同横坐标不同,按照横坐标的数值大小从左到右排列,横坐标相同纵坐标不同,在这个纵坐标前面已经有元素占据了位置,那么排在最后一个元素的右边。

总结

在一些业务场景下,需要使用到这样的栅格系统来对页面进行分片再填充,那么使用 RGL 是比较方便的,但在实践过程中可能是了解的不够深入吧,按照github上的例子实践的时候也出现了一些问题比如即使设置了 isResizable 之后也不能对 grid-item 进行resize,对于他的响应特性、可拖动特性、grid-item 的 resize特性会在以后的实践中再继续总结补充。