因为有个拖拉拽的需求,类似于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就是当前鼠标位于当前容器具体的left
和top
最后将top和left传出去。
再看下onDrag
这个相对重要,这里开始用到了react-draggable提供的位置信息。比如deltaX,deltaY
,通过onDragStart
计算的top
和left
,加上这里位置的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和一些灵感后改造的,适合嵌套拖拽/同层级拖拽的布局组件。