aboutsummaryrefslogtreecommitdiff
path: root/viewer/src/services/ui/ldZoom.ts
diff options
context:
space:
mode:
Diffstat (limited to 'viewer/src/services/ui/ldZoom.ts')
-rw-r--r--viewer/src/services/ui/ldZoom.ts128
1 files changed, 128 insertions, 0 deletions
diff --git a/viewer/src/services/ui/ldZoom.ts b/viewer/src/services/ui/ldZoom.ts
new file mode 100644
index 0000000..9f77dea
--- /dev/null
+++ b/viewer/src/services/ui/ldZoom.ts
@@ -0,0 +1,128 @@
1/* ldgallery - A static generator which turns a collection of tagged
2-- pictures into a searchable web gallery.
3--
4-- Copyright (C) 2020 Pacien TRAN-GIRARD
5--
6-- This program is free software: you can redistribute it and/or modify
7-- it under the terms of the GNU Affero General Public License as
8-- published by the Free Software Foundation, either version 3 of the
9-- License, or (at your option) any later version.
10--
11-- This program is distributed in the hope that it will be useful,
12-- but WITHOUT ANY WARRANTY; without even the implied warranty of
13-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14-- GNU Affero General Public License for more details.
15--
16-- You should have received a copy of the GNU Affero General Public License
17-- along with this program. If not, see <https://www.gnu.org/licenses/>.
18*/
19
20import { PictureProperties, Resolution } from '@/@types/gallery';
21import { importHammer } from '@/plugins/asyncLib';
22import { CSSProperties, Ref } from 'vue';
23
24/**
25 * Mousewheel and pinch zoom handler.
26 */
27export const useLdZoom = (
28 imageStyle: Ref<CSSProperties>,
29 containerElement: HTMLDivElement,
30 imageElement: HTMLImageElement,
31 pictureProperties: PictureProperties,
32 maxScaleFactor = 10,
33 scrollZoomSpeed: number = 1 / 7,
34) => {
35 let scaleFactor = 0.0;
36
37 /**
38 * Register event listeners.
39 */
40 updateImageScale(scaleFactor);
41
42 new ResizeObserver(() => {
43 updateImageScale(scaleFactor);
44 }).observe(containerElement);
45
46 containerElement.addEventListener('wheel', wheelEvent => {
47 wheelEvent.preventDefault();
48 const newScaleFactor = scaleFactor - Math.sign(wheelEvent.deltaY) * (scrollZoomSpeed * scaleFactor);
49 zoom(wheelEvent.offsetX, wheelEvent.offsetY, newScaleFactor);
50 });
51
52 importHammer().then(() => {
53 const pinchListener = new Hammer(containerElement);
54 pinchListener.get('pinch').set({ enable: true });
55 installPinchHandler(pinchListener);
56 });
57
58 return { imageStyle };
59
60 // ---
61
62 function installPinchHandler(pinchListener: HammerManager) {
63 let startScaleFactor = 0.0;
64
65 pinchListener.on('pinchstart', () => {
66 startScaleFactor = scaleFactor;
67 });
68
69 pinchListener.on('pinchmove', (pinchEvent: HammerInput) => {
70 const focusX = pinchEvent.center.x + containerElement.scrollLeft;
71 const focusY = pinchEvent.center.y + containerElement.scrollTop;
72 const scaleFactor = pinchEvent.scale * startScaleFactor;
73 zoom(focusX, focusY, scaleFactor);
74 });
75 }
76
77 /**
78 * Returns the picture resolution as it should be displayed.
79 */
80 function getDisplayResolution(): Resolution {
81 return {
82 width: pictureProperties.resolution.width * scaleFactor,
83 height: pictureProperties.resolution.height * scaleFactor,
84 };
85 }
86
87 /**
88 * Applies scaling to the DOM image element.
89 * To call after internal intermediate computations because DOM properties aren't stable.
90 */
91 function resizeImageElement() {
92 const imageDim = getDisplayResolution();
93 imageElement.width = imageDim.width;
94 imageElement.height = imageDim.height;
95 }
96
97 /**
98 * Centers the image element inside its container if it fits, or stick to the top and left borders otherwise.
99 * It's depressingly hard to do in pure CSS…
100 */
101 function recenterImageElement() {
102 const imageDim = getDisplayResolution();
103 const marginLeft = Math.max((containerElement.clientWidth - imageDim.width) / 2, 0);
104 const marginTop = Math.max((containerElement.clientHeight - imageDim.height) / 2, 0);
105 imageStyle.value.marginLeft = `${marginLeft}px`;
106 imageStyle.value.marginTop = `${marginTop}px`;
107 }
108
109 function zoom(focusX: number, focusY: number, scaleFactor: number) {
110 const imageDim = getDisplayResolution();
111 const ratioX = focusX / imageDim.width;
112 const ratioY = focusY / imageDim.height;
113 updateImageScale(Math.min(scaleFactor, maxScaleFactor));
114
115 const newImageDim = getDisplayResolution();
116 containerElement.scrollLeft -= focusX - ratioX * newImageDim.width;
117 containerElement.scrollTop -= focusY - ratioY * newImageDim.height;
118 }
119
120 function updateImageScale(newScaleFactor: number) {
121 const horizontalFillRatio = containerElement.clientWidth / pictureProperties.resolution.width;
122 const verticalFillRatio = containerElement.clientHeight / pictureProperties.resolution.height;
123 const minScaleFactor = Math.min(horizontalFillRatio, verticalFillRatio, 1.0);
124 scaleFactor = Math.max(newScaleFactor, minScaleFactor);
125 resizeImageElement();
126 recenterImageElement();
127 }
128};