diff options
author | pacien | 2020-04-26 21:51:37 +0200 |
---|---|---|
committer | pacien | 2020-04-26 21:55:10 +0200 |
commit | 29d432e64e0482935ef91dbfed37d4d4cf26c42f (patch) | |
tree | a9ffc21c3b8385f405bcf6638abd3b017b0d0d63 | |
parent | f8a1763c3bee0e236c86ba9f6b46aceb212dea10 (diff) | |
download | ldgallery-29d432e64e0482935ef91dbfed37d4d4cf26c42f.tar.gz |
viewer/LdZoom: add support for pinch-to-zoom
GitHub: closes #106
-rw-r--r-- | readme.md | 1 | ||||
-rw-r--r-- | viewer/package-lock.json | 10 | ||||
-rw-r--r-- | viewer/package.json | 2 | ||||
-rw-r--r-- | viewer/src/services/ldzoom.ts | 46 | ||||
-rw-r--r-- | viewer/src/views/MainLayout.vue | 1 |
5 files changed, 46 insertions, 14 deletions
@@ -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 |
21 | import ResizeObserver from 'resize-observer-polyfill'; | 21 | import ResizeObserver from 'resize-observer-polyfill'; |
22 | import "hammerjs"; | ||
22 | 23 | ||
23 | /** | 24 | /** |
24 | * Mousewheel picture zoom helper. | 25 | * Mousewheel and pinch zoom handler. |
25 | */ | 26 | */ |
26 | export default class LdZoom { | 27 | export 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, | |||
87 | html { | 87 | html { |
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}; |