组件
鼠标跟随磁吸 Magnetic
让按钮与图标在光标悬停时产生朝向指针的物理吸附微位移,并在光标移出后通过弹簧平滑回正。
基础用法
最简单的磁吸用法。将交互按钮包裹在 Magnetic 容器内即可拥有物理磁力吸附交互:
Loading…
安装与引入
通过 CLI 自动添加组件,或手动复制源码至项目中:
pnpm dlx @wui-design/cli@latest add @wui/magnetic安装动效依赖与工具库
pnpm add motion clsx tailwind-merge复制组件源码到
components/ui/magnetic.tsx"use client"
import * as React from "react"
import {
motion,
useMotionValue,
useReducedMotion,
useSpring,
type HTMLMotionProps,
type SpringOptions,
} from "motion/react"
import { cn } from "@/lib/utils"
export interface MagneticProps extends HTMLMotionProps<"div"> {
/** How strongly the content follows the pointer. @default 0.18 */
strength?: number
/** Maximum movement on either axis, in pixels. @default 12 */
maxDistance?: number
/** Spring physics used for movement and return. */
springConfig?: SpringOptions
/** Disable pointer tracking without changing the layout. @default false */
disabled?: boolean
// Narrows motion's children type (which allows MotionValue) back to ReactNode.
children?: React.ReactNode
}
/** Moves its content subtly toward the pointer, then springs back to rest. */
function Magnetic({
strength = 0.18,
maxDistance = 12,
springConfig = { stiffness: 260, damping: 22, mass: 0.35 },
disabled = false,
className,
style,
children,
onPointerMove,
onPointerLeave,
onPointerCancel,
...props
}: MagneticProps) {
const reduceMotion = useReducedMotion()
const rawX = useMotionValue(0)
const rawY = useMotionValue(0)
const x = useSpring(rawX, springConfig)
const y = useSpring(rawY, springConfig)
React.useEffect(() => {
if (disabled || reduceMotion) {
rawX.set(0)
rawY.set(0)
}
}, [disabled, rawX, rawY, reduceMotion])
const reset = () => {
rawX.set(0)
rawY.set(0)
}
const handlePointerMove = (event: React.PointerEvent<HTMLDivElement>) => {
onPointerMove?.(event)
if (
event.defaultPrevented ||
disabled ||
reduceMotion ||
event.pointerType === "touch"
) {
return
}
const rect = event.currentTarget.getBoundingClientRect()
const offsetX = (event.clientX - (rect.left + rect.width / 2)) * strength
const offsetY = (event.clientY - (rect.top + rect.height / 2)) * strength
const limit = Math.max(0, maxDistance)
rawX.set(Math.max(-limit, Math.min(limit, offsetX)))
rawY.set(Math.max(-limit, Math.min(limit, offsetY)))
}
const handlePointerLeave = (event: React.PointerEvent<HTMLDivElement>) => {
onPointerLeave?.(event)
reset()
}
const handlePointerCancel = (event: React.PointerEvent<HTMLDivElement>) => {
onPointerCancel?.(event)
reset()
}
return (
<motion.div
data-slot="magnetic"
{...props}
className={cn("inline-flex", className)}
style={{
...style,
x: disabled || reduceMotion ? 0 : x,
y: disabled || reduceMotion ? 0 : y,
}}
onPointerMove={handlePointerMove}
onPointerLeave={handlePointerLeave}
onPointerCancel={handlePointerCancel}
>
{children}
</motion.div>
)
}
export { Magnetic }
属性 Props
Magnetic 接受以下物理吸附参数配置,并继承 motion.div 的全部 HTML 属性:
| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
| children | React.ReactNode | — | 受磁吸影响的子元素(如按钮、图标、浮动徽标等)。 |
| strength | number | 0.18 | 磁吸跟随强度系数(0~1)。数值越大吸附越灵敏,推荐在 0.15~0.3 之间。 |
| maxDistance | number | 12 | X / Y 轴方向上的最大允许偏移距离(像素 px)。 |
| springConfig | SpringOptions | { stiffness: 260, damping: 22, mass: 0.35 } | Motion 物理弹簧参数配置对象(刚度、阻尼与质量)。 |
| disabled | boolean | false | 是否禁用磁吸效果。禁用时元素归零静止且不响应指针位移。 |
| className | string | — | 应用于磁吸外层容器的额外 CSS 类名(默认包含 inline-flex)。 |
事件 Events
Magnetic 透传底层的指针跟踪事件:
| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
| onPointerMove | (event: React.PointerEvent<HTMLDivElement>) => void | — | 指针在元素内移动时触发,内部通过 MotionValue 计算坐标偏移并驱动 Spring 弹簧。 |
| onPointerLeave | (event: React.PointerEvent<HTMLDivElement>) => void | — | 指针离开元素边界时触发,内部自动平滑回弹归零。 |
| onPointerCancel | (event: React.PointerEvent<HTMLDivElement>) => void | — | 指针事件被系统中断时触发,自动回弹归零。 |
使用场景与设计规范
Magnetic 能够赋予静态按钮生动的物理反馈,极大地提升用户的点击意愿。
- 使用场景:
- 社交媒体与快捷操作浮动栏(Dock):导航坞、工具条中的图标吸附反馈。
- 首屏主操作 CTA(Hero CTA):官网头部的核心操作按钮。
- 创意交互与展示项:作品展示、浮动联系小球(Floating Action Button)。
- 设计规范与克制原则:
- 位移幅度控制:
maxDistance建议设置在8px~16px以内。过大的位移会造成光标“追逐”按钮的糟糕体验,导致用户难以精准点击。 - 移动端与触控:触控设备(Touch)由于没有持续光标位置,组件内部会自动忽略位移计算,防止页面滚动卡顿。
- 位移幅度控制:
- 与 Button 组合:
- 内部子组件通常搭配
Button或可点击的链接,磁吸容器不会影响原生点击事件触发。
- 内部子组件通常搭配
场景示例
浮动社交操作栏(Dock)
在悬浮操作栏中将每个图标单独赋予微磁吸动效:
Loading…
首屏核心行动点(Hero CTA)
在官网顶部主视觉区域为操作按钮配置磁吸反馈:
Loading…
无障碍与交互 Accessibility
- 减少动态效果降级:内置
useReducedMotion监听。当用户系统设置了“减少动态效果(prefers-reduced-motion)”时,磁吸位移自动清零并停止监听,保证元素处于静止状态。 - 键盘导航兼容:磁吸包装层不会干扰子元素的 Tab 焦点流与屏幕阅读器朗读,键盘用户可以正常聚焦与激活。
- 触摸屏兼容:触摸屏上自动静止,避免干扰手势上下滑动。