F08▸A033▸framer-motion
Magnetic Button
磁吸按鈕
難度 ★★★★★標籤 hover · 磁吸 · useSpring3 min read
效果簡述
游標靠近按鈕時,按鈕「被吸過去」一點。是 Awwwards 系作品常見的招。
核心是 mouseX - centerX 乘 0.2~0.3 的位移 + spring 平滑。
Claude Code Prompt
參考:A033 magnetic button 技術:framer-motion useMotionValue + useSpring 具體行為: - 包一層 wrapper(hit area 比按鈕大一點,padding 加大) - onMouseMove: const rect = e.currentTarget.getBoundingClientRect() - x.set((e.clientX - (rect.left + rect.width/2)) * 0.3) - y.set((e.clientY - (rect.top + rect.height/2)) * 0.3) - onMouseLeave: x.set(0); y.set(0) - 用 useSpring 包 x/y → stiffness 200 damping 18 限制: - 倍率 0.2 ~ 0.4 之間(再多就鬼魅) - 手機沒效果(記得加 @media (hover: hover)) - 一頁不要超過 3 個(會搶焦點)
完整程式碼
'use client'
import { motion, useMotionValue, useSpring } from 'framer-motion'
export function MagneticButton({ children = 'Hire me' }: { children?: React.ReactNode }) {
const x = useMotionValue(0)
const y = useMotionValue(0)
const sx = useSpring(x, { stiffness: 200, damping: 18 })
const sy = useSpring(y, { stiffness: 200, damping: 18 })
function onMove(e: React.MouseEvent<HTMLDivElement>) {
const r = e.currentTarget.getBoundingClientRect()
x.set((e.clientX - (r.left + r.width / 2)) * 0.3)
y.set((e.clientY - (r.top + r.height / 2)) * 0.3)
}
function onLeave() {
x.set(0)
y.set(0)
}
return (
<div onMouseMove={onMove} onMouseLeave={onLeave} className="p-8">
<motion.button
style={{ x: sx, y: sy }}
className="rounded-full bg-foreground px-6 py-3 font-medium text-background"
>
{children}
</motion.button>
</div>
)
}何時用
- ✅ Hero CTA(要醒目又有質感)
- ✅ 作品集 contact 按鈕
- ✅ 重點轉換點(訂閱、下載)
何時別用
- ❌ 一般表單按鈕(會分心)
- ❌ Nav menu items(hover 一堆東西在跳)
- ❌ 手機為主的站(沒 hover 完全失效)
我踩過的坑
-
wrapper hit area 太小:onMouseMove 綁在按鈕本身的話,按鈕一被 吸走就脫離了 hit area,會抖。一定要包外層更大的 div。
-
沒用 useSpring:直接綁 x/y 是「跟著游標瞬移」,沒磁吸感。 spring 才有「被吸過去再彈回」的物理感。
-
手機沒處理:mobile 上 mousemove 不會觸發,但 layout 還在。 要包
@media (hover: hover)或檢查matchMedia('(pointer: fine)')。
相關效果
- F07 Hover Lift — 比較收斂的 hover
- F09 Tilt 3D Card — 一樣靠游標 + spring