Skip to content

Instantly share code, notes, and snippets.

@balazsbotond
Last active April 25, 2024 11:19
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save balazsbotond/1a876d8ccec87e961ec4a4ae5efb5d33 to your computer and use it in GitHub Desktop.
Save balazsbotond/1a876d8ccec87e961ec4a4ae5efb5d33 to your computer and use it in GitHub Desktop.
Simple Canvas Pan And Zoom

Simple Canvas Pan And Zoom

I needed a simple way to implement dragging and scroll wheel zooming on an HTML canvas, but I couldn't find a pure JS example. The most popular one uses an SVG node to convert coordinates, but I didn't like that so I created my own version.

Try it here: https://jsfiddle.net/balazsbotond/6n2uzjkq/30/

class Transform {
constructor(ctx) {
this.ctx = ctx;
this.s = 1;
this.dx = 0;
this.dy = 0;
}
scale(s) {
this.ctx.scale(s, s);
this.s *= 1 / s;
this.dx *= 1 / s;
this.dy *= 1 / s;
}
translate(dx, dy) {
this.ctx.translate(dx, dy);
this.dx -= dx;
this.dy -= dy;
}
transform({ x, y }) {
return {
x: this.s * x + this.dx,
y: this.s * y + this.dy
};
}
}
class PanAndZoom {
constructor(canvas, draw) {
this.canvas = canvas;
this.ctx = this.canvas.getContext('2d');
this.onDraw = draw;
this.transform = new Transform(this.ctx);
this.canvas.addEventListener('wheel', e => this.onWheel(e));
this.canvas.addEventListener('mousedown', e => this.onMouseDown(e));
this.canvas.addEventListener('mousemove', e => this.onMouseMove(e));
this.canvas.addEventListener('mouseup', e => this.onMouseUp(e));
this.draw();
}
draw() {
this.onDraw(this.ctx, this.transform);
}
mouseOffset(e) {
return {
x: e.pageX - this.canvas.offsetLeft,
y: e.pageY - this.canvas.offsetTop
};
}
onMouseDown(e, ctx) {
e.preventDefault();
e.stopPropagation();
this.dragStart = this.transform.transform(this.mouseOffset(e));
this.dragging = true;
}
onMouseMove(e, ctx) {
e.preventDefault();
e.stopPropagation();
if (!this.dragging) {
return;
}
const offset = this.mouseOffset(e);
const dragEnd = this.transform.transform(offset);
const dx = dragEnd.x - this.dragStart.x;
const dy = dragEnd.y - this.dragStart.y;
this.transform.translate(dx, dy);
this.draw();
this.dragStart = this.transform.transform(offset);
}
onMouseUp(e, ctx) {
e.preventDefault();
e.stopPropagation();
this.dragging = false;
}
onWheel(e, ctx) {
e.preventDefault();
e.stopPropagation();
const offset = this.mouseOffset(e);
const zoomCenter = this.transform.transform(offset);
const factor = Math.sign(e.deltaY) > 0 ? 0.9 : 1.1;
this.transform.translate(zoomCenter.x, zoomCenter.y);
this.transform.scale(factor);
this.transform.translate(-zoomCenter.x, -zoomCenter.y);
this.draw();
}
}
function clearCanvas(ctx, transform) {
const { x: left, y: top } = transform.transform({ x: 0, y: 0 });
const { x: right, y: bottom } = transform.transform({ x: ctx.canvas.width, y: ctx.canvas.height });
const width = Math.abs(right - left);
const height = Math.abs(bottom - top);
ctx.fillStyle = 'black';
ctx.fillRect(left, top, width, height);
}
function drawCircle(ctx) {
ctx.beginPath();
ctx.arc(100, 100, 50, 0, 2 * Math.PI, false);
ctx.strokeStyle = 'teal';
ctx.lineWidth = 5;
ctx.fillStyle = 'white';
ctx.stroke();
ctx.fill();
}
function draw(ctx, transform) {
clearCanvas(ctx, transform);
drawCircle(ctx);
}
function run() {
const canvas = document.getElementById('c');
const panAndZoom = new PanAndZoom(canvas, draw);
}
document.addEventListener('DOMContentLoaded', run);
<canvas id="c" width="200" height="200" style="border: 2px solid gold;"></canvas>
@zhoupan163
Copy link

Awesome! Great code!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment