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
相关产品推荐
相关产品推荐

