new version
This commit is contained in:
parent
86c0f1419a
commit
5c0d6e4a2f
@ -1,5 +1,5 @@
|
||||
import { convertHex } from '../common/convert';
|
||||
import { clamp } from '../common/helper';
|
||||
import { clamp, debounce } from '../common/helper';
|
||||
import { Placement } from '../common/types/canvas';
|
||||
import { IcyNetUser } from '../server/types/user';
|
||||
import { Picker } from './picker';
|
||||
@ -15,6 +15,7 @@ export class ViewCanvas {
|
||||
private _wrapper = $('<div class="canvas__wrapper">');
|
||||
private _container = $('<div class="canvas__container">');
|
||||
private _cursor = $('<div class="canvas__cursor">');
|
||||
private _coods = $('<div class="canvas__coordinates">');
|
||||
private _size = 1000;
|
||||
|
||||
private _viewWidth = 0;
|
||||
@ -22,7 +23,7 @@ export class ViewCanvas {
|
||||
|
||||
private _posx = 0;
|
||||
private _posy = 0;
|
||||
private _zoom = this._size;
|
||||
private _zoom = 1;
|
||||
|
||||
private _mousex = 0;
|
||||
private _mousey = 0;
|
||||
@ -42,10 +43,8 @@ export class ViewCanvas {
|
||||
constructor() {}
|
||||
|
||||
public moveCanvas(): void {
|
||||
const pixelSize = this._size / this._zoom;
|
||||
this._canvas.style.top = `${this._posy}px`;
|
||||
this._canvas.style.left = `${this._posx}px`;
|
||||
this._canvas.style.transform = `scale(${pixelSize})`;
|
||||
this._canvas.style.transform = `scale(${this._zoom})`;
|
||||
this._container.style.transform = `translate(${this._posx}px, ${this._posy}px)`;
|
||||
}
|
||||
|
||||
public center(): void {
|
||||
@ -60,13 +59,11 @@ export class ViewCanvas {
|
||||
|
||||
public moveCursor(): void {
|
||||
// Zoom multiplier
|
||||
const pixelSize = this._size / this._zoom;
|
||||
// Apparent size of the canvas after scaling it
|
||||
const realSize = pixelSize * this._size;
|
||||
const realSize = this._zoom * this._size;
|
||||
// The difference between the real canvas size and apparent size
|
||||
const scale = this._size / 2 - realSize / 2;
|
||||
const screenX = this._posx + scale;
|
||||
const screenY = this._posy + scale;
|
||||
const screenX = this._posx;
|
||||
const screenY = this._posy;
|
||||
|
||||
// Position of the on-screen cursor, snapped
|
||||
// Relative to top left of screen
|
||||
@ -78,27 +75,34 @@ export class ViewCanvas {
|
||||
);
|
||||
|
||||
// Position of the cursor on the canvas
|
||||
this._relcursorx = Math.floor((this._cursorx - screenX) / pixelSize);
|
||||
this._relcursory = Math.floor((this._cursory - screenY) / pixelSize);
|
||||
this._relcursorx = Math.floor((this._cursorx - screenX) / this._zoom);
|
||||
this._relcursory = Math.floor((this._cursory - screenY) / this._zoom);
|
||||
|
||||
this._screencursorx = this._relcursorx * pixelSize + screenX;
|
||||
this._screencursory = this._relcursory * pixelSize + screenY;
|
||||
this._screencursorx = this._relcursorx * this._zoom + screenX;
|
||||
this._screencursory = this._relcursory * this._zoom + screenY;
|
||||
|
||||
this._cursor.style.top = `${this._screencursory + pixelSize / 2}px`;
|
||||
this._cursor.style.left = `${this._screencursorx + pixelSize / 2}px`;
|
||||
this._cursor.style.transform = `scale(${pixelSize})`;
|
||||
this._cursor.style.top = `${this._screencursory + this._zoom / 2}px`;
|
||||
this._cursor.style.left = `${this._screencursorx + this._zoom / 2}px`;
|
||||
this._cursor.style.transform = `scale(${this._zoom})`;
|
||||
|
||||
this._coods.innerText = `(${this._relcursorx}, ${
|
||||
this._relcursory
|
||||
}) ${this._zoom.toFixed(2)}x`;
|
||||
|
||||
this._updateURL();
|
||||
}
|
||||
|
||||
public initialize(): void {
|
||||
this.picker.initialize();
|
||||
|
||||
this._wrapper.append(this._coods);
|
||||
this._container.append(this._canvas);
|
||||
this._container.append(this._cursor);
|
||||
this._wrapper.append(this._cursor);
|
||||
this._wrapper.append(this._container);
|
||||
this._wrapper.append(this.picker.element);
|
||||
document.body.append(this._wrapper);
|
||||
|
||||
this._container.addEventListener('pointermove', (ev: MouseEvent) => {
|
||||
this._wrapper.addEventListener('pointermove', (ev: MouseEvent) => {
|
||||
const currentX = this._mousex;
|
||||
const currentY = this._mousey;
|
||||
|
||||
@ -109,39 +113,69 @@ export class ViewCanvas {
|
||||
const offsetY = currentY - this._mousey;
|
||||
|
||||
if (this._dragging) {
|
||||
this._posx -= offsetX;
|
||||
this._posy -= offsetY;
|
||||
const realSize = this._zoom * this._size;
|
||||
this._posx = clamp(
|
||||
this._posx - offsetX,
|
||||
this._cursorx - realSize,
|
||||
this._cursorx,
|
||||
);
|
||||
this._posy = clamp(
|
||||
this._posy - offsetY,
|
||||
this._cursory - realSize,
|
||||
this._cursory,
|
||||
);
|
||||
|
||||
this.moveCanvas();
|
||||
this.moveCursor();
|
||||
}
|
||||
});
|
||||
|
||||
this._container.addEventListener('pointerdown', (ev: MouseEvent) => {
|
||||
this._wrapper.addEventListener('mousedown', (ev: MouseEvent) => {
|
||||
this._mousex = ev.clientX;
|
||||
this._mousey = ev.clientY;
|
||||
this._dragging = true;
|
||||
});
|
||||
|
||||
this._container.addEventListener('pointerup', (ev: MouseEvent) => {
|
||||
this._wrapper.addEventListener('mouseup', (ev: MouseEvent) => {
|
||||
this._dragging = false;
|
||||
});
|
||||
|
||||
this._container.addEventListener('pointerleave', (ev: MouseEvent) => {
|
||||
this._wrapper.addEventListener('touchstart', (ev: TouchEvent) => {
|
||||
const touch = ev.touches[0] || ev.changedTouches[0];
|
||||
this._mousex = touch.pageX;
|
||||
this._mousey = touch.pageY;
|
||||
this._dragging = true;
|
||||
});
|
||||
|
||||
this._wrapper.addEventListener('touchend', (ev: TouchEvent) => {
|
||||
this._dragging = false;
|
||||
});
|
||||
|
||||
this._container.addEventListener('wheel', (ev: WheelEvent) => {
|
||||
this._wrapper.addEventListener('pointerleave', (ev: MouseEvent) => {
|
||||
this._dragging = false;
|
||||
});
|
||||
|
||||
this._wrapper.addEventListener('wheel', (ev: WheelEvent) => {
|
||||
ev.preventDefault();
|
||||
|
||||
this._mousex = ev.clientX;
|
||||
this._mousey = ev.clientY;
|
||||
|
||||
const delta = Math.ceil(ev.deltaY / 2) * 2;
|
||||
this._zoom = clamp((this._zoom += delta), 8, this._size);
|
||||
const scaleX = (ev.clientX - this._posx) / this._zoom;
|
||||
const scaleY = (ev.clientY - this._posy) / this._zoom;
|
||||
|
||||
ev.deltaY < 0 ? (this._zoom *= 1.2) : (this._zoom /= 1.2);
|
||||
this._zoom = clamp(this._zoom, 1, 100);
|
||||
|
||||
this._posx = ev.clientX - scaleX * this._zoom;
|
||||
this._posy = ev.clientY - scaleY * this._zoom;
|
||||
|
||||
const realSize = this._zoom * this._size;
|
||||
this._posx = clamp(this._posx, this._cursorx - realSize, this._cursorx);
|
||||
this._posy = clamp(this._posy, this._cursory - realSize, this._cursory);
|
||||
|
||||
this.moveCanvas();
|
||||
this.moveCursor();
|
||||
this.moveCanvas();
|
||||
});
|
||||
|
||||
this.picker.registerOnPlace((color) => {
|
||||
@ -155,8 +189,6 @@ export class ViewCanvas {
|
||||
}
|
||||
});
|
||||
|
||||
this.setView();
|
||||
|
||||
window.addEventListener('resize', () => this.setView());
|
||||
window.addEventListener('keyup', (ev: KeyboardEvent) => {
|
||||
const numeral = parseInt(ev.key, 10);
|
||||
@ -198,7 +230,7 @@ export class ViewCanvas {
|
||||
public setView() {
|
||||
this._viewWidth = document.body.clientWidth;
|
||||
this._viewHeight = document.body.clientHeight;
|
||||
this.center();
|
||||
this._fromURL();
|
||||
}
|
||||
|
||||
public fill(size: number, canvas: number[]) {
|
||||
@ -219,6 +251,8 @@ export class ViewCanvas {
|
||||
}
|
||||
}
|
||||
this._ctx!.putImageData(data, 0, 0);
|
||||
|
||||
this.setView();
|
||||
}
|
||||
|
||||
public setPixel(x: number, y: number, pixel: number) {
|
||||
@ -235,4 +269,53 @@ export class ViewCanvas {
|
||||
this._user = user;
|
||||
this.picker.setLoggedIn(user);
|
||||
}
|
||||
|
||||
private _updateURL = debounce(() => {
|
||||
const urlelements = new URLSearchParams({
|
||||
px: this._relcursorx.toString(),
|
||||
py: this._relcursory.toString(),
|
||||
z: this._zoom.toFixed(2),
|
||||
});
|
||||
window.history.replaceState(
|
||||
null,
|
||||
document.title,
|
||||
`/?${urlelements.toString()}`,
|
||||
);
|
||||
}, 500);
|
||||
|
||||
private _fromURL(): void {
|
||||
const search = window.location.search.substring(1);
|
||||
if (!search?.length) {
|
||||
this.center();
|
||||
return;
|
||||
}
|
||||
|
||||
const obj = new URLSearchParams(search);
|
||||
const move = !!obj.get('px') || !!obj.get('py');
|
||||
|
||||
if (!move) {
|
||||
return this.center();
|
||||
}
|
||||
|
||||
if (obj.get('z')) {
|
||||
this._zoom = clamp(parseFloat(obj.get('z')), 1, 100);
|
||||
}
|
||||
|
||||
if (obj.get('px')) {
|
||||
this._relcursorx = clamp(parseInt(obj.get('px'), 10), 0, this._size);
|
||||
}
|
||||
|
||||
if (obj.get('py')) {
|
||||
this._relcursory = clamp(parseInt(obj.get('py'), 10), 0, this._size);
|
||||
}
|
||||
|
||||
this._cursorx = this._viewWidth / 2;
|
||||
this._cursory = this._viewHeight / 2;
|
||||
|
||||
this._posx = -Math.ceil(this._relcursorx * this._zoom - this._cursorx) - 1;
|
||||
this._posy = -Math.ceil(this._relcursory * this._zoom - this._cursory);
|
||||
|
||||
this.moveCanvas();
|
||||
this.moveCursor();
|
||||
}
|
||||
}
|
||||
|
@ -21,21 +21,31 @@ body {
|
||||
image-rendering: pixelated; /* Awesome future-browsers */
|
||||
-ms-interpolation-mode: nearest-neighbor; /* IE */
|
||||
|
||||
position: absolute;
|
||||
z-index: 1001;
|
||||
transform-origin: center center;
|
||||
transform-origin: left top;
|
||||
|
||||
&__wrapper {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
background-color: rgb(143, 143, 143);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
&__container {
|
||||
position: relative;
|
||||
flex-grow: 1;
|
||||
overflow: hidden;
|
||||
width: 0;
|
||||
height: 0;
|
||||
}
|
||||
|
||||
&__coordinates {
|
||||
position: absolute;
|
||||
top: 1rem;
|
||||
left: 1rem;
|
||||
background-color: rgba(221, 221, 221, 0.404);
|
||||
padding: 0.5rem;
|
||||
border-radius: 5px;
|
||||
z-index: 1008;
|
||||
}
|
||||
|
||||
&__cursor {
|
||||
|
@ -1,3 +1,13 @@
|
||||
export function clamp(x: number, min: number, max: number): number {
|
||||
return Math.min(Math.max(x, min), max);
|
||||
}
|
||||
|
||||
export function debounce(func: Function, timeout = 300) {
|
||||
let timer: any;
|
||||
return (...args: any[]) => {
|
||||
clearTimeout(timer);
|
||||
timer = setTimeout(() => {
|
||||
func.apply(this, args);
|
||||
}, timeout);
|
||||
};
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user