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

React Canvas实现谷歌地图式光标定点缩放问题求助

实现画布以鼠标为中心的缩放效果

核心思路

要实现类似谷歌地图的缩放效果,关键是让鼠标位置对应的画布原始坐标(世界坐标)在缩放前后保持不变。你之前的问题在于固定以画布中心为缩放原点,现在需要在缩放触发时动态调整平移偏移量(panOffset),而非在渲染阶段用固定比例计算偏移。

具体修改步骤

1. 跟踪鼠标在画布上的真实坐标

首先需要获取鼠标相对于画布内部的坐标(要考虑画布可能的CSS缩放,比如画布元素被CSS调整了显示尺寸):

const [mousePos, setMousePos] = useState({ x: 0, y: 0 });

useLayoutEffect(() => {
  const canvas = document.getElementById("canvas");
  const handleMouseMove = (e) => {
    const rect = canvas.getBoundingClientRect();
    // 转换为画布内部的真实坐标,适配CSS缩放
    const x = (e.clientX - rect.left) * (canvas.width / rect.width);
    const y = (e.clientY - rect.top) * (canvas.height / rect.height);
    setMousePos({ x, y });
  };
  canvas.addEventListener("mousemove", handleMouseMove);
  return () => canvas.removeEventListener("mousemove", handleMouseMove);
}, []);

2. 重写缩放事件处理逻辑

在滚轮触发缩放时,计算新的缩放比例和对应的平移偏移量,确保鼠标位置的世界坐标不变:

const handleWheel = (e) => {
  e.preventDefault();
  const canvas = document.getElementById("canvas");
  const rect = canvas.getBoundingClientRect();
  
  // 获取当前鼠标在画布内的真实坐标
  const mouseX = (e.clientX - rect.left) * (canvas.width / rect.width);
  const mouseY = (e.clientY - rect.top) * (canvas.height / rect.height);

  // 定义缩放因子,可根据需求调整
  const delta = e.deltaY > 0 ? 0.9 : 1.1;
  const newScale = Math.max(0.1, Math.min(10, scale * delta)); // 限制缩放范围,避免极端值

  // 计算缩放前后的世界坐标,保持鼠标点的世界坐标不变
  const worldX = (mouseX - panOffset.x) / scale;
  const worldY = (mouseY - panOffset.y) / scale;
  const newPanX = mouseX - worldX * newScale;
  const newPanY = mouseY - worldY * newScale;

  setScale(newScale);
  setPanOffset({ x: newPanX, y: newPanY });
};

记得给画布添加滚轮事件监听:

useLayoutEffect(() => {
  const canvas = document.getElementById("canvas");
  canvas.addEventListener("wheel", handleWheel);
  return () => canvas.removeEventListener("wheel", handleWheel);
}, [scale, panOffset]);

3. 简化渲染阶段的变换逻辑

原来的中心偏移计算(scaleOffsetX/Y)已经不需要了,直接用调整后的panOffset和scale进行画布变换:

useLayoutEffect(() => {
  const canvas = document.getElementById("canvas");
  const ctx = canvas.getContext("2d");
  ctx.clearRect(0, 0, canvas.width, canvas.height);

  ctx.save();
  // 直接应用平移和缩放变换
  ctx.translate(panOffset.x, panOffset.y);
  ctx.scale(scale, scale);

  elements.forEach((element) => {
    if (action === "writing" && selectedElement.id === element.id) return;
    drawElement(ctx, element);
  });
  ctx.restore();
}, [elements, action, selectedElement, panOffset, scale]);

原理说明

  • 世界坐标:指画布元素未经过缩放和平移的原始坐标,比如你绘制的图形的原始位置。
  • 缩放时,先通过当前鼠标的画布坐标和panOffset、scale计算出对应的世界坐标;缩放后,反向计算新的panOffset,让这个世界坐标仍然对应鼠标当前的画布位置,从而实现鼠标点“固定”的缩放效果。
  • 之前你尝试用鼠标百分比替换0.5出现偏移,是因为没有在缩放触发时关联panOffset和scale的变化,只是在渲染阶段临时调整偏移,导致鼠标移动时状态不匹配。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.30 10:04:59