Skip to content

Instantly share code, notes, and snippets.

@krabhi1
Last active November 9, 2023 03:39
Show Gist options
  • Save krabhi1/af717bca34b18487753c54f6e7f572f0 to your computer and use it in GitHub Desktop.
Save krabhi1/af717bca34b18487753c54f6e7f572f0 to your computer and use it in GitHub Desktop.
pan+zoom logic in canvas react

pan+zoom

very smooth pan+zoom logic in react+canvas

Functions

  • worldToScreen()
  • screenToWorld()
import { useEffect, useRef } from "react";
class Point {
constructor(public x: number = 0, public y: number = 0) {}
}
export default function App() {
const canvasRef = useRef<HTMLCanvasElement>(null);
useEffect(() => {
const canvas = canvasRef.current!!;
const ctx = canvas.getContext("2d")!!;
let isDragging = false;
let startDrag = new Point();
let offset = new Point();
let currentZoom = 1;
const minZoom = 0.1; // Minimum zoom level
const maxZoom = 8; // Maximum zoom level
canvas.addEventListener("pointerdown", (e) => {
const down = getMousePoint(e);
console.log("offset", offset, "down", down);
isDragging = true;
startDrag.x = down.x - offset.x;
startDrag.y = down.y - offset.y;
canvas.setPointerCapture(e.pointerId);
});
canvas.addEventListener("pointerup", (e) => {
isDragging = false;
const up = getMousePoint(e);
canvas.releasePointerCapture(e.pointerId);
});
canvas.addEventListener("pointermove", (e) => {
const move = getMousePoint(e);
if (isDragging) {
offset.x = move.x - startDrag.x;
offset.y = move.y - startDrag.y;
onDraw();
} else {
const world = screenToWorld(move);
const screen=worldToScreen(world)
console.log("world", world,"screen",screen);
}
});
canvas.addEventListener("wheel", (e) => {
const point = getMousePoint(e as any);
const zoomFactor = e.deltaY > 0 ? 0.9 : 1.1;
const newZoom = currentZoom * zoomFactor;
if (newZoom >= minZoom && newZoom <= maxZoom) {
// Calculate the translation needed to keep the mouse cursor fixed
const scale = newZoom / currentZoom;
const dx = (point.x - offset.x) * (1 - scale);
const dy = (point.y - offset.y) * (1 - scale);
offset.x += dx;
offset.y += dy;
currentZoom = newZoom;
onDraw();
}
});
function onDraw() {
ctx.clearRect(0, 0, 600, 400);
ctx.save();
ctx.translate(offset.x, offset.y);
ctx.scale(currentZoom, currentZoom);
drawLines([new Point(0, 1000), new Point(0, 0), new Point(1000, 0)]);
ctx.fillRect(70, 70, 100, 100);
ctx.fillRect(200, 70, 100, 50);
ctx.restore();
}
onDraw();
//utils
function getMousePoint(e: PointerEvent) {
const rect = canvas.getBoundingClientRect();
return {
x: e.clientX - rect.left,
y: e.clientY - rect.top,
} as Point;
}
function drawLines(points: Point[]) {
if (points.length < 2) {
return;
}
ctx.beginPath();
ctx.moveTo(points[0].x, points[0].y);
for (let i = 1; i < points.length; i++) {
ctx.lineTo(points[i].x, points[i].y);
}
ctx.stroke();
}
function screenToWorld(screen: Point) {
const worldX = (screen.x - offset.x) / currentZoom;
const worldY = (screen.y - offset.y) / currentZoom;
return new Point(worldX, worldY);
}
function worldToScreen(world: Point) {
const screenX = world.x * currentZoom + offset.x;
const screenY = world.y * currentZoom + offset.y;
return new Point(screenX, screenY);
}
}, []);
return (
<div>
<canvas
ref={canvasRef}
width={"600px"}
height={"400px"}
style={{
border: "black solid 1px",
}}
></canvas>
</div>
);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment