不能直接用 cursor: url() 实现动态光标,因其仅支持静态图片,无法响应鼠标实时坐标、缩放、滚动或定位变化;需隐藏原生光标并用 fixed 定位 div + js 实时更新 clientx/clienty 坐标模拟。

为什么不能直接用 cursor: url() 实现动态光标
因为 cursor: url() 只支持静态图片,无法响应鼠标实时坐标、缩放、滚动偏移或页面内定位变化。一旦页面有 transform、scroll 或 position: fixed 容器,系统级光标就完全脱离上下文——它压根不知道你在滚动或缩放什么。
真正可控的“跟随光标”,本质是隐藏原生光标 + 用一个 div 模拟,并靠 JS 实时更新其 left/top。关键不在“画个图标”,而在“坐标算得对不对”。
fixed 定位模拟光标时,pageX/pageY 为什么不准
直接把 event.pageX 赋给 fixed 元素的 left/top,在滚动后会明显偏移。因为 pageX/pageY 是相对于整个文档左上角的坐标,而 fixed 元素的定位基准是视口(viewport)左上角——两者差了一个 window.scrollX 和 window.scrollY。
正确做法是用 clientX/clientY,它天然以视口为参考系,和 fixed 定位同源:
document.addEventListener('mousemove', (e) => {
cursorEl.style.left = `${e.clientX}px`;
cursorEl.style.top = `${e.clientY}px`;
});
-
clientX/clientY:推荐,免去滚动补偿,兼容性好(IE9+) -
pageX/pageY:需手动减去window.scrollX/window.scrollY,多一步易错 - 避免用
screenX/screenY:那是屏幕坐标,和页面完全无关
如何处理页面缩放(zoom / transform: scale)导致的偏移
当用户缩放页面(Ctrl+/- 或 macOS 触控板缩放),clientX/clientY 仍准确,但模拟光标元素的渲染尺寸会随缩放拉伸,造成“光标图标变大但移动轨迹变慢”的错觉。这不是坐标错,是视觉反馈失配。
解决方案分两步:
- 用
transform: scale()替代宽高缩放,保持元素布局尺寸不变 - 监听
resize或用window.devicePixelRatio粗略判断缩放(注意:无标准 API 获取 zoom 值,devicePixelRatio仅适用于设备像素比变化场景) - 更稳妥的做法:给光标元素加
pointer-events: none,并确保其z-index足够高,避免被缩放容器裁剪
移动端 touch 事件怎么同步适配
移动端没有 mousemove,必须监听 touchmove,且注意:touches 是类数组,通常只取第一个触点(e.touches[0]);同时要 preventDefault() 防止默认滚动干扰光标追踪。
建议合并监听逻辑:
const handleMove = (e) => {
const x = e.type === 'touchmove' ? e.touches[0].clientX : e.clientX;
const y = e.type === 'touchmove' ? e.touches[0].clientY : e.clientY;
cursorEl.style.left = `${x}px`;
cursorEl.style.top = `${y}px`;
};
<p>document.addEventListener('mousemove', handleMove);
document.addEventListener('touchmove', handleMove, { passive: false });</p>
- 务必加
{ passive: false },否则preventDefault()在某些浏览器中无效 - 移动端还需禁用原生光标:在 CSS 中加
* { cursor: none; },但注意别影响textarea等可编辑元素的文本光标 - 触摸结束(
touchend)后,光标可能悬停在最后位置,建议加个setTimeout清除或淡出
最麻烦的不是代码,而是不同设备下 touchstart 的触发时机、click 与 touchend 的竞态、以及 iOS Safari 对 cursor: none 的部分忽略——这些细节不实测几轮根本绕不过去。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











