今天要講另一個很常用的 motion Component ─ <Reorder>
,專門用來操作元件排序動畫,以後在做 todolist 除了新增予刪除,還可以加上拖曳 :D
昨天超陽春的頁面轉場,會跳兩次黑幕其實就是因為各頁的 animate
元件 render 一次,拿到資料又 re-render 一次,修改的方式就是當拿到元件也跟著進行刷黑幕,在這之前補一個 loading 過渡就行了。
Reorder
包含兩個元件 :
<Reorder.Group>
: 預設是 <ul>
元素<Reorder.Item>
: 預設是 <li>
元素沒錯就是專門給列表使用的,適用於拖曳的操作,幫我們 做列表的排列與拖曳排列時的動畫,解決以往在原生排列時的各種繁瑣的計算。 主要是 <Reorder.Item>
元件具有 layout
props 的屬性, layout
幫助版面上重排 (reflow) 的動畫卡頓問題,可以看 原始碼結構,Reorder 幫我們做了什麼 :
<Reorder.Item>
主要<Component
// 拖曳方向
drag={axis}
{...props}
dragSnapToOrigin
style={{ ...style, x: point.x, y: point.y, zIndex }}
// 解決卡頓問題
layout={layout}
// 拖曳判斷,判斷對節點的前後最更新動畫
onDrag={(event, gesturePoint) => {
const { velocity } = gesturePoint
velocity[axis] &&
updateOrder(value, point[axis].get(), velocity[axis])
onDrag && onDrag(event, gesturePoint)
}}
// 這個我查了一下會測量跟其他元素的距離,還有有關滾輪捲軸的距離判斷
onLayoutMeasure={(measured) => {
measuredLayout.current = measured
}}
ref={externalRef}
>
{children}
</Component>
最後拖曳事件完成會進行判斷 決定交換與否 ,由此可知幫我們省掉不少哩哩叩叩的設置。
另外我好奇陣列元素交換,發現是用 splice
,因為我最知道 解構賦值交換的方式 偷吃步
[item[1],item[3]] = [item[3],item[1]]
任何要列表項目要排序只要用 <Reorder.Group>
包起來,並且 <Reorder.Item>
作為拖曳的目標 :
// 結構
<Reorder.Group>
{items.map(item => (
<Reorder.Item>
{item}
</Reorder.Item>
))}
</Reorder.Group>
兩個都有各自的 props 負責 :
as
: 不只是 <ul>
,也可以是其他 HTML 標籤,但是目前不能是自製元件,但有未來有計畫加入。values
(必要) : 要排序的 陣列 資料,搭配 React 的資料流 stateonReorder
(必要) : 更新陣列的方法 ,使用 setState
axis
: 子元素拖曳的方向性, x 與 y 則一,如果想要子元素可以兩個方向拖曳,可以修改 drag
prop 的值 drag={true}
。as
: 跟 Group 一樣,不只是預設的 <li>
,其他 HTML 標籤都可以value
(必要) : 資料import { Reorder } from "framer-motion"
function List() {
const [items, setItems] = useState([0, 1, 2, 3])
return (
<Reorder.Group values={items} onReorder={setItems}>
{items.map(item => (
<Reorder.Item key={item} value={item}>
{item}
</Reorder.Item>
))}
</Reorder.Group>
)
}
要注意的是 state 一開始的資料如果沒有就放上空陣列 ,如果是放 null
會導致 <Reorder.Group>
報錯。
並不一定都是在 <Reorder.Item>
元素本身才能進行拖曳,有時候會希望聚焦在一個區塊可以進行拖曳 ,這時候 拖曳的功能就要轉移到特定元件身上,並且等到元件進行點擊後 (onPointerDown
) 開啟拖曳事件。
// 引入 useDragControls
import { Reorder, useDragControls } from "framer-motion"
// 使用 useDragControls Hooks
const controls = useDragControls()
useDragControls
做的事就是通知 Reorder.Item
「請開始你的表演拖曳移動」
使用上只要在要拖曳的主元件,例 : 在列表排序操作就是指 <Reorder.Item>
,引入 Hooks 並且把方法傳遞給要控制的元件
import { Reorder, useDragControls } from "framer-motion"
function Item({ value }) {
const controls = useDragControls()
return (
<Reorder.Item
value={value}
dragListener={false}
// dragControls 把控制交出來
dragControls={controls}
<div
className="reorder-handle"
{/* 通常是點下去之後進行拖曳的判斷 */}
onPointerDown={(e) => controls.start(e)}
/>
</Reorder.Item>
)
}
這個屬性不一定用在 Reorder 元件,任何拖曳目標都可以。
<Reorder.Group>
: 界定陣列資料,並且通知 React 更新資料。<Reorder.Item>
: 讓資料可以拖曳。useDragControls
: 把拖曳的開始交給拖曳元件的子元件控制。基本的元件與 props 使用到這邊就告一段落了,到目前為止都是區塊級的元素操作,motion 元件也是可以使用在 SVG ,在 Hooks 篇開始前,下一篇決定來試試看 SVG :D