const cursor = document.getElementById('cursor');
const ring = document.getElementById('cursorRing');
let mx = 0, my = 0, rx = 0, ry = 0;
// 实时记录光标位置
document.addEventListener('mousemove', e => { mx = e.clientX; my = e.clientY; });
// 每次 frame:实心点紧跟光标,环延迟跟随
function tick() {
rx += (mx - rx) * 0.15; // 环慢一拍
ry += (my - ry) * 0.15;
anime({ targets: cursor, left: mx, top: my, duration: 0 });
anime({ targets: ring, left: rx, top: ry, duration: 80, easing: 'linear' });
requestAnimationFrame(tick);
}
tick();
// 点击时给环一个脉冲动画
document.addEventListener('click', () => {
anime({ targets: ring, scale: [1, 1.8, 1],
duration: 300, easing: 'easeOutCubic' });
});
核心原理
`rx += (mx - rx) * 0.15` 是经典的"插值跟随"技巧——环每次只走当前位置到目标位置的 15%,形成延迟感;实心点每帧直接定位,无延迟。两层叠加就有了"快核心 + 慢拖尾"的层次感。点击时用 `scale: [1, 1.8, 1]` 做脉冲环。
💡 移动端无 `mousemove`,可改用 `touchmove`,或者检测到 touch 设备时直接隐藏自定义光标、还原原生光标,避免体验割裂。
requestAnimationFrame
插值跟随
linear easing
scale pulse
touch fallback