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

如何避免React在鼠标位置更新时重复重绘Canvas?

问题分析

你的问题核心在于多个并行的requestAnimationFrame渲染循环同时运行,导致气泡被多次驱动移动,看起来速度异常。具体原因:

  • draw函数在组件每次重渲染(比如mousePos更新时)都会被重新创建,而渲染逻辑的useEffect依赖draw,所以每次draw变化都会启动新的渲染循环,鼠标高频移动时会短时间内存在多个并行循环。
  • draw函数内每次渲染都重置canvas宽高,既浪费性能,也会干扰canvas渲染状态。
优化方案

1. 稳定draw函数,避免频繁重建

用useCallback包裹draw函数,明确依赖项,确保仅在依赖变化时重新创建:

const draw = useCallback((ctx: CanvasRenderingContext2D) => {
  ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
  bubbles.forEach((bubble) => {
    bubble.intersectWall();
    bubble.move();
    bubble.draw(mousePos);
  });
}, [bubbles, mousePos]);

将mousePos作为draw的依赖而非参数传递,既保证鼠标位置变化时能正确绘制瞳孔,也让draw的定义更稳定。

2. 调整渲染循环的触发逻辑

将渲染useEffect的依赖改为稳定后的draw,同时把canvas尺寸调整剥离到resize事件中:

useEffect(() => {
  const canvas = canvasRef.current as HTMLCanvasElement;
  const context = canvas.getContext("2d") as CanvasRenderingContext2D;
  
  // 单独处理canvas尺寸适配
  const resizeCanvas = () => {
    canvas.width = window.innerWidth;
    canvas.height = window.innerHeight;
  };
  resizeCanvas();
  window.addEventListener('resize', resizeCanvas);

  let animationFrameId: number;
  const render = () => {
    draw(context);
    animationFrameId = window.requestAnimationFrame(render);
  };
  render();

  return () => {
    window.cancelAnimationFrame(animationFrameId);
    window.removeEventListener('resize', resizeCanvas);
  };
}, [draw]);

这样避免了每次渲染都重置canvas尺寸,减少不必要的性能损耗。

3. 优化鼠标位置更新逻辑

将screenX/screenY替换为clientX/clientY(更符合canvas视口坐标系),同时用useCallback稳定事件处理函数:

const handleMouseMove = useCallback((e: MouseEvent) => {
  setMousePos({ x: e.clientX, y: e.clientY });
}, []);

4. 修正初始化逻辑的依赖

确保气泡初始化仅执行一次,且鼠标事件监听绑定稳定的处理函数:

useEffect(() => {
  const canvas = canvasRef.current as HTMLCanvasElement;
  const context = canvas.getContext("2d") as CanvasRenderingContext2D;

  window.addEventListener("mousemove", handleMouseMove);

  const initBubbles = [];
  for (let i = 0; i < 1; i++) {
    initBubbles.push(
      new Bubble(
        randomNumber(0 + BUBBLE_RADIUS, window.innerWidth - BUBBLE_RADIUS),
        randomNumber(0 + BUBBLE_RADIUS, window.innerHeight - BUBBLE_RADIUS),
        randomNumber(-1, 4),
        randomNumber(-4, 4),
        context
      )
    );
  }
  setBubbles(initBubbles);

  return () => {
    window.removeEventListener("mousemove", handleMouseMove);
  };
}, [handleMouseMove]);
核心优化总结
  • 用useCallback稳定频繁创建的函数,避免触发不必要的useEffect执行。
  • 将canvas尺寸调整从渲染循环中剥离,放到resize事件单独处理。
  • 确保始终只有一个requestAnimationFrame循环在运行,避免并行渲染导致的速度异常。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.31 23:50:33