From 29d432e64e0482935ef91dbfed37d4d4cf26c42f Mon Sep 17 00:00:00 2001 From: pacien Date: Sun, 26 Apr 2020 21:51:37 +0200 Subject: viewer/LdZoom: add support for pinch-to-zoom GitHub: closes #106 --- viewer/package-lock.json | 10 +++++++++ viewer/package.json | 2 ++ viewer/src/services/ldzoom.ts | 46 ++++++++++++++++++++++++++++------------- viewer/src/views/MainLayout.vue | 1 + 4 files changed, 45 insertions(+), 14 deletions(-) (limited to 'viewer') diff --git a/viewer/package-lock.json b/viewer/package-lock.json index 1e1fdda..d4a5fb0 100644 --- a/viewer/package-lock.json +++ b/viewer/package-lock.json @@ -1232,6 +1232,11 @@ "@types/node": "*" } }, + "@types/hammerjs": { + "version": "2.0.36", + "resolved": "https://registry.npmjs.org/@types/hammerjs/-/hammerjs-2.0.36.tgz", + "integrity": "sha512-7TUK/k2/QGpEAv/BCwSHlYu3NXZhQ9ZwBYpzr9tjlPIL2C5BeGhH3DmVavRx3ZNyELX5TLC91JTz/cen6AAtIQ==" + }, "@types/json-schema": { "version": "7.0.4", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.4.tgz", @@ -6487,6 +6492,11 @@ "pify": "^4.0.1" } }, + "hammerjs": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/hammerjs/-/hammerjs-2.0.8.tgz", + "integrity": "sha1-BO93hiz/K7edMPdpIJWTAiK/YPE=" + }, "handle-thing": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/handle-thing/-/handle-thing-2.0.1.tgz", diff --git a/viewer/package.json b/viewer/package.json index 8f7de83..bc63dfd 100644 --- a/viewer/package.json +++ b/viewer/package.json @@ -13,8 +13,10 @@ "@fortawesome/fontawesome-svg-core": "^1.2.28", "@fortawesome/free-solid-svg-icons": "^5.13.0", "@fortawesome/vue-fontawesome": "^0.1.9", + "@types/hammerjs": "^2.0.36", "buefy": "^0.8.15", "core-js": "^3.6.4", + "hammerjs": "^2.0.8", "resize-observer-polyfill": "^1.5.1", "v-lazy-image": "^1.4.0", "vue": "^2.6.11", diff --git a/viewer/src/services/ldzoom.ts b/viewer/src/services/ldzoom.ts index 61b5dc6..27debb5 100644 --- a/viewer/src/services/ldzoom.ts +++ b/viewer/src/services/ldzoom.ts @@ -19,25 +19,26 @@ // polyfill still required for IE and Safari, see https://caniuse.com/#feat=resizeobserver import ResizeObserver from 'resize-observer-polyfill'; +import "hammerjs"; /** - * Mousewheel picture zoom helper. + * Mousewheel and pinch zoom handler. */ export default class LdZoom { readonly containerElement: HTMLDivElement; readonly imageElement: HTMLImageElement; readonly maxScaleFactor: number; - readonly zoomSpeed: number; + readonly scrollZoomSpeed: number; scaleFactor: number = 0.0; constructor( containerElement: HTMLDivElement, imageElement: HTMLImageElement, - maxScaleFactor: number, zoomSpeed: number + maxScaleFactor: number, scrollZoomSpeed: number ) { this.containerElement = containerElement; this.imageElement = imageElement; this.maxScaleFactor = maxScaleFactor; - this.zoomSpeed = zoomSpeed; + this.scrollZoomSpeed = scrollZoomSpeed; } /** @@ -52,10 +53,30 @@ export default class LdZoom { this.containerElement.addEventListener('wheel', wheelEvent => { wheelEvent.preventDefault(); - this.zoom(wheelEvent); + const zoomDelta = -Math.sign(wheelEvent.deltaY) * this.scrollZoomSpeed; + this.zoom(wheelEvent.offsetX, wheelEvent.offsetY, zoomDelta); }); - // TODO: handle pinch-to-zoom. + const pinchListener = new Hammer(this.containerElement); + pinchListener.get('pinch').set({enable: true}); + this.installPinchHandler(pinchListener); + } + + private installPinchHandler(pinchListener: HammerManager) { + let lastScaleFactor = 0.0; + + pinchListener.on('pinchstart', (pinchEvent: HammerInput) => { + lastScaleFactor = pinchEvent.scale; + }); + + pinchListener.on('pinchmove', (pinchEvent: HammerInput) => { + // FIXME: pinchEvent.center isn't always well-centered + const focusX = pinchEvent.center.x + this.containerElement.scrollLeft; + const focusY = pinchEvent.center.y + this.containerElement.scrollTop; + const zoomDelta = pinchEvent.scale - lastScaleFactor; + lastScaleFactor = pinchEvent.scale; + this.zoom(focusX, focusY, zoomDelta); + }); } /** @@ -69,15 +90,12 @@ export default class LdZoom { this.imageElement.style.marginTop = `${marginTop}px`; } - private zoom(wheelEvent: WheelEvent) { - const ratioX = wheelEvent.offsetX / this.imageElement.clientWidth; - const ratioY = wheelEvent.offsetY / this.imageElement.clientHeight; - - const zoomDelta = -Math.sign(wheelEvent.deltaY) * this.zoomSpeed; + private zoom(focusX: number, focusY: number, zoomDelta: number) { + const ratioX = focusX / this.imageElement.clientWidth; + const ratioY = focusY / this.imageElement.clientHeight; this.setImageScale(Math.min(this.scaleFactor + zoomDelta, this.maxScaleFactor)); - - this.containerElement.scrollLeft -= wheelEvent.offsetX - ratioX * this.imageElement.clientWidth; - this.containerElement.scrollTop -= wheelEvent.offsetY - ratioY * this.imageElement.clientHeight; + this.containerElement.scrollLeft -= focusX - ratioX * this.imageElement.clientWidth; + this.containerElement.scrollTop -= focusY - ratioY * this.imageElement.clientHeight; } private setImageScale(newScaleFactor: number) { diff --git a/viewer/src/views/MainLayout.vue b/viewer/src/views/MainLayout.vue index 6e707e6..c09e99a 100644 --- a/viewer/src/views/MainLayout.vue +++ b/viewer/src/views/MainLayout.vue @@ -87,6 +87,7 @@ body, html { height: 100%; overflow: hidden; + touch-action: none; background-color: $content-bgcolor; --layout-top: #{$layout-top}; --layout-left: #{$layout-left}; -- cgit v1.2.3