You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

React.js自定义光标滚动时无法跟随鼠标问题咨询

问题原因

你现在的实现有3个核心问题,导致滚动时光标位置异常:

  • 定位参考系和坐标计算逻辑不匹配:你在mousemove事件里给坐标加了window.pageYOffset,说明你给自定义光标用的是position: absolute(相对于整个文档定位),但滚动事件里你又在已有的style值基础上重复累加滚动偏移,滚动次数越多偏移误差越大。
  • 事件绑定方式错误:直接在函数组件的渲染主流程里调用window.addEventListener,每次组件重渲染都会新增一个重复的scroll监听器,监听器不断叠加会直接导致逻辑混乱、内存泄漏。
  • 多余的scroll监听完全没必要:只要选对定位方式,根本不需要额外监听滚动事件计算位置。
最优解决方案(性能最好、逻辑最简单)

自定义光标全局跟随的场景,优先用position: fixed定位,参考系是浏览器视口,滚动时光标会自动保持和视口的相对位置,完全不需要计算滚动偏移,也不需要绑定scroll事件。

具体实现步骤

  1. 给自定义光标元素添加基础样式,注意必须加pointer-events: none避免光标挡住页面元素交互,用transform移动元素可以触发GPU加速,比修改top/left流畅度高很多:
.custom-cursor {
  position: fixed;
  width: 30px;
  height: 30px;
  border-radius: 50%;
  pointer-events: none;
  z-index: 9999;
  /* 初始位置可以放在视口外,避免加载时闪现在左上角 */
  top: 0;
  left: 0;
}
  1. 删掉所有scroll事件相关的绑定和计算逻辑,把mousemove事件直接绑定在window上(绑在.app容器上会导致鼠标移出容器后光标不跟随),事件逻辑简化为直接取clientX/clientY赋值,不需要加任何滚动偏移:
import React, { useEffect, createRef } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { hideCustomCursor, showCustomCursor } from 'src/actions';

import Header from 'src/components/Header';
import Portfolio from 'src/components/Portfolio';
import MainTitile from 'src/components/MainTitle';
import TopButton from 'src/components/TopButton';
import ServicesSkill from 'src/components/ServicesSkill';
import About from 'src/components/About';
import Contact from 'src/components/Contact';
import CustomCursor from 'src/components/CustomCursor';

import './styles.scss';

function App() {
  const dispatch = useDispatch();
  const customCursorVisible = useSelector((state) => state.customCursorVisible);
  const cursor = createRef();

  const hideCursor = () => {
    dispatch(hideCustomCursor());
  };

  const showCursor = () => {
    dispatch(showCustomCursor());
  };

  // 用useEffect绑定事件,只在组件挂载时执行一次,卸载时清理监听器
  useEffect(() => {
    const handleMouseMove = (event) => {
      if (!cursor.current) return;
      // 直接用视口坐标,减15是为了让光标中心对齐鼠标位置
      cursor.current.style.transform = `translate(${event.clientX - 15}px, ${event.clientY - 15}px)`;
    };
    window.addEventListener('mousemove', handleMouseMove);
    window.addEventListener('mouseleave', hideCursor);
    window.addEventListener('mouseenter', showCursor);

    return () => {
      window.removeEventListener('mousemove', handleMouseMove);
      window.removeEventListener('mouseleave', hideCursor);
      window.removeEventListener('mouseenter', showCursor);
    };
  }, []);

  return (
    <div className="app">
      { customCursorVisible && (<CustomCursor ref={cursor} />)}
      <Header />
      <TopButton />
      <MainTitile />
      <ServicesSkill />
      <Portfolio />
      <About />
      <Contact />
    </div>
  );
}

export default App;
其他方案说明

如果你因为特殊需求必须用position: absolute定位,也不需要在scroll事件里累加之前的坐标值:只需要用一个ref实时存储最新的clientX/clientY值,scroll事件触发时,直接用存储的clientY + window.pageYOffset计算top值、存储的clientX + window.pageXOffset计算left值即可,每次都重新计算全量坐标,不要基于已有的style值做累加,避免偏移误差。但这种方式需要额外绑定scroll事件,性能不如fixed定位方案。

注意:不要用React state存储光标坐标,mousemove事件触发频率极高,每次setState都会触发组件重渲染,会导致明显的卡顿,用ref直接操作DOM是这类高频交互场景的最优选择。


内容的提问来源于stack exchange,提问作者David Poulain

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.26 23:18:47