Last active
September 23, 2020 19:35
-
-
Save bellinitte/3bdd04332901810b7c0d83ea9747e0a6 to your computer and use it in GitHub Desktop.
Utility functions for handling geometrically correct panning and zooming of a canvas
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
class Viewport { | |
constructor(canvas) { | |
this.canvas = canvas; | |
this.camera = { | |
x: 0, | |
y: 0, | |
z: 1, | |
}; | |
this.pivot = null; | |
} | |
startPan(screenPoint) { | |
this.pivot = { | |
x: this.camera.x + (screenPoint.x - this.canvas.width / 2) * this.camera.z, | |
y: this.camera.y + (screenPoint.y - this.canvas.height / 2) * this.camera.z, | |
}; | |
} | |
pan(screenPoint) { | |
if (this.pivot) { | |
this.camera.x = this.pivot.x - (screenPoint.x - this.canvas.width / 2) * this.camera.z; | |
this.camera.y = this.pivot.y - (screenPoint.y - this.canvas.height / 2) * this.camera.z; | |
} | |
} | |
endPan() { | |
this.pivot = null; | |
} | |
zoom(screenPivotPoint, factor) { | |
const difference = this.camera.z * (1 - factor); | |
this.camera.x += (screenPivotPoint.x - this.canvas.width / 2) * difference; | |
this.camera.y += (screenPivotPoint.y - this.canvas.height / 2) * difference; | |
this.camera.z *= factor; | |
} | |
projectToCanvas(screenPoint) { | |
return { | |
x: (screenPoint.x - this.canvas.width / 2) * this.camera.z + this.camera.x, | |
y: (screenPoint.y - this.canvas.height / 2) * this.camera.z + this.camera.y, | |
}; | |
} | |
projectToScreen(canvasPoint) { | |
return { | |
x: this.canvas.width / 2 + (canvasPoint.x - this.camera.x) / this.camera.z, | |
y: this.canvas.height / 2 + (canvasPoint.y - this.camera.y) / this.camera.z, | |
}; | |
} | |
scaleToCanvas(screenScalar) { | |
return screenScalar * this.camera.z; | |
} | |
scaleToScreen(canvasScalar) { | |
return canvasScalar / this.camera.z; | |
} | |
} | |
const canvas = document.getElementsByTagName('canvas')[0]; | |
const viewport = new Viewport(canvas); | |
const ctx = canvas.getContext('2d'); | |
function resize() { | |
canvas.width = window.innerWidth; | |
canvas.height = window.innerHeight; | |
} | |
window.addEventListener('resize', resize); | |
resize(); | |
window.addEventListener('mousedown', e => { | |
viewport.startPan({x: e.x, y: e.y}); | |
}); | |
window.addEventListener('mousemove', e => { | |
viewport.pan({x: e.x, y: e.y}); | |
}); | |
window.addEventListener('mouseup', e => { | |
viewport.endPan(); | |
}); | |
window.addEventListener("wheel", e => { | |
const factor = e.deltaY < 0 ? 0.9 : (1 / 0.9); | |
viewport.zoom({x: e.x, y: e.y}, factor); | |
}); | |
function animate() { | |
ctx.clearRect(0, 0, canvas.width, canvas.height); | |
// draw a grid of points | |
for (let y = -2; y <= 2; y++) { | |
for (let x = -2; x <= 2; x++) { | |
ctx.beginPath(); | |
const point = viewport.projectToScreen({x: x * 100, y: y * 100}); | |
const radius = 10; | |
ctx.arc(point.x, point.y, viewport.scaleToScreen(radius), 0, Math.PI * 2, false); | |
ctx.fill(); | |
} | |
} | |
requestAnimationFrame(animate); | |
} | |
animate(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment