Skip to content

Instantly share code, notes, and snippets.

@mlomb
Last active February 20, 2022 22:23
Show Gist options
  • Save mlomb/c3de284779cbefa105f44b1026d1885b to your computer and use it in GitHub Desktop.
Save mlomb/c3de284779cbefa105f44b1026d1885b to your computer and use it in GitHub Desktop.
Boilerplate for canvas with zoom and panning with minimal code
<html>
<head>
<title>Visualizer boilerplate</title>
</head>
<body>
<style>
body {
background-color: #303030;
margin: 0;
padding: 0;
overflow: hidden;
}
</style>
<canvas></canvas>
<script>
const canvas = document.querySelector('canvas');
const ctx = canvas.getContext('2d');
function draw(){
ctx.fillStyle = "#ff00ff";
ctx.fillRect(50,50,100,100);
}
function onClick(x, y, button) {
console.log("onClick", x, y, button);
}
</script>
<script>
let dirty = true;
let scale = 1;
let pos = { x: 0, y: 0 };
let panning = false;
function pan(amount) {
if(amount.x !== 0 || amount.y !== 0) {
panning = true;
dirty = true;
pos.x += amount.x;
pos.y += amount.y;
}
}
function scaleAt(at, amount) {
scale *= amount;
pos.x = at.x - (at.x - pos.x) * amount;
pos.y = at.y - (at.y - pos.y) * amount;
dirty = true;
}
function drawCanvas() {
requestAnimationFrame(drawCanvas);
if (dirty) {
dirty = false;
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
ctx.setTransform(1, 0, 0, 1, 0, 0);
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.setTransform(
scale, 0,
0, scale,
pos.x, pos.y
);
draw();
}
}
window.onresize = () => dirty = true;
canvas.oncontextmenu = e => e.preventDefault();
canvas.addEventListener("mousemove", mouseEvent);
canvas.addEventListener("mousedown", mouseEvent);
canvas.addEventListener("mouseup", mouseEvent);
canvas.addEventListener("mouseout", mouseEvent);
canvas.addEventListener("wheel", mouseWheelEvent, { passive: false });
const mouse = { x: 0, y: 0, oldX: 0, oldY: 0, button: false };
function mouseEvent(event) {
if (event.type === "mousedown") {
mouse.button = true;
panning = false;
}
if (event.type === "mouseup" || event.type === "mouseout") {
if(mouse.button && panning == false) {
const invScale = (1/scale);
onClick(
Math.ceil((mouse.x - pos.x) * invScale),
Math.ceil((mouse.y - pos.y) * invScale),
event.button
);
}
mouse.button = false;
}
mouse.oldX = mouse.x;
mouse.oldY = mouse.y;
mouse.x = event.offsetX;
mouse.y = event.offsetY;
if(mouse.button) pan({ x: mouse.x - mouse.oldX, y: mouse.y - mouse.oldY });
}
function mouseWheelEvent(event) {
var x = event.offsetX;
var y = event.offsetY;
if (event.deltaY < 0) { scaleAt({x, y}, 1.1) }
else { scaleAt({x, y}, 1 / 1.1) }
event.preventDefault();
}
requestAnimationFrame(drawCanvas);
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment