new version

This commit is contained in:
Evert Prants 2022-04-03 15:25:29 +03:00
parent 86c0f1419a
commit 5c0d6e4a2f
Signed by: evert
GPG Key ID: 1688DA83D222D0B5
3 changed files with 139 additions and 36 deletions

View File

@ -1,5 +1,5 @@
import { convertHex } from '../common/convert'; import { convertHex } from '../common/convert';
import { clamp } from '../common/helper'; import { clamp, debounce } from '../common/helper';
import { Placement } from '../common/types/canvas'; import { Placement } from '../common/types/canvas';
import { IcyNetUser } from '../server/types/user'; import { IcyNetUser } from '../server/types/user';
import { Picker } from './picker'; import { Picker } from './picker';
@ -15,6 +15,7 @@ export class ViewCanvas {
private _wrapper = $('<div class="canvas__wrapper">'); private _wrapper = $('<div class="canvas__wrapper">');
private _container = $('<div class="canvas__container">'); private _container = $('<div class="canvas__container">');
private _cursor = $('<div class="canvas__cursor">'); private _cursor = $('<div class="canvas__cursor">');
private _coods = $('<div class="canvas__coordinates">');
private _size = 1000; private _size = 1000;
private _viewWidth = 0; private _viewWidth = 0;
@ -22,7 +23,7 @@ export class ViewCanvas {
private _posx = 0; private _posx = 0;
private _posy = 0; private _posy = 0;
private _zoom = this._size; private _zoom = 1;
private _mousex = 0; private _mousex = 0;
private _mousey = 0; private _mousey = 0;
@ -42,10 +43,8 @@ export class ViewCanvas {
constructor() {} constructor() {}
public moveCanvas(): void { public moveCanvas(): void {
const pixelSize = this._size / this._zoom; this._canvas.style.transform = `scale(${this._zoom})`;
this._canvas.style.top = `${this._posy}px`; this._container.style.transform = `translate(${this._posx}px, ${this._posy}px)`;
this._canvas.style.left = `${this._posx}px`;
this._canvas.style.transform = `scale(${pixelSize})`;
} }
public center(): void { public center(): void {
@ -60,13 +59,11 @@ export class ViewCanvas {
public moveCursor(): void { public moveCursor(): void {
// Zoom multiplier // Zoom multiplier
const pixelSize = this._size / this._zoom;
// Apparent size of the canvas after scaling it // 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 // The difference between the real canvas size and apparent size
const scale = this._size / 2 - realSize / 2; const screenX = this._posx;
const screenX = this._posx + scale; const screenY = this._posy;
const screenY = this._posy + scale;
// Position of the on-screen cursor, snapped // Position of the on-screen cursor, snapped
// Relative to top left of screen // Relative to top left of screen
@ -78,27 +75,34 @@ export class ViewCanvas {
); );
// Position of the cursor on the canvas // Position of the cursor on the canvas
this._relcursorx = Math.floor((this._cursorx - screenX) / pixelSize); this._relcursorx = Math.floor((this._cursorx - screenX) / this._zoom);
this._relcursory = Math.floor((this._cursory - screenY) / pixelSize); this._relcursory = Math.floor((this._cursory - screenY) / this._zoom);
this._screencursorx = this._relcursorx * pixelSize + screenX; this._screencursorx = this._relcursorx * this._zoom + screenX;
this._screencursory = this._relcursory * pixelSize + screenY; this._screencursory = this._relcursory * this._zoom + screenY;
this._cursor.style.top = `${this._screencursory + pixelSize / 2}px`; this._cursor.style.top = `${this._screencursory + this._zoom / 2}px`;
this._cursor.style.left = `${this._screencursorx + pixelSize / 2}px`; this._cursor.style.left = `${this._screencursorx + this._zoom / 2}px`;
this._cursor.style.transform = `scale(${pixelSize})`; this._cursor.style.transform = `scale(${this._zoom})`;
this._coods.innerText = `(${this._relcursorx}, ${
this._relcursory
}) ${this._zoom.toFixed(2)}x`;
this._updateURL();
} }
public initialize(): void { public initialize(): void {
this.picker.initialize(); this.picker.initialize();
this._wrapper.append(this._coods);
this._container.append(this._canvas); this._container.append(this._canvas);
this._container.append(this._cursor); this._wrapper.append(this._cursor);
this._wrapper.append(this._container); this._wrapper.append(this._container);
this._wrapper.append(this.picker.element); this._wrapper.append(this.picker.element);
document.body.append(this._wrapper); document.body.append(this._wrapper);
this._container.addEventListener('pointermove', (ev: MouseEvent) => { this._wrapper.addEventListener('pointermove', (ev: MouseEvent) => {
const currentX = this._mousex; const currentX = this._mousex;
const currentY = this._mousey; const currentY = this._mousey;
@ -109,39 +113,69 @@ export class ViewCanvas {
const offsetY = currentY - this._mousey; const offsetY = currentY - this._mousey;
if (this._dragging) { if (this._dragging) {
this._posx -= offsetX; const realSize = this._zoom * this._size;
this._posy -= offsetY; 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.moveCanvas();
this.moveCursor(); this.moveCursor();
} }
}); });
this._container.addEventListener('pointerdown', (ev: MouseEvent) => { this._wrapper.addEventListener('mousedown', (ev: MouseEvent) => {
this._mousex = ev.clientX; this._mousex = ev.clientX;
this._mousey = ev.clientY; this._mousey = ev.clientY;
this._dragging = true; this._dragging = true;
}); });
this._container.addEventListener('pointerup', (ev: MouseEvent) => { this._wrapper.addEventListener('mouseup', (ev: MouseEvent) => {
this._dragging = false; 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._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(); ev.preventDefault();
this._mousex = ev.clientX; this._mousex = ev.clientX;
this._mousey = ev.clientY; this._mousey = ev.clientY;
const delta = Math.ceil(ev.deltaY / 2) * 2; const scaleX = (ev.clientX - this._posx) / this._zoom;
this._zoom = clamp((this._zoom += delta), 8, this._size); 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.moveCursor();
this.moveCanvas();
}); });
this.picker.registerOnPlace((color) => { this.picker.registerOnPlace((color) => {
@ -155,8 +189,6 @@ export class ViewCanvas {
} }
}); });
this.setView();
window.addEventListener('resize', () => this.setView()); window.addEventListener('resize', () => this.setView());
window.addEventListener('keyup', (ev: KeyboardEvent) => { window.addEventListener('keyup', (ev: KeyboardEvent) => {
const numeral = parseInt(ev.key, 10); const numeral = parseInt(ev.key, 10);
@ -198,7 +230,7 @@ export class ViewCanvas {
public setView() { public setView() {
this._viewWidth = document.body.clientWidth; this._viewWidth = document.body.clientWidth;
this._viewHeight = document.body.clientHeight; this._viewHeight = document.body.clientHeight;
this.center(); this._fromURL();
} }
public fill(size: number, canvas: number[]) { public fill(size: number, canvas: number[]) {
@ -219,6 +251,8 @@ export class ViewCanvas {
} }
} }
this._ctx!.putImageData(data, 0, 0); this._ctx!.putImageData(data, 0, 0);
this.setView();
} }
public setPixel(x: number, y: number, pixel: number) { public setPixel(x: number, y: number, pixel: number) {
@ -235,4 +269,53 @@ export class ViewCanvas {
this._user = user; this._user = user;
this.picker.setLoggedIn(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();
}
} }

View File

@ -21,21 +21,31 @@ body {
image-rendering: pixelated; /* Awesome future-browsers */ image-rendering: pixelated; /* Awesome future-browsers */
-ms-interpolation-mode: nearest-neighbor; /* IE */ -ms-interpolation-mode: nearest-neighbor; /* IE */
position: absolute;
z-index: 1001; z-index: 1001;
transform-origin: center center; transform-origin: left top;
&__wrapper { &__wrapper {
width: 100%; width: 100%;
height: 100%; height: 100%;
display: flex; display: flex;
background-color: rgb(143, 143, 143); background-color: rgb(143, 143, 143);
overflow: hidden;
} }
&__container { &__container {
position: relative; position: relative;
flex-grow: 1; width: 0;
overflow: hidden; 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 { &__cursor {

View File

@ -1,3 +1,13 @@
export function clamp(x: number, min: number, max: number): number { export function clamp(x: number, min: number, max: number): number {
return Math.min(Math.max(x, min), max); 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);
};
}