因為有個拖拉拽的需求,類似于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和一些靈感后改造的,適合嵌套拖拽/同層級拖拽的布局組件。