diff options
author | sanine <sanine.not@pm.me> | 2022-05-29 17:11:48 -0500 |
---|---|---|
committer | sanine <sanine.not@pm.me> | 2022-05-29 17:11:48 -0500 |
commit | bc6f5873f17ae10f42f87825ac64b136cfee3d72 (patch) | |
tree | f433186af60125c45ba63f545a556afc48c24e23 /src/Canvas.js | |
parent | 62ccd5f48cd1f49effaa76ef01eb45072a0a42df (diff) |
add zooming and panning
Diffstat (limited to 'src/Canvas.js')
-rw-r--r-- | src/Canvas.js | 95 |
1 files changed, 81 insertions, 14 deletions
diff --git a/src/Canvas.js b/src/Canvas.js index 590034a..bd39f47 100644 --- a/src/Canvas.js +++ b/src/Canvas.js @@ -1,46 +1,113 @@ +import { clamp } from './modules/Util.js'; + class Canvas { constructor(rootId) { const root = document.getElementById(rootId); this.element = document.createElement('canvas'); this.context = this.element.getContext('2d'); - this.fillWindow(); - window.addEventListener('resize', () => this.fillWindow()); - root.appendChild(this.element); + /* state variables */ + this.scale = 1; + this.zoom = 1; + const ZOOM_SPEED = 1.2; + + this.mousePos = { x: 0, y: 0 }; + this.pan = { x: 0, y: 0 }; + this.panning = false; - this.ondraw = null; + /* callbacks */ + this.onDraw = null; + this.onMouseMove = null; - /* automatically compute drawing-space mouse coordinates */ - this.mousePos = { x: 0, y: 0 }; - this.onmousemove = null; + /* register event listeners */ + + /* mouse movement */ this.element.addEventListener('mousemove', e => { const matrix = this.context.getTransform(); matrix.invertSelf(); - const screenX = e.offsetX; const screenY = e.offsetY; - this.mousePos.x = matrix.a*screenX + matrix.b*screenY; - this.mousePos.y = matrix.c*screenX + matrix.d*screenY; - if (this.onmousemove) this.onmousemove(this.mousePos); + /* compute drawing-space position */ + const x = matrix.a*screenX + matrix.b*screenY + matrix.e; + const y = matrix.c*screenX + matrix.d*screenY + matrix.f; + + /* compute movement */ + const dx = e.movementX / (this.zoom * this.scale); + const dy = e.movementY / (this.zoom * this.scale); + + /* pan? */ + if (this.panning) { + this.pan.x += dx; + this.pan.y += dy; + this.setTransform(); + } + + this.mousePos.x = x; + this.mousePos.y = y; + + if (this.onMouseMove) this.onMouseMove(this.mousePos); }); + /* clicking */ + this.element.addEventListener('mousedown', e => { + if (e.button === 1) this.panning = true; + }); + this.element.addEventListener('mouseup', e => { + if (e.button === 1) this.panning = false; + }); + + /* mouse leave */ + this.element.addEventListener('mouseleave', e => { + this.panning = false; + }); + + /* mouse wheel */ + this.element.addEventListener('wheel', e => { + if (this.panning) return; // don't zoom and pan simultaneously + const delta = e.deltaY < 0 ? ZOOM_SPEED : 1/ZOOM_SPEED; + this.zoom *= delta; + this.zoom = clamp(this.zoom, 1, Infinity); + this.setTransform(); + this.draw(); + }); + + /* finalize setup */ + this.fillWindow(); + window.addEventListener('resize', () => this.fillWindow()); + root.appendChild(this.element); + } + + setTransform() { + /* clamp pan */ + console.log('---'); + console.log(this.pan); + const xMax = this.element.width / (this.zoom * this.scale) - 1; + const yMax = this.element.height / (this.zoom * this.scale) - 1; + this.pan.x = clamp(this.pan.x, xMax, 0); + this.pan.y = clamp(this.pan.y, yMax, 0); + console.log(xMax, yMax); + console.log(this.pan); + + this.context.setTransform(1, 0, 0, 1, 0, 0); + this.context.scale(this.zoom * this.scale, this.zoom * this.scale); + this.context.translate(this.pan.x, this.pan.y); } fillWindow() { const width = window.innerWidth; const height = window.innerHeight; - const scale = Math.max(width, height); + this.scale = Math.max(width, height); this.element.width = width; this.element.height = height; - this.context.scale(scale, scale); + this.setTransform(); this.draw(); } draw() { this.context.clearRect(0, 0, 1, 1); - if (this.ondraw) this.ondraw(this.context); + if (this.onDraw) this.onDraw(this.context); } } |