aboutsummaryrefslogtreecommitdiff
path: root/viewer/src/services/ldzoom.ts
diff options
context:
space:
mode:
Diffstat (limited to 'viewer/src/services/ldzoom.ts')
-rw-r--r--viewer/src/services/ldzoom.ts92
1 files changed, 92 insertions, 0 deletions
diff --git a/viewer/src/services/ldzoom.ts b/viewer/src/services/ldzoom.ts
new file mode 100644
index 0000000..f001805
--- /dev/null
+++ b/viewer/src/services/ldzoom.ts
@@ -0,0 +1,92 @@
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
20// polyfill still required for IE and Safari, see https://caniuse.com/#feat=resizeobserver
21import ResizeObserver from 'resize-observer-polyfill';
22
23/**
24 * Mousewheel picture zoom helper.
25 */
26export default class LdZoom {
27 readonly containerElement: HTMLDivElement;
28 readonly imageElement: HTMLImageElement;
29 readonly maxScaleFactor: number;
30 readonly zoomSpeed: number;
31 scaleFactor: number;
32
33 constructor(
34 containerElement: HTMLDivElement, imageElement: HTMLImageElement,
35 maxScaleFactor: number, zoomSpeed: number
36 ) {
37 this.containerElement = containerElement;
38 this.imageElement = imageElement;
39 this.maxScaleFactor = maxScaleFactor;
40 this.zoomSpeed = zoomSpeed;
41 this.scaleFactor = imageElement.clientWidth / imageElement.naturalWidth;
42 }
43
44 public install() {
45 new ResizeObserver(() => {
46 this.setImageScale(this.scaleFactor);
47 this.recenterImageElement();
48 }).observe(this.containerElement);
49
50 this.containerElement.addEventListener('wheel', wheelEvent => {
51 wheelEvent.preventDefault();
52 this.zoom(wheelEvent);
53 });
54
55 // TODO: handle pinch-to-zoom.
56
57 this.recenterImageElement();
58 }
59
60 /**
61 * Centers the image element inside its container if it fits, or stick to the top and left borders otherwise.
62 * It's depressingly hard to do in pure CSS…
63 */
64 private recenterImageElement() {
65 const marginLeft = Math.max((this.containerElement.clientWidth - this.imageElement.clientWidth) / 2, 0);
66 const marginTop = Math.max((this.containerElement.clientHeight - this.imageElement.clientHeight) / 2, 0);
67 this.imageElement.style.marginLeft = `${marginLeft}px`;
68 this.imageElement.style.marginTop = `${marginTop}px`;
69 }
70
71 private zoom(wheelEvent: WheelEvent) {
72 const ratioX = wheelEvent.offsetX / this.imageElement.clientWidth;
73 const ratioY = wheelEvent.offsetY / this.imageElement.clientHeight;
74
75 const zoomDelta = -Math.sign(wheelEvent.deltaY) * this.zoomSpeed;
76 this.setImageScale(Math.min(this.scaleFactor + zoomDelta, this.maxScaleFactor));
77
78 this.containerElement.scrollLeft -= wheelEvent.offsetX - ratioX * this.imageElement.clientWidth;
79 this.containerElement.scrollTop -= wheelEvent.offsetY - ratioY * this.imageElement.clientHeight;
80 }
81
82 private setImageScale(newScaleFactor: number) {
83 const horizontalFillRatio = this.containerElement.clientWidth / this.imageElement.naturalWidth;
84 const verticalFillRatio = this.containerElement.clientHeight / this.imageElement.naturalHeight;
85 const minScaleFactor = Math.min(horizontalFillRatio, verticalFillRatio, 1.0);
86 this.scaleFactor = Math.max(newScaleFactor, minScaleFactor);
87
88 this.imageElement.width = this.scaleFactor * this.imageElement.naturalWidth;
89 this.imageElement.height = this.scaleFactor * this.imageElement.naturalHeight;
90 this.recenterImageElement();
91 }
92}