磁吸按钮
按钮会向指针方向倾斜,文字移动得更多一些;指针离开后再平稳回位。
使用 GSAP 为 React 按钮制作磁吸悬停效果。用一个约 40px 内边距的透明感应区包住按钮。指针在感应区内移动时,计算指针相对按钮中心的偏移,用 gsap.to 让按钮向指针方向移动偏移量的 35%,内部文字再额外移动 15%(duration 0.4,ease "power3.out")。指针离开时,用 0.6 秒的 "expo.out" 让两者回到 x: 0、y: 0。只对 transform 做动画。当用户开启 prefers-reduced-motion 时完全不启用此效果。按钮文字为“立即开始”。
源代码
'use client'
import gsap from 'gsap'
import s from './demo.module.css'
export default function MagneticButton({ labels }: { labels: Record<string, string> }) {
const reduced = () => window.matchMedia('(prefers-reduced-motion: reduce)').matches
const onMove = (e: React.PointerEvent<HTMLDivElement>) => {
if (reduced()) return
const button = e.currentTarget.firstElementChild as HTMLElement
const r = button.getBoundingClientRect()
const x = e.clientX - r.left - r.width / 2
const y = e.clientY - r.top - r.height / 2
gsap.to(button, { x: x * 0.35, y: y * 0.35, duration: 0.4, ease: 'power3.out' })
gsap.to(button.firstElementChild, { x: x * 0.15, y: y * 0.15, duration: 0.4, ease: 'power3.out' })
}
const onLeave = (e: React.PointerEvent<HTMLDivElement>) => {
const button = e.currentTarget.firstElementChild as HTMLElement
gsap.to([button, button.firstElementChild], { x: 0, y: 0, duration: 0.6, ease: 'expo.out' })
}
return (
<div className={s.zone} onPointerMove={onMove} onPointerLeave={onLeave}>
<button type="button" className={s.button}>
<span className={s.label}>{labels.cta}</span>
</button>
</div>
)
}
.zone {
padding: 40px;
}
.button {
display: inline-flex;
align-items: center;
height: 52px;
padding: 0 28px;
border: 0;
border-radius: 999px;
background: var(--stage-ink);
color: var(--stage-bg);
font: 500 var(--text-base) / 1 var(--font-body);
white-space: nowrap;
cursor: pointer;
will-change: transform;
}
.button:focus-visible {
outline: 2px solid var(--color-focus);
outline-offset: 3px;
}
.label {
display: inline-block;
will-change: transform;
}