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 /viewer/src | |
parent | f8a1763c3bee0e236c86ba9f6b46aceb212dea10 (diff) | |
download | ldgallery-29d432e64e0482935ef91dbfed37d4d4cf26c42f.tar.gz |
viewer/LdZoom: add support for pinch-to-zoom
GitHub: closes #106
Diffstat (limited to 'viewer/src')
-rw-r--r-- | viewer/src/services/ldzoom.ts | 46 | ||||
-rw-r--r-- | viewer/src/views/MainLayout.vue | 1 |
2 files changed, 33 insertions, 14 deletions
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}; |