aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorpacien2020-04-26 21:51:37 +0200
committerpacien2020-04-26 21:55:10 +0200
commit29d432e64e0482935ef91dbfed37d4d4cf26c42f (patch)
treea9ffc21c3b8385f405bcf6638abd3b017b0d0d63
parentf8a1763c3bee0e236c86ba9f6b46aceb212dea10 (diff)
downloadldgallery-29d432e64e0482935ef91dbfed37d4d4cf26c42f.tar.gz
viewer/LdZoom: add support for pinch-to-zoom
GitHub: closes #106
-rw-r--r--readme.md1
-rw-r--r--viewer/package-lock.json10
-rw-r--r--viewer/package.json2
-rw-r--r--viewer/src/services/ldzoom.ts46
-rw-r--r--viewer/src/views/MainLayout.vue1
5 files changed, 46 insertions, 14 deletions
diff --git a/readme.md b/readme.md
index c3b8cb8..dc370a2 100644
--- a/readme.md
+++ b/readme.md
@@ -55,6 +55,7 @@ Builds of this software embed and make use of the following libraries:
55 * buefy, licensed under the MIT License 55 * buefy, licensed under the MIT License
56 * core-js, licensed under the MIT License 56 * core-js, licensed under the MIT License
57 * resize-observer-polyfill, licensed under the MIT License 57 * resize-observer-polyfill, licensed under the MIT License
58 * hammerjs, licensed under the MIT License
58 * v-lazy-image, licensed under the MIT License 59 * v-lazy-image, licensed under the MIT License
59 * vue, licensed under the MIT License 60 * vue, licensed under the MIT License
60 * vue-class-component, licensed under the MIT License 61 * vue-class-component, licensed under the MIT License
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 @@
1232 "@types/node": "*" 1232 "@types/node": "*"
1233 } 1233 }
1234 }, 1234 },
1235 "@types/hammerjs": {
1236 "version": "2.0.36",
1237 "resolved": "https://registry.npmjs.org/@types/hammerjs/-/hammerjs-2.0.36.tgz",
1238 "integrity": "sha512-7TUK/k2/QGpEAv/BCwSHlYu3NXZhQ9ZwBYpzr9tjlPIL2C5BeGhH3DmVavRx3ZNyELX5TLC91JTz/cen6AAtIQ=="
1239 },
1235 "@types/json-schema": { 1240 "@types/json-schema": {
1236 "version": "7.0.4", 1241 "version": "7.0.4",
1237 "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.4.tgz", 1242 "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.4.tgz",
@@ -6487,6 +6492,11 @@
6487 "pify": "^4.0.1" 6492 "pify": "^4.0.1"
6488 } 6493 }
6489 }, 6494 },
6495 "hammerjs": {
6496 "version": "2.0.8",
6497 "resolved": "https://registry.npmjs.org/hammerjs/-/hammerjs-2.0.8.tgz",
6498 "integrity": "sha1-BO93hiz/K7edMPdpIJWTAiK/YPE="
6499 },
6490 "handle-thing": { 6500 "handle-thing": {
6491 "version": "2.0.1", 6501 "version": "2.0.1",
6492 "resolved": "https://registry.npmjs.org/handle-thing/-/handle-thing-2.0.1.tgz", 6502 "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 @@
13 "@fortawesome/fontawesome-svg-core": "^1.2.28", 13 "@fortawesome/fontawesome-svg-core": "^1.2.28",
14 "@fortawesome/free-solid-svg-icons": "^5.13.0", 14 "@fortawesome/free-solid-svg-icons": "^5.13.0",
15 "@fortawesome/vue-fontawesome": "^0.1.9", 15 "@fortawesome/vue-fontawesome": "^0.1.9",
16 "@types/hammerjs": "^2.0.36",
16 "buefy": "^0.8.15", 17 "buefy": "^0.8.15",
17 "core-js": "^3.6.4", 18 "core-js": "^3.6.4",
19 "hammerjs": "^2.0.8",
18 "resize-observer-polyfill": "^1.5.1", 20 "resize-observer-polyfill": "^1.5.1",
19 "v-lazy-image": "^1.4.0", 21 "v-lazy-image": "^1.4.0",
20 "vue": "^2.6.11", 22 "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 @@
19 19
20// polyfill still required for IE and Safari, see https://caniuse.com/#feat=resizeobserver 20// polyfill still required for IE and Safari, see https://caniuse.com/#feat=resizeobserver
21import ResizeObserver from 'resize-observer-polyfill'; 21import ResizeObserver from 'resize-observer-polyfill';
22import "hammerjs";
22 23
23/** 24/**
24 * Mousewheel picture zoom helper. 25 * Mousewheel and pinch zoom handler.
25 */ 26 */
26export default class LdZoom { 27export default class LdZoom {
27 readonly containerElement: HTMLDivElement; 28 readonly containerElement: HTMLDivElement;
28 readonly imageElement: HTMLImageElement; 29 readonly imageElement: HTMLImageElement;
29 readonly maxScaleFactor: number; 30 readonly maxScaleFactor: number;
30 readonly zoomSpeed: number; 31 readonly scrollZoomSpeed: number;
31 scaleFactor: number = 0.0; 32 scaleFactor: number = 0.0;
32 33
33 constructor( 34 constructor(
34 containerElement: HTMLDivElement, imageElement: HTMLImageElement, 35 containerElement: HTMLDivElement, imageElement: HTMLImageElement,
35 maxScaleFactor: number, zoomSpeed: number 36 maxScaleFactor: number, scrollZoomSpeed: number
36 ) { 37 ) {
37 this.containerElement = containerElement; 38 this.containerElement = containerElement;
38 this.imageElement = imageElement; 39 this.imageElement = imageElement;
39 this.maxScaleFactor = maxScaleFactor; 40 this.maxScaleFactor = maxScaleFactor;
40 this.zoomSpeed = zoomSpeed; 41 this.scrollZoomSpeed = scrollZoomSpeed;
41 } 42 }
42 43
43 /** 44 /**
@@ -52,10 +53,30 @@ export default class LdZoom {
52 53
53 this.containerElement.addEventListener('wheel', wheelEvent => { 54 this.containerElement.addEventListener('wheel', wheelEvent => {
54 wheelEvent.preventDefault(); 55 wheelEvent.preventDefault();
55 this.zoom(wheelEvent); 56 const zoomDelta = -Math.sign(wheelEvent.deltaY) * this.scrollZoomSpeed;
57 this.zoom(wheelEvent.offsetX, wheelEvent.offsetY, zoomDelta);
56 }); 58 });
57 59
58 // TODO: handle pinch-to-zoom. 60 const pinchListener = new Hammer(this.containerElement);
61 pinchListener.get('pinch').set({enable: true});
62 this.installPinchHandler(pinchListener);
63 }
64
65 private installPinchHandler(pinchListener: HammerManager) {
66 let lastScaleFactor = 0.0;
67
68 pinchListener.on('pinchstart', (pinchEvent: HammerInput) => {
69 lastScaleFactor = pinchEvent.scale;
70 });
71
72 pinchListener.on('pinchmove', (pinchEvent: HammerInput) => {
73 // FIXME: pinchEvent.center isn't always well-centered
74 const focusX = pinchEvent.center.x + this.containerElement.scrollLeft;
75 const focusY = pinchEvent.center.y + this.containerElement.scrollTop;
76 const zoomDelta = pinchEvent.scale - lastScaleFactor;
77 lastScaleFactor = pinchEvent.scale;
78 this.zoom(focusX, focusY, zoomDelta);
79 });
59 } 80 }
60 81
61 /** 82 /**
@@ -69,15 +90,12 @@ export default class LdZoom {
69 this.imageElement.style.marginTop = `${marginTop}px`; 90 this.imageElement.style.marginTop = `${marginTop}px`;
70 } 91 }
71 92
72 private zoom(wheelEvent: WheelEvent) { 93 private zoom(focusX: number, focusY: number, zoomDelta: number) {
73 const ratioX = wheelEvent.offsetX / this.imageElement.clientWidth; 94 const ratioX = focusX / this.imageElement.clientWidth;
74 const ratioY = wheelEvent.offsetY / this.imageElement.clientHeight; 95 const ratioY = focusY / this.imageElement.clientHeight;
75
76 const zoomDelta = -Math.sign(wheelEvent.deltaY) * this.zoomSpeed;
77 this.setImageScale(Math.min(this.scaleFactor + zoomDelta, this.maxScaleFactor)); 96 this.setImageScale(Math.min(this.scaleFactor + zoomDelta, this.maxScaleFactor));
78 97 this.containerElement.scrollLeft -= focusX - ratioX * this.imageElement.clientWidth;
79 this.containerElement.scrollLeft -= wheelEvent.offsetX - ratioX * this.imageElement.clientWidth; 98 this.containerElement.scrollTop -= focusY - ratioY * this.imageElement.clientHeight;
80 this.containerElement.scrollTop -= wheelEvent.offsetY - ratioY * this.imageElement.clientHeight;
81 } 99 }
82 100
83 private setImageScale(newScaleFactor: number) { 101 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,
87html { 87html {
88 height: 100%; 88 height: 100%;
89 overflow: hidden; 89 overflow: hidden;
90 touch-action: none;
90 background-color: $content-bgcolor; 91 background-color: $content-bgcolor;
91 --layout-top: #{$layout-top}; 92 --layout-top: #{$layout-top};
92 --layout-left: #{$layout-left}; 93 --layout-left: #{$layout-left};