Simple Canvas Pan and Zoom
by Botond Balázs
HTML
<canvas width="200" height="200" id="c"></canvas>
CSS
canvas { border: 2px solid gold; }
JavaScript
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 t = this.transform.transform(offset);
this.transform.translate(t.x, t.y);
...