Skip to content

Commit

Permalink
move events to class
Browse files Browse the repository at this point in the history
  • Loading branch information
Quozul committed May 15, 2024
1 parent 4e904ac commit 562d86a
Show file tree
Hide file tree
Showing 3 changed files with 110 additions and 119 deletions.
2 changes: 1 addition & 1 deletion index.html
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
<link href="/style.css" rel="stylesheet" type="text/css">
</head>
<body>
<canvas-image src="/canvas-image/image.jpg"></canvas-image>
<canvas-image src="http://localhost:8001/api/image/5fd5eab07a90bbc827c95564faf1cf3699c1504527543121fe15b1fbaf9a441d.jpg"></canvas-image>
<script type="module" src="/src/main.ts"></script>
</body>
</html>
75 changes: 0 additions & 75 deletions src/listenMouseMove.ts

This file was deleted.

152 changes: 109 additions & 43 deletions src/main.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { loadImagePromise } from "./loadImage.ts";
import { listenMouseMove } from "./listenMouseMove.ts";

class CanvasImage extends HTMLElement {
static observedAttributes = ["src"];
Expand All @@ -13,6 +12,10 @@ class CanvasImage extends HTMLElement {
private displayWidth: number = 0;
private displayHeight: number = 0;
private zoomFactor: number = 1;
private evCache: PointerEvent[] = [];
private previousDistance = -1;
private maxZoom: number = 1;
private minZoom: number = 1;

constructor() {
super();
Expand All @@ -21,17 +24,9 @@ class CanvasImage extends HTMLElement {
connectedCallback() {
const shadow = this.attachShadow({ mode: "open" });
const style = document.createElement("style");
style.textContent = `
canvas {
width: 100%;
height: 100%;
touch-action: none;
box-sizing: border-box;
}
`;
style.textContent = "canvas{width:100%;height:100%;touch-action:none;box-sizing:border-box;}";
shadow.appendChild(style);

console.log("Custom element added to page.");
this.canvas = document.createElement("canvas");
this.context = this.canvas.getContext("2d");
shadow.appendChild(this.canvas);
Expand All @@ -44,35 +39,9 @@ class CanvasImage extends HTMLElement {
this.updateCanvasSize();

resizeObserver.observe(this.canvas);

if (this.context === null) return;
this.loadImage();
window.requestAnimationFrame(this.draw);

listenMouseMove(this.canvas, this.fixOffsets.bind(this), (x: number, y: number, zoomLevel: number) => {
if (!this.context || !this.source) return;

const newZoomFactor = this.zoomFactor * zoomLevel;

const newDisplayedWidth = this.source.width * newZoomFactor;
const newDisplayedHeight = this.source.height * newZoomFactor;

const currentWidth = this.displayWidth;
const currentHeight = this.displayHeight;

const additionalWidth = newDisplayedWidth - currentWidth;
const additionalHeight = newDisplayedHeight - currentHeight;

this.zoomFactor = newZoomFactor;
this.calculateImageZoom();

const imageX = x - this.offsetX;
const imageY = y - this.offsetY;

const centerPercentX = imageX / currentWidth;
const centerPercentY = imageY / currentHeight;
this.fixOffsets(-additionalWidth * centerPercentX, -additionalHeight * centerPercentY);
});
this.initEvents(this.canvas);
}

disconnectedCallback() {
Expand All @@ -88,20 +57,75 @@ class CanvasImage extends HTMLElement {
}
}

private fixOffsets(x: number, y: number) {
if (!this.context) return;
this.offsetX = Math.max(-this.displayWidth + 10, Math.min(this.offsetX + x, this.context.canvas.width - 10));
this.offsetY = Math.max(-this.displayHeight + 10, Math.min(this.offsetY + y, this.context.canvas.height - 10));
private initEvents(element: HTMLCanvasElement) {
const handleMove = (event: PointerEvent) => {
event.stopPropagation();
event.preventDefault();

const index = this.evCache.findIndex((cachedEv) => cachedEv.pointerId === event.pointerId);
if (index === -1) return;

if (this.evCache.length === 2) {
const currentDistance = calculateTouchDistance(this.evCache);
const zoomLevel = currentDistance / this.previousDistance;
this.handleZoom(event.clientX - element.clientLeft, event.clientY - element.clientTop, zoomLevel);
this.previousDistance = currentDistance;
}

const previousPointerEvent = this.evCache[index];
const offsetX = (event.clientX - previousPointerEvent.clientX) / this.evCache.length;
const offsetY = (event.clientY - previousPointerEvent.clientY) / this.evCache.length;
this.fixOffsets(offsetX, offsetY);

this.evCache[index] = event;
};

const handleStart = (event: PointerEvent) => {
handleMove(event);
this.evCache.push(event);
this.previousDistance = calculateTouchDistance(this.evCache);
element.setPointerCapture(event.pointerId);
};

const handleEnd = (event: PointerEvent) => {
const index = this.evCache.findIndex((cachedEv) => cachedEv.pointerId === event.pointerId);
if (index === -1) return;
this.evCache.splice(index, 1);
element.releasePointerCapture(event.pointerId);
};

const handleWheel = (event: WheelEvent) => {
const delta = event.deltaY;
const zoomLevel = delta > 0 ? 0.9 : 1.1;

const mouseX = event.clientX - element.clientLeft;
const mouseY = event.clientY - element.clientTop;

this.handleZoom(mouseX, mouseY, zoomLevel);
};

element.addEventListener("pointerdown", handleStart);
element.addEventListener("pointermove", handleMove);
element.addEventListener("pointerup", handleEnd);
element.addEventListener("pointercancel", handleEnd);
element.addEventListener("pointerout", handleEnd);
element.addEventListener("pointerleave", handleEnd);
element.addEventListener("wheel", handleWheel);
}

private calculateImageZoom(forceCenter: boolean = false) {
if (!this.context || !this.source) return;

if (forceCenter) {
this.zoomFactor = Math.min(
this.minZoom = this.zoomFactor = Math.min(
this.context.canvas.width / this.source.width,
this.context.canvas.height / this.source.height,
);

this.maxZoom = Math.max(
this.source.width / this.context.canvas.width,
this.source.height / this.context.canvas.height,
);
}

this.displayWidth = this.source.width * this.zoomFactor;
Expand All @@ -119,6 +143,7 @@ class CanvasImage extends HTMLElement {
if (!this.context || !this.source) return;
this.context.clearRect(0, 0, this.context.canvas.width, this.context.canvas.height);

this.context.imageSmoothingEnabled = false;
this.context.drawImage(
this.source,
0,
Expand All @@ -132,6 +157,31 @@ class CanvasImage extends HTMLElement {
);
};

private handleZoom(x: number, y: number, zoomLevel: number) {
if (!this.source) return;

const newZoomFactor = between(this.zoomFactor * zoomLevel, this.minZoom, this.maxZoom);

const newDisplayedWidth = this.source.width * newZoomFactor;
const newDisplayedHeight = this.source.height * newZoomFactor;

const currentWidth = this.displayWidth;
const currentHeight = this.displayHeight;

const additionalWidth = newDisplayedWidth - currentWidth;
const additionalHeight = newDisplayedHeight - currentHeight;

this.zoomFactor = newZoomFactor;
this.calculateImageZoom();

const imageX = x - this.offsetX;
const imageY = y - this.offsetY;

const centerPercentX = imageX / currentWidth;
const centerPercentY = imageY / currentHeight;
this.fixOffsets(-additionalWidth * centerPercentX, -additionalHeight * centerPercentY);
}

private async loadImage(src: string | null = this.getAttribute("src")) {
if (!src) {
this.source = null;
Expand All @@ -141,12 +191,28 @@ class CanvasImage extends HTMLElement {
this.calculateImageZoom(true);
}

private fixOffsets(x: number, y: number) {
if (!this.context) return;
this.offsetX = between(this.offsetX + x, -this.displayWidth + 10, this.context.canvas.width - 10);
this.offsetY = between(this.offsetY + y, -this.displayHeight + 10, this.context.canvas.height - 10);
}

private updateCanvasSize() {
if (!this.canvas) return;

this.canvas.width = this.canvas.clientWidth;
this.canvas.height = this.canvas.clientHeight;
}
}

customElements.define("canvas-image", CanvasImage);

const calculateTouchDistance = (evCache: PointerEvent[]) => {
if (evCache.length === 2) {
const touch1 = evCache[0];
const touch2 = evCache[1];
return Math.sqrt(Math.pow(touch2.clientX - touch1.clientX, 2) + Math.pow(touch2.clientY - touch1.clientY, 2));
}
return 0;
};

const between = (value: number, min: number, max: number): number => Math.max(min, Math.min(value, max));

0 comments on commit 562d86a

Please sign in to comment.