如何在React中实时绘制鼠标移动轨迹?

开发者艳丽 阅读 11

我在做一个画板功能,想记录并实时显示鼠标移动的路径,但发现轨迹断断续续,甚至有时候根本不显示。我监听了 mousemove 事件,并把坐标存到 state 里,然后用 SVG 的 polyline 渲染,可效果很卡,还经常漏点。

是不是 setState 太频繁导致的?或者我的渲染方式有问题?下面是我目前的简化代码:

const MouseTrail = () => {
  const [points, setPoints] = useState([]);
  
  const handleMouseMove = (e) => {
    setPoints(prev => [...prev, { x: e.clientX, y: e.clientY }]);
  };

  return (
    <div onMouseMove={handleMouseMove} style={{ width: '100vw', height: '100vh' }}>
      <svg style={{ position: 'absolute', top: 0, left: 0 }}>
        <polyline points={points.map(p => <code>${p.x},${p.y}</code>).join(' ')} 
                  stroke="red" fill="none" />
      </svg>
    </div>
  );
};
我来解答 赞 3 收藏
二维码
手机扫码查看
1 条解答
Mc.志鲜
Mc.志鲜 Lv1
问题在于 mousemove 触发太频繁,每次都 setState 会导致严重卡顿。用 ref 存点,用 requestAnimationFrame 控制渲染频率,或者直接上 canvas。

用 ref + requestAnimationFrame 优化版:

const MouseTrail = () => {
const pointsRef = useRef([]);
const [points, setPoints] = useState([]);

useEffect(() => {
let rafId;
const handleMouseMove = (e) => {
pointsRef.current.push({ x: e.clientX, y: e.clientY });
rafId = requestAnimationFrame(() => {
setPoints([...pointsRef.current]);
});
};

window.addEventListener('mousemove', handleMouseMove);
return () => {
window.removeEventListener('mousemove', handleMouseMove);
cancelAnimationFrame(rafId);
};
}, []);

const pathData = points.map(p => ${p.x},${p.y}).join(' ');

return (
<svg style={{ position: 'absolute', top: 0, left: 0, width: '100%', height: '100%', pointerEvents: 'none' }}>
<polyline points={pathData} stroke="red" strokeWidth="2" fill="none" />
</svg>
);
};


如果点太多还是卡,直接换 canvas,渲染上千个点都没问题:

const MouseTrail = () => {
const canvasRef = useRef(null);
const pointsRef = useRef([]);

useEffect(() => {
const canvas = canvasRef.current;
const ctx = canvas.getContext('2d');
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;

const handleMouseMove = (e) => {
pointsRef.current.push({ x: e.clientX, y: e.clientY });

// 只画最新50个点,避免线条太长
if (pointsRef.current.length > 50) pointsRef.current.shift();

ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.beginPath();
ctx.strokeStyle = 'red';
ctx.lineWidth = 2;
pointsRef.current.forEach((p, i) => {
i === 0 ? ctx.moveTo(p.x, p.y) : ctx.lineTo(p.x, p.y);
});
ctx.stroke();
};

window.addEventListener('mousemove', handleMouseMove);
return () => window.removeEventListener('mousemove', handleMouseMove);
}, []);

return <canvas ref={canvasRef} style={{ position: 'absolute', top: 0, left: 0 }} />;
};
点赞
2026-03-18 16:19