From 496169146af1093a4cf7bcae0772d0c93bc64208 Mon Sep 17 00:00:00 2001 From: Quozul <30729291+Quozul@users.noreply.github.com> Date: Thu, 16 May 2024 09:22:59 +0200 Subject: [PATCH] move events to class --- src/listenMouseMove.ts | 75 -------------------- src/main.ts | 152 +++++++++++++++++++++++++++++------------ 2 files changed, 109 insertions(+), 118 deletions(-) delete mode 100644 src/listenMouseMove.ts diff --git a/src/listenMouseMove.ts b/src/listenMouseMove.ts deleted file mode 100644 index 1feece5..0000000 --- a/src/listenMouseMove.ts +++ /dev/null @@ -1,75 +0,0 @@ -export function listenMouseMove( - element: HTMLElement, - callback: (deltaX: number, deltaY: number) => void, - zoomCallback: (x: number, y: number, zoomLevel: number) => void, -) { - const evCache: PointerEvent[] = []; - let previousDistance = -1; - - const calculateTouchDistance = () => { - 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 handleMove = (event: PointerEvent) => { - event.stopPropagation(); - event.preventDefault(); - - const index = evCache.findIndex((cachedEv) => cachedEv.pointerId === event.pointerId); - if (index === -1) return; - const previousPointerEvent = evCache[index]; - - if (evCache.length === 2) { - // Calculate the distance between the two pointers - const currentDistance = calculateTouchDistance(); - const zoomLevel = currentDistance / previousDistance; - - const mouseX = event.clientX - element.clientLeft; - const mouseY = event.clientY - element.clientTop; - - zoomCallback(mouseX, mouseY, zoomLevel); - previousDistance = currentDistance; - } - - const offsetX = (event.clientX - previousPointerEvent.clientX) / evCache.length; - const offsetY = (event.clientY - previousPointerEvent.clientY) / evCache.length; - callback(offsetX, offsetY); - - evCache[index] = event; - }; - - const handleStart = (event: PointerEvent) => { - handleMove(event); - evCache.push(event); - previousDistance = calculateTouchDistance(); - element.setPointerCapture(event.pointerId); - }; - - const handleEnd = (event: PointerEvent) => { - const index = evCache.findIndex((cachedEv) => cachedEv.pointerId === event.pointerId); - 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; - - zoomCallback(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); -} diff --git a/src/main.ts b/src/main.ts index c339b7a..cfe7884 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1,5 +1,4 @@ import { loadImagePromise } from "./loadImage.ts"; -import { listenMouseMove } from "./listenMouseMove.ts"; class CanvasImage extends HTMLElement { static observedAttributes = ["src"]; @@ -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(); @@ -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); @@ -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() { @@ -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; @@ -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, @@ -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; @@ -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));