# 磁吸 Magnetic > 鼠标靠近按钮,按钮往鼠标那边挪一点,离开后弹回原位。 分类:动效 · 角色:点缀(贴在某个元素上,一页两三个) [全部组件和流程](https://www.dachuiai.com/llms.txt) · [动效选型规则](https://www.dachuiai.com/guides/motion.md) ## 给 AI:用户把这个组件发给了你 1. 先看用户的项目:框架、页面类型、现在的风格、手机还是电脑为主。 2. 对照下面的「适合 / 不适合」判断合不合适。不合适就直接说,并从[动效选型规则](https://www.dachuiai.com/guides/motion.md)或[全部组件](https://www.dachuiai.com/llms.txt)里另挑一个。 3. 合适就按「装进项目」装好,示例里的文字和图片换成用户自己的。 4. 告诉用户用了什么、放在哪个文件、以后想改快慢、颜色、大小改哪个参数。 ## 什么时候用 - 适合:首屏最重要的那一个按钮:立即开始、联系我、预约。 - 不适合:表单提交按钮、后台按钮;一排很多按钮都加(会乱)。 - 适合的网站:官网、作品集、SaaS、活动页 - 放在:按钮 - 手机上:没效果:手机上按钮照常能点。 - 性能代价:低:只改 transform。 - 常搭配:[光标跟随 Custom Cursor](https://www.dachuiai.com/c/custom-cursor.md)、[流体背景 Fluid Background](https://www.dachuiai.com/c/fluid-background.md)、[粒子聚合 Particle Morph](https://www.dachuiai.com/c/particle-morph.md) ## 装进项目 - 写法:React 18 以上 + TypeScript + Tailwind CSS v4,一个文件就能用,不引用别的组件。 - 依赖:`npm i lucide-react`(lucide-react 只有最后的演示示例用到;不留示例的话,删掉对应的 import 就不用装) - 文件:原样存成 `magnetic.tsx`(比如 `src/components/magnetic.tsx`)。能跑命令就下载[原文](https://www.dachuiai.com/code/magnetic.tsx),比手抄准:`curl -fsSL -o src/components/magnetic.tsx https://www.dachuiai.com/code/magnetic.tsx`(Windows 写 `curl.exe`)。下载完看一眼第一行是 `import`,不是网页。 - 导出:`Magnetic` 是可以直接用的部分;最后的 `export default` 是演示示例,只作参考。 - 图片:示例用了本站的 [/art/silk-violet.jpg](https://www.dachuiai.com/art/silk-violet.jpg)。换成用户自己的图;只想先看效果,可以从本站同样的路径下载,放进项目的 public 目录。 - 注意:直接包住现有按钮就行:感应区不占位置,不会撑开布局。外层是 inline-block,间距、对齐写在 Magnetic 的 className 里。 - 保留代码里 `prefers-reduced-motion` 的处理:用户开了「减弱动态效果」时内容照样完整显示。 - Next.js(有 app 文件夹):文件第一行加 `'use client';`。 - 用 JavaScript 不用 TypeScript:Vite 和 Next.js 都能直接用 .tsx 文件;也可以删掉类型标注,改名 .jsx。 - 没有 Tailwind:先装 Tailwind CSS v4(Vite 项目 `npm i tailwindcss @tailwindcss/vite`,vite 配置的 plugins 里加 `tailwindcss()`,入口引入的那个 CSS 文件(一般是 `src/index.css`)第一行写 `@import "tailwindcss";`),或者把类名改写成项目用的样式。 - 已经装了 Tailwind v3:别为了一个组件升级。类名大多通用,少数 v4 写法要换,比如焦点框 `outline-2` 在 v3 里要写成 `outline outline-2`。 - 不是 React(Vue、Svelte、小程序、纯 HTML):照着代码的做法改写,时长、曲线、参数保持一样。Wix、有赞这类建站平台装不进代码,告诉用户去平台自带的动效设置里找。 ## 参数 | 参数 | 默认值 | 作用 | | --- | --- | --- | | `children` | | 包在里面的内容 | | `range` | `140` | 多远开始吸(px) | | `strength` | `0.35` | 吸过去的比例 | | `className` | `''` | 加在最外层的类名,用来调大小、位置、间距 | ## 你可以说 > 首页的「立即开始」按钮加 Magnetic 磁吸效果:鼠标靠近时按钮往鼠标方向挪一点,离开后弹回原位。 ## 完整代码 ```tsx import {useEffect, useRef, useState, type ReactNode} from 'react'; import {ArrowRight} from 'lucide-react'; /* --------------------------------------------------------------- * Magnetic 磁吸 * 鼠标靠近按钮,按钮像被吸住一样往鼠标那边挪一点; * 里面的文字挪得更多一点,有层次感。鼠标离开后弹回原位。 * range:多远开始吸(px);strength:吸过去的比例。 * 感应区不占位置:听整页的鼠标,进了按钮四周 range 以内才吸, * 包住现有按钮不会把页面撑开。外层是 inline-block,className 加在外层。 * ------------------------------------------------------------- */ export function Magnetic({children, range = 140, strength = 0.35, className = ''}: {children: ReactNode; range?: number; strength?: number; className?: string}) { const area = useRef(null); // 不动的外层:按它算感应区 const ref = useRef(null); const [p, setP] = useState({x: 0, y: 0, k: 0}); useEffect(() => { if (matchMedia('(prefers-reduced-motion: reduce)').matches) return; let near = false; const reset = () => { if (near) { near = false; setP({x: 0, y: 0, k: 0}); } }; const move = (e: PointerEvent) => { if (e.pointerType === 'touch') return; // 手指滑动页面时不跟着跑 const a = area.current!.getBoundingClientRect(); if (e.clientX < a.left - range || e.clientX > a.right + range || e.clientY < a.top - range || e.clientY > a.bottom + range) return reset(); near = true; const r = ref.current!.getBoundingClientRect(); const dx = e.clientX - (r.left + r.width / 2), dy = e.clientY - (r.top + r.height / 2); const k = Math.max(0, 1 - Math.hypot(dx, dy) / (range + r.width / 2)); setP({x: dx * strength * Math.min(1, k * 2), y: dy * strength * Math.min(1, k * 2), k}); }; const page = document.documentElement; addEventListener('pointermove', move, {passive: true}); page.addEventListener('pointerleave', reset); return () => { removeEventListener('pointermove', move); page.removeEventListener('pointerleave', reset); }; }, [range, strength]); return (
{children}
); } /* ---------------- 示例:首页最重要的那个按钮 ---------------- */ export default function MagneticDemo() { return (

用 AI 做你的第一个产品

不用写代码,说清楚就行

e.preventDefault()} className="group relative flex h-[72px] items-center gap-3 rounded-full bg-white px-12 text-xl font-black text-[#0B0B14] transition-shadow duration-300 focus-visible:outline-2 focus-visible:outline-offset-4 focus-visible:outline-white" style={{boxShadow: '0 calc(14px + 16px * var(--mag-k)) calc(40px + 50px * var(--mag-k)) rgba(150,110,255,calc(.3 + .5 * var(--mag-k)))'}} > 立即开始
); } ```