iT邦幫忙

2022 iThome 鐵人賽

DAY 11
0
自我挑戰組

向網頁施點魔法粉 framer-motion 系列 第 11

#11 Line up ! Reorder Component

  • 分享至 

  • xImage
  •  

今天要講另一個很常用的 motion Component ─ <Reorder>,專門用來操作元件排序動畫,以後在做 todolist 除了新增予刪除,還可以加上拖曳 :D

昨天超陽春的頁面轉場,會跳兩次黑幕其實就是因為各頁的 animate 元件 render 一次,拿到資料又 re-render 一次,修改的方式就是當拿到元件也跟著進行刷黑幕,在這之前補一個 loading 過渡就行了。

目錄

  1. 站好排隊 ! Reorder 元件
  2. 乾坤大挪移 : Reorder 的用法
  3. 交給你了 ! useDragControls

站好排隊 ! Reorder 元件

Reorder 包含兩個元件 :

  1. <Reorder.Group> : 預設是 <ul> 元素
  2. <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 的用法

任何要列表項目要排序只要用 <Reorder.Group> 包起來,並且 <Reorder.Item> 作為拖曳的目標 :

// 結構
<Reorder.Group>
  {items.map(item => (
    <Reorder.Item>
      {item}
    </Reorder.Item>
  ))}
</Reorder.Group>

兩個都有各自的 props 負責 :

  • Group :
    1. as : 不只是 <ul>,也可以是其他 HTML 標籤,但是目前不能是自製元件,但有未來有計畫加入。
    2. values (必要) : 要排序的 陣列 資料,搭配 React 的資料流 state
    3. onReorder (必要) : 更新陣列的方法 ,使用 setState
    4. axis : 子元素拖曳的方向性, x 與 y 則一,如果想要子元素可以兩個方向拖曳,可以修改 drag prop 的值 drag={true}
  • Item : 除了以下的 props 也可以接收所有 motion 元件的 props
    1. as : 跟 Group 一樣,不只是預設的 <li> ,其他 HTML 標籤都可以
    2. 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> 報錯。

交給你了 ! useDragControls

並不一定都是在 <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


上一篇
#10 Ok,Bye... AnimatePresence Page Transition
下一篇
#12 Point and Line to Plane - SVG Animation
系列文
向網頁施點魔法粉 framer-motion 15
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言