1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
|
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');
/* 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;
/* callbacks */
this.onDraw = null;
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;
/* 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;
this.scale = Math.max(width, height);
this.element.width = width; this.element.height = height;
this.setTransform();
this.draw();
}
draw() {
this.context.clearRect(0, 0, 1, 1);
if (this.onDraw) this.onDraw(this.context);
}
}
export default Canvas;
|