Hello I need some help with applying an answer on another question to my code. I’m currently trying to recreate aseprite’s zoom functionality but, with my current code, when I zoom on different sides of the canvas it shifts a couple of pixels.
Previously I was told that my question was a duplicate of: Zoom in on a point (using scale and translate)
But I can’t seem to get their answer to work with my code. Please help me with this.
Here is a gif displaying what currently happens:
Zoom shift problem
And here is how it should work:
Aseprite zoom
Here is my relavant code:
<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>