磁场按钮(rAF 插值磁吸跟随按钮动效源码)
磁场按钮源码:按钮被光标「磁吸」跟随,距离越近吸力越强,rAF 逐帧插值产生顺滑磁性拖拽感,附带感应光环可视化磁场范围,原生 JS 零依赖。
效果演示(真实可交互)
把鼠标移入演示区的虚线舞台,一个浅蓝色光环会跟随光标出现并放大,这就是「磁场」的可视化范围。三枚按钮(主色、描边、深色)会朝光标方向平滑移动:距离近的按钮被吸得明显,距离远的几乎不动,模拟真实磁力随距离衰减的规律。移出舞台后按钮带着惯性缓缓回弹归位,按钮内的文字还会反向漂移 35%,产生立体景深。与直接把位移写进 transform 的简易版不同,本组件用 requestAnimationFrame 每帧做插值逼近,位移曲线连续顺滑,观感接近物理弹簧。光环只做视觉提示,不拦截事件,指针穿过它不会有任何卡顿。按钮的位移是纯视觉反馈,实际点击命中区域仍是原位,因此磁吸效果不会影响表单提交或路由跳转的可靠性。多按钮之间吸力独立计算,互不干扰。磁吸适合用于主 CTA、导航入口这类需要被「注意到」的元素;配合文字反向漂移,按钮会显得有厚度和深度,比静态高亮更有记忆点,适合营销落地页的首屏引导。
鼠标进入区域后按钮被平滑「吸」向光标,移出后惯性回弹
完整代码
HTML:舞台容器包着光环与三枚按钮,按钮内部文字单独包一层用于反向漂移:
<div class="bm-field" id="bmField">
<div class="bm-ring" id="bmRing"></div>
<button class="bm-btn bm-primary"><span class="bm-text">磁场按钮</span></button>
<button class="bm-btn bm-outline"><span class="bm-text">描边磁场</span></button>
<button class="bm-btn bm-dark"><span class="bm-text">深色磁场</span></button>
</div>
CSS:光环默认缩小隐藏,指针进入后弹性放大浮现:
.bm-ring {
position: absolute; width: 220px; height: 220px; border-radius: 50%;
border: 2px dashed rgba(108,140,255,.5);
transform: translate(-50%, -50%) scale(.4); opacity: 0;
transition: transform .5s cubic-bezier(.34,1.2,.64,1), opacity .3s ease;
}
.bm-field.bm-on .bm-ring { opacity: 1; transform: translate(-50%, -50%) scale(1); }
.bm-btn { transition: box-shadow .25s ease; will-change: transform; }
原生 JS 核心:每个按钮维护「目标坐标」与「当前坐标」,rAF 每帧按 18% 逼近,形成磁性拖拽感:
const field = document.getElementById('bmField');
const ring = document.getElementById('bmRing');
const buttons = [...field.querySelectorAll('.bm-btn')];
const textEls = buttons.map((b) => b.querySelector('.bm-text'));
const state = buttons.map(() => ({ tx: 0, ty: 0, cx: 0, cy: 0 }));
let raf = null;
function tick() {
let settled = true;
buttons.forEach((btn, i) => {
const s = state[i];
s.cx += (s.tx - s.cx) * 0.18; // 每帧向目标逼近 18%
s.cy += (s.ty - s.cy) * 0.18;
if (Math.abs(s.tx - s.cx) > 0.1 || Math.abs(s.ty - s.cy) > 0.1) settled = false;
btn.style.transform = `translate(${s.cx.toFixed(2)}px, ${s.cy.toFixed(2)}px)`;
textEls[i].style.transform = `translate(${(s.cx * 0.35).toFixed(2)}px, ${(s.cy * 0.35).toFixed(2)}px)`;
});
raf = settled ? null : requestAnimationFrame(tick); // 稳定后自动停帧省电
}
function onMove(e) {
ring.style.left = e.clientX - field.getBoundingClientRect().left + 'px';
ring.style.top = e.clientY - field.getBoundingClientRect().top + 'px';
buttons.forEach((btn, i) => {
const rect = btn.getBoundingClientRect();
const dx = e.clientX - (rect.left + rect.width / 2);
const dy = e.clientY - (rect.top + rect.height / 2);
const dist = Math.hypot(dx, dy);
const pull = Math.max(0, 1 - dist / 220) * 0.5; // 距离越近吸力越强
state[i].tx = dx * pull;
state[i].ty = dy * pull;
});
if (!raf) raf = requestAnimationFrame(tick);
}
field.addEventListener('pointermove', onMove);
field.addEventListener('pointerleave', () => {
state.forEach((s) => { s.tx = 0; s.ty = 0; }); // 归零目标,自然回弹
if (!raf) raf = requestAnimationFrame(tick);
});
参数说明
| 参数 | 默认值 | 说明 |
|---|---|---|
| 磁场半径 | dist / 220 | 220px 内才有吸力,调大范围更广 |
| 吸力系数 | pull * 0.5 | 位移比例上限 50%,调大按钮「飞」得更明显 |
| 插值速率 | * 0.18 | 每帧逼近比例,越小越粘滞、越大越跟手 |
| 文字反向系数 | * 0.35 | 文字与按钮反向漂移的景深幅度 |
| 光环尺寸 | 220px | 应与磁场半径一致,提示用户有效范围 |
兼容性与性能
- 原生 JS 零依赖,
pointermove+requestAnimationFrame组合,Chrome / Firefox / Safari / Edge 全支持 - 动画只操作
transform(GPU 合成),每帧仅更新少量按钮的字符串样式,性能开销极小 - 自动停帧机制:按钮稳定后
raf置空,指针不动时不消耗任何 CPU - 自动停帧同时保证指针静止时零开销;若页面中放置多个磁场区域,各自独立的 rAF 循环互不冲突,也可统一收敛到一个循环里统一调度,便于批量控制开关
- 触屏无 hover 无 mousemove,磁吸默认不触发;光环同样不可见,页面交互不受影响
常见问题
Q: 按钮被「吸」得太远脱离按钮语义? 磁吸位移只是视觉反馈,按钮实际占位未变,可点击区域仍是原位置;如担心误触,把 pull 上限从 0.5 降到 0.25。
Q: 多按钮会互相遮挡吗? 按钮默认不重叠;若间距太近,靠近光标时会互相挤压,建议保持 20px 以上间距或按需只磁吸主 CTA。
Q: 为什么我的版本没有惯性回弹? 惯性来自插值逼近:回弹时目标归零、当前坐标逐帧追赶,直接赋 translate(0,0) 就没有过渡。保留 tick 循环即可。
Q: 可以商用吗? 可以。本组件为粤喵自研原创代码,免费商用无需署名。
许可与来源
- 许可证:自研原创 —— 粤喵自研原创,可免费用于个人与商业项目,无需署名。查看来源公示与许可清单
- 来源:粤喵团队原创自研(从效果思路到代码实现均为独立完成,未直接复制第三方代码)
- 首次发布:2026-09-01 · 最近更新:2026-09-01
- 修改日志:2026-09-01 首次发布;2026-09-13 补充许可证与来源标注、复制按钮说明与法务页入口
- 纠错:联系页面 / min@cactus.asia,2 个工作日内核实处理
常见问题
这个组件可以商用吗?
可以。本组件为粤喵自研原创,可免费用于个人和商业项目,无需署名。
浏览器兼容性如何?
支持所有现代浏览器(Chrome / Firefox / Safari / Edge 最新版本)。旧版 IE 不支持 CSS 动画相关特性。
怎么修改动画参数?
直接调整 CSS 中的 animation-duration(时长)、animation-timing-function(缓动曲线)等属性即可,代码中有注释说明。