F09▸A034▸framer-motion
Tilt 3D Card
3D 傾斜卡片
難度 ★★★★★標籤 hover · 3D · perspective3 min read
效果簡述
游標在卡片上移動時,卡片用 perspective + rotateX/rotateY 做 3D 傾斜。
產品卡、人物卡都好用。一定要 perspective 在父層才有立體感。
移動滑鼠看 3D 傾斜
±12°
Claude Code Prompt
參考:A034 tilt 3D card
技術:framer-motion useMotionValue + useTransform + useSpring
具體行為:
- 父層 style={ perspective: 800 }
- 卡片用 motion.div,rotateX/rotateY 從 useMotionValue
- onMouseMove: 算出 normalized x/y in [-0.5, 0.5]
- useTransform: rotateY = x * 14(度數);rotateX = -y * 14
- useSpring 包平滑:stiffness 150 damping 12
- onMouseLeave: 歸零
限制:
- 角度 ±10° ~ ±15° 最自然,超過就鬼
- 父層 perspective 不能省,不然只是 2D skew
- 手機無效(建議降級成靜態)完整程式碼
'use client'
import { motion, useMotionValue, useSpring, useTransform } from 'framer-motion'
export function Tilt3DCard() {
const x = useMotionValue(0)
const y = useMotionValue(0)
const sx = useSpring(x, { stiffness: 150, damping: 12 })
const sy = useSpring(y, { stiffness: 150, damping: 12 })
const rotateY = useTransform(sx, [-0.5, 0.5], [-14, 14])
const rotateX = useTransform(sy, [-0.5, 0.5], [14, -14])
function onMove(e: React.MouseEvent<HTMLDivElement>) {
const r = e.currentTarget.getBoundingClientRect()
x.set((e.clientX - r.left) / r.width - 0.5)
y.set((e.clientY - r.top) / r.height - 0.5)
}
function onLeave() {
x.set(0)
y.set(0)
}
return (
<div style={{ perspective: 800 }}>
<motion.div
onMouseMove={onMove}
onMouseLeave={onLeave}
style={{ rotateX, rotateY, transformStyle: 'preserve-3d' }}
className="h-48 w-64 rounded-xl border bg-card p-6"
>
Tilt me
</motion.div>
</div>
)
}何時用
- ✅ 商品 / 案例卡(hover 預覽)
- ✅ 團隊成員卡
- ✅ 主視覺裡的單一焦點卡
何時別用
- ❌ 一整面 grid 卡片(會像地震)
- ❌ 內含 form 的卡片(互動衝突)
- ❌ 主要受眾用手機
我踩過的坑
-
沒 perspective 看起來是 2D skew:父層必須
perspective: 800左右,子層才有透視感。 -
rotateX 方向要反:游標往下時 → 卡片頂端往後仰 → rotateX 要負。 直接套
+y會反向。 -
沒 spring 抖到爆:直接綁 motionValue 卡頓感很強。spring stiffness 150 / damping 12 是手感最舒服的範圍。
相關效果
- F07 Hover Lift — 平面版 hover
- F08 Magnetic Button — 一樣靠游標 + spring