BI布局拖拽 (1) 深入react-gird-layout源码

因为有个拖拉拽的需求,类似于quickBi那样的效果。在网上调研了一下发现react-grid-layout实现效果类似,但其也有局限性,比如不支持嵌套,不支持在多个gridLyaout之间互相拖拽。

要求:基于react-grid-layout的思路,改造,让其支持嵌套等效果。

所以本篇只深入拖拽原理,其他的像resize等先略过

react-grid-layout整体思路

首先拉下代码

主要看ReactGridLayout和GritItem这两个组件。

用法:

具体更细节用法可以看官网,这里我们知道了他主要接收了layout的一个数组,

结构大概是这样

w,h,x,y,顾名思义是宽高,在grid布局中第x行,第y列。

大概流程图

ReactGridLayout->GirdItem->ReactDraggable

react-grid-layout底层依赖了react-draggable这个库。

react-draggable

看一下react-draggable这个库,拉下来代码主要看

DraggableCode这个组件,上面的Draggable是有状态管理的。

DraggableCode

主要是将children clone后,绑上onMouseDown事件等等



handleDragStart主要是计算得到获取鼠标按下的坐标,然后保存起来,调用props.onStart传出去。

接着在document上监听mouseMove和mouseup事件。这样一旦开始拖拽,就会触发对应事件。

在看下mousemove触发的hadnleDrag事件

createCoreData用来计算当前位移的距离,deltaX表示在x上位移了多少px等。

handleDrag事件也是一样,计算新的位置信息然后调用props.onDrag事件。

在看mouseup触发的handelStop

也是一样计算位置信息,然后调用props.onStop事件,

最后是一些变量重置事件取消监听等等。

到这里我们知道react-draggable主要是通过绑定mouse事件,然后计算位置信息传出。

ReactGridLayout

现在我们知道了,拖拽能力由底层react-draggable提供。

接着我们看ReactridLayout这一层

用法:

主要是layout数组,用于保存每个节点的位置信息,以及children,必须是一个数组,且最好是原生标签,而不是react组件。这个后面就会知道了

这里主要看这三个,

第一个就是渲染children,通过React.Children.map,将children包装了一层。

第二个主要是渲染从外部拖进来的元素,这个后面在看。

第三个则是一个占位符的渲染,像拖拽时下面的红色占位符。

再具体看下processGriditem

主要是包了一层GridItem组件,然后传入一些属性和对应事件。

我们看下GridItem组件主要做了什么。

GridItem

可以看到,GridItem主要是将children clone了一下,加上了专属的样式,类名,已经绑定了一个ref,所以我们传入的children最好是原生标签,如果是react组件,需要自己透传这些属性/类名,否则拖拽不生效。

我们这里主要看mixinDraggable函数,拖拽事件,缩放也是通过react-draggable组件能力提供的。

mixin混入,为children提供绑定拖拽的能力。

这里就比较简单了,直接将child包了一层DraggbaleCore组件。

再看一下这个流程图

现在理解react-draggable提供底层拖拽能力,主要是绑定时间,获取对应的位置信息,传递给GridtIem组件,然后看下GridItem组件是怎么消费这些信息的。

首先看下onDragStart

GridItem的onDragStart主要也是计算位置信息,因为最后要计算出x,y的数据,需要以当前容器的x,y为准,所以这里要计算父元素的pLeft和pTop,当前鼠标的left和top减去父元素的left和top就是当前鼠标位于当前容器具体的lefttop

最后将top和left传出去。

再看下onDrag

这个相对重要,这里开始用到了react-draggable提供的位置信息。比如deltaX,deltaY,通过onDragStart计算的topleft,加上这里位置的deltaX和deltaY,可以得到当前鼠标新的left和top

然后就开始调用calcXY,计算得到当前拖拽元素新的x和y

主要是根据rowHeight(每一行的高度),加上colsWidth(每一列的宽度),得到新的left和top对应的xy数据。

最后还有一些边界处理,防止出界。然后调用props.onDrag去将新的xy传出去。

最后看下onDragStop事件

可以看到跟onDrag差不多,主要多了一个变量重置。

最后还可以看下GridItem的一些其他细节,比如css的设定

先通过calcGridItemPosition,传入当前元素的x,y,w,h得到对应的top和left。

主要看这两个,如果属于拖拽的时候,那么top和left就是当前鼠标具体的位置信息。

如果不属于拖拽时,就是通过x,y,w,h计算得到的top和left


默认使用translate,性能会好一点,但需要注意,如果子级元素有使用position: fixed的,会被当前girdItem影响。

到这里我们就知道了GridItem的主要作用

  • 1 给children加上对应属性,包装ReactDragabble组件,提供拖拽能力。
  • 2 onDragStart计算left和top,onDrag通过消费react-draggble提供的deltaX和deltaY,再根据onDragStart计算的left和top,最后计算得到新的xy,传递出去,onDragStop事件主要是变量重置。
  • 3 计算具体的位置信息,left和top,通过style赋值到对应的dom上。

再来看这个流程图,现在我们理解了
react-draggable提供底层能力
Grid-Item计算位置信息,得到新的xy

再来看看React-Grid-Layout是怎么消费GridItem提供的数据的。

React-Grid-Layout消费GridItem的数据

我们只看drag相关的事件,首先是
onDragStart

可以看到onDragStart比较简洁,主要是创建了占位符的数据,因为拖拽的时候需要占位符表示新的元素会到哪里去。这样的话占位符就会显示了。
placeholder函数主要就是来显示占位符的,通过activeDrag数据,占位符就会显示。

然后看下onDrag事件
onDrag

可以看到,这里主要是调用moveElement函数,传入新的x和y,然后moveElement会模拟当前的元素到新的xy后,发生的碰撞等,然后递归调用,直到当前拖拽元素挪到新的xy后,且不会发生碰撞,至此得到新的layout数组,该数组就是所有元素新的位置信息,最后通过compact处理一下。
compact主要是处理不留空白,有多余位置往上面挤。

moveElement函数比较复杂,这里只需要关注他的功能。

onDrag函数就会得到新的layout,然后重新渲染。最后看下onDragStop

onDragStop

onDragStop跟onDrag差不多,主要是多了重置变量,清除占位符。然后调用onLayoutMaybeChanged将layout传出去。

至此

该流程已经基本走通一遍,我们会发现,目前的react-grid-layout是不支持同层级拖拽的,比如从box1拖拽到box2,因为其底层没有处理

下一篇会讲基于当前react-grid-layout和一些灵感后改造的,适合嵌套拖拽/同层级拖拽的布局组件。

相关推荐
一斤代码6 小时前
vue3 下载图片(标签内容可转图)
前端·javascript·vue
中微子6 小时前
React Router 源码深度剖析解决面试中的深层次问题
前端·react.js
中微子6 小时前
React Router 面试指南:从基础到实战
前端·react.js·前端框架
3Katrina6 小时前
深入理解 useLayoutEffect:解决 UI "闪烁"问题的利器
前端·javascript·面试
前端_学习之路7 小时前
React--Fiber 架构
前端·react.js·架构
甜瓜看代码7 小时前
1.
react.js·node.js·angular.js
伍哥的传说7 小时前
React 实现五子棋人机对战小游戏
前端·javascript·react.js·前端框架·node.js·ecmascript·js
我在北京coding7 小时前
element el-table渲染二维对象数组
前端·javascript·vue.js
2501_915918417 小时前
Fiddler中文版全面评测:功能亮点、使用场景与中文网资源整合指南
android·ios·小程序·https·uni-app·iphone·webview