复刻Aseprite画布定点缩放功能遇像素偏移问题求助
复刻Aseprite画布定点缩放时的像素偏移问题
我正尝试复刻Aseprite的画布定点缩放功能,但当前代码在画布不同区域进行缩放操作时,会出现像素偏移的问题。已知该问题对应Stack Overflow上《Zoom in on a point (using scale and translate)》的重复问题,参考过相关解答但无法适配现有代码,请求技术协助。
效果说明
- 问题效果:缩放时画布会偏移,鼠标指向的画布位置无法保持在原屏幕位置
- 预期效果:Aseprite式缩放,鼠标指向的画布区域在缩放过程中始终固定在当前鼠标位置
原问题代码
<html> <body> <main> <section Id="canvas-panel"> <section id="canvas" style="width: 256px; height: 256px; background-size: 12.5%" > <canvas width="256px" height="256px"></canvas> </section> </section> </main> </body> </html> <script> let canvasPanel = document.getElementById("canvas-panel"); let canvas = document.getElementById("canvas"); // Function to calculate the closest point on the border of the canvas to the click position. function calculateClosestBorderPoint(event) { const canvasRect = canvas.getBoundingClientRect(); const clickX = event.clientX; const clickY = event.clientY; let closestX = canvasRect.left; let closestY = canvasRect.top; // Determine if the click is closer to the left or right side of the canvas. if (clickX < canvasRect.left) { closestX = canvasRect.left; } else if (clickX > canvasRect.right) { closestX = canvasRect.right; } else { closestX = clickX; } // Determine if the click is closer to the top or bottom of the canvas. if (clickY < canvasRect.top) { closestY = canvasRect.top; } else if (clickY > canvasRect.bottom) { closestY = canvasRect.bottom; } else { closestY = clickY; } return { x: closestX, y: closestY }; } // Initialize scale. let scale = 1; // Define zoom limits const minScale = 0.5; // Minimum zoom limit. const maxScale = 4.0; // Maximum zoom limit. // Function to update the canvas transformation. function updateCanvasTransform() { canvas.style.transform = `scale(${scale})`; } canvasPanel.addEventListener("wheel", function (e) { e.preventDefault(); const zoomDirection = e.deltaY < 0 ? 1 : -1; if (scale == minScale && zoomDirection == -1) return; if (scale == maxScale && zoomDirection == 1) return; const zoomFactor = 1.1; const closestPoint = calculateClosestBorderPoint(event); scale *= zoomFactor ** zoomDirection; // Apply zoom factor based on direction // Clamp scale to limits scale = Math.min(scale, maxScale); scale = Math.max(scale, minScale); // Update canvas position to keep the closest point centered relative to the viewport const canvasRect = canvas.getBoundingClientRect(); const viewportCenterX = canvasRect.width / 2; const viewportCenterY = canvasRect.height / 2; // Convert closest point to relative position within the viewport (0-1 scale) let relativeX = ((closestPoint.x - canvasRect.left) / canvasRect.width) * 100; let relativeY = ((closestPoint.y - canvasRect.top) / canvasRect.height) * 100; canvas.style.transformOrigin = `${relativeX}% ${relativeY}%`; // Update canvas transformation using only scale updateCanvasTransform(); }); </script> <style> #canvas { position: fixed; overflow: hidden; padding: 0; margin: 0; transform: scale(1) translate(0px, 0px); background-color: #1d1d1d; } #canvas-panel { position: relative; width: 100%; height: 100%; background-color: #303030; } html, body { background-color: #161616; margin: 0; width: 100%; height: 100%; } </style>
解决方案
核心思路是使用translate + scale的组合变换,严格按照定点缩放的数学公式计算偏移量,确保鼠标指向的画布位置在缩放前后保持在屏幕的同一位置。以下是修改后的完整代码:
<html> <body> <main> <section id="canvas-panel"> <section id="canvas" style="width: 256px; height: 256px; background-size: 12.5%" > <canvas width="256px" height="256px"></canvas> </section> </section> </main> </body> </html> <script> const canvasPanel = document.getElementById("canvas-panel"); const canvas = document.getElementById("canvas"); // 初始化缩放和偏移量 let scale = 1; let translateX = 0; let translateY = 0; // 缩放限制 const minScale = 0.5; const maxScale = 4.0; const zoomFactor = 1.1; // 更新画布变换 function updateCanvasTransform() { canvas.style.transform = `translate(${translateX}px, ${translateY}px) scale(${scale})`; } canvasPanel.addEventListener("wheel", function(e) { e.preventDefault(); const zoomDirection = e.deltaY < 0 ? 1 : -1; const newScale = Math.min(Math.max(scale * (zoomFactor ** zoomDirection), minScale), maxScale); if (newScale === scale) return; // 获取鼠标相对于画布容器的坐标 const panelRect = canvasPanel.getBoundingClientRect(); const mouseX = e.clientX - panelRect.left; const mouseY = e.clientY - panelRect.top; // 获取画布原始尺寸(未缩放) const canvasWidth = canvas.offsetWidth; const canvasHeight = canvas.offsetHeight; // 计算缩放前后的偏移量变化,确保鼠标点位置不变 const mouseCanvasX = (mouseX - translateX) / scale; const mouseCanvasY = (mouseY - translateY) / scale; // 更新缩放和偏移 scale = newScale; translateX = mouseX - mouseCanvasX * scale; translateY = mouseY - mouseCanvasY * scale; updateCanvasTransform(); }); </script> <style> #canvas { position: absolute; overflow: hidden; padding: 0; margin: 0; transform-origin: 0 0; /* 固定变换原点为左上角,简化计算 */ background-color: #1d1d1d; } #canvas-panel { position: relative; width: 100%; height: 100%; overflow: hidden; background-color: #303030; } html, body { background-color: #161616; margin: 0; width: 100%; height: 100%; overflow: hidden; } </style>
关键修改点
- 固定变换原点:将
transform-origin设为左上角(0 0),避免动态修改原点带来的计算误差 - 记录偏移量:新增
translateX/Y变量跟踪画布的位置偏移 - 严格数学计算:根据鼠标位置、当前缩放比例,计算缩放后的偏移量,确保鼠标指向的画布坐标在缩放前后对应同一屏幕位置
- 简化容器布局:将画布的
position改为absolute,容器添加overflow: hidden,避免固定定位的布局冲突
内容的提问来源于stack exchange,提问作者RandomSkinCreator
相关产品推荐
相关产品推荐

