Frontend Lab
← 回到所有效果
G12L101gsap

Flip Layout Animation

Flip 排版變換

難度 ★★★★標籤 flip · layout · FLIP3 min read

效果簡述

從 grid 變 list、或位置互換時,元素「平滑滑過去」而不是瞬移。 GSAP 的 Flip plugin 做的就是經典的 FLIP(First/Last/Invert/Play) 技術。Framer 的 layout 動畫 + AnimatePresence 是同樣思路的另一實作。

A
B
C
D
E
F
預設|grid ↔ list 切換

Claude Code Prompt

💬 Claude Code Prompt
參考:L101 flip layout
技術:gsap Flip plugin

具體行為:
- gsap.registerPlugin(Flip)
- 先拍:const state = Flip.getState(items)
- 改 DOM / state(grid → list)
- 下一幀:Flip.from(state, { duration: 0.6, ease: 'power2.inOut' })

限制:
- React 要在 setState 後等 commit,用 requestAnimationFrame 包
- Flip plugin 是免費的(不像 SplitText)
- 元素數量大於 30 開始卡,要分批

完整程式碼

FlipLayout.tsx
'use client'

import gsap from 'gsap'
import { Flip } from 'gsap/Flip'
import { useEffect, useRef, useState } from 'react'

export function FlipLayout() {
const root = useRef<HTMLDivElement>(null)
const [grid, setGrid] = useState(true)

useEffect(() => {
  gsap.registerPlugin(Flip)
}, [])

function toggle() {
  const items = root.current?.querySelectorAll('.item')
  if (!items) return
  const state = Flip.getState(items)
  setGrid((g) => !g)
  requestAnimationFrame(() => {
    Flip.from(state, { duration: 0.6, ease: 'power2.inOut' })
  })
}

return (
  <>
    <div ref={root} className={grid ? 'grid grid-cols-3 gap-3' : 'flex flex-col gap-2'}>
      {['A','B','C','D','E','F'].map((x) => (
        <div key={x} className="item bg-orange-500/20 p-4">{x}</div>
      ))}
    </div>
    <button onClick={toggle}>切換</button>
  </>
)
}

何時用

  • ✅ Grid / List 切換
  • ✅ 排序變化(拖曳重排)
  • ✅ 篩選後位置重排

何時別用

  • ❌ 元素 > 30(會卡)
  • ❌ 內含 form / iframe(state 抓不到)
  • ❌ 互動需要立即回饋(Flip 600ms 期間別人 hover 會錯位)

我踩過的坑

  1. 沒 requestAnimationFrame setState 前後位置一樣:React state 還沒 commit,DOM 沒變,Flip.from 對比不到差異。用 requestAnimationFrame 等下一幀。

  2. transition: all 干擾 Flip:CSS 本身也有過渡時,會跟 Flip 打架。 Flip 動畫的目標屬性別在 CSS 寫 transition。

  3. Framer layout 是同一概念:如果整站已經用 Framer,不必引 GSAP, <motion.div layout> 達到 80% 同效果。

相關效果

搜尋

按 ⌘K 隨時開啟