aboutsummaryrefslogtreecommitdiff
path: root/viewer/src
diff options
context:
space:
mode:
Diffstat (limited to 'viewer/src')
-rw-r--r--viewer/src/components/LdPicture.vue11
-rw-r--r--viewer/src/services/ldzoom.ts60
2 files changed, 45 insertions, 26 deletions
diff --git a/viewer/src/components/LdPicture.vue b/viewer/src/components/LdPicture.vue
index 622165d..de46bcb 100644
--- a/viewer/src/components/LdPicture.vue
+++ b/viewer/src/components/LdPicture.vue
@@ -18,6 +18,7 @@
18--> 18-->
19 19
20<template> 20<template>
21 <!-- FIXME: v-dragscroll interferes with pinch-to-zoom -->
21 <div 22 <div
22 ref="containerElement" 23 ref="containerElement"
23 v-dragscroll 24 v-dragscroll
@@ -34,7 +35,7 @@
34 class="ld-picture-element" 35 class="ld-picture-element"
35 :class="{'slow-loading': Boolean(slowLoadingStyle)}" 36 :class="{'slow-loading': Boolean(slowLoadingStyle)}"
36 :style="slowLoadingStyle" 37 :style="slowLoadingStyle"
37 @load="lazyImageLoaded" 38 @load="clearSlowLoading"
38 /> 39 />
39 <b-loading :active="loader" :is-full-page="false" class="ld-picture-loader" /> 40 <b-loading :active="loader" :is-full-page="false" class="ld-picture-loader" />
40 </div> 41 </div>
@@ -49,7 +50,7 @@ import DragScrollClickFix from "@/services/dragscrollclickfix";
49export default class LdPicture extends Vue { 50export default class LdPicture extends Vue {
50 @Prop({ required: true }) readonly picture!: Gallery.Picture; 51 @Prop({ required: true }) readonly picture!: Gallery.Picture;
51 @Ref() readonly containerElement!: HTMLDivElement; 52 @Ref() readonly containerElement!: HTMLDivElement;
52 @Ref() readonly imageElement!: any; // FIXME: no typedef for v-lazy-image 53 @Ref() readonly imageElement!: Vue;
53 54
54 readonly SLOW_LOADING_TIMEOUT_MS: number = 1500; 55 readonly SLOW_LOADING_TIMEOUT_MS: number = 1500;
55 readonly dragScrollClickFix = new DragScrollClickFix(); 56 readonly dragScrollClickFix = new DragScrollClickFix();
@@ -60,17 +61,13 @@ export default class LdPicture extends Vue {
60 61
61 mounted() { 62 mounted() {
62 this.timer = setTimeout(this.generateSlowLoadingStyle, this.SLOW_LOADING_TIMEOUT_MS); 63 this.timer = setTimeout(this.generateSlowLoadingStyle, this.SLOW_LOADING_TIMEOUT_MS);
64 new LdZoom(this.containerElement, this.imageElement.$el as HTMLImageElement, this.picture.properties, 10, 1 / 5).install();
63 } 65 }
64 66
65 destroyed() { 67 destroyed() {
66 this.clearSlowLoading(); 68 this.clearSlowLoading();
67 } 69 }
68 70
69 lazyImageLoaded() {
70 this.clearSlowLoading();
71 new LdZoom(this.containerElement, this.imageElement.$el, 10, 1 / 5).install();
72 }
73
74 clearSlowLoading() { 71 clearSlowLoading() {
75 if (this.timer) clearTimeout(this.timer); 72 if (this.timer) clearTimeout(this.timer);
76 this.timer = null; 73 this.timer = null;
diff --git a/viewer/src/services/ldzoom.ts b/viewer/src/services/ldzoom.ts
index 50f006e..c28c2c8 100644
--- a/viewer/src/services/ldzoom.ts
+++ b/viewer/src/services/ldzoom.ts
@@ -27,28 +27,31 @@ import "hammerjs";
27export default class LdZoom { 27export default class LdZoom {
28 readonly containerElement: HTMLDivElement; 28 readonly containerElement: HTMLDivElement;
29 readonly imageElement: HTMLImageElement; 29 readonly imageElement: HTMLImageElement;
30 readonly pictureProperties: Gallery.PictureProperties;
30 readonly maxScaleFactor: number; 31 readonly maxScaleFactor: number;
31 readonly scrollZoomSpeed: number; 32 readonly scrollZoomSpeed: number;
32 scaleFactor: number = 0.0; 33 scaleFactor: number = 0.0;
33 34
34 constructor( 35 constructor(
35 containerElement: HTMLDivElement, imageElement: HTMLImageElement, 36 containerElement: HTMLDivElement, imageElement: HTMLImageElement,
37 pictureProperties: Gallery.PictureProperties,
36 maxScaleFactor: number, scrollZoomSpeed: number 38 maxScaleFactor: number, scrollZoomSpeed: number
37 ) { 39 ) {
38 this.containerElement = containerElement; 40 this.containerElement = containerElement;
39 this.imageElement = imageElement; 41 this.imageElement = imageElement;
42 this.pictureProperties = pictureProperties;
40 this.maxScaleFactor = maxScaleFactor; 43 this.maxScaleFactor = maxScaleFactor;
41 this.scrollZoomSpeed = scrollZoomSpeed; 44 this.scrollZoomSpeed = scrollZoomSpeed;
42 } 45 }
43 46
44 /** 47 /**
45 * Register event listeners. 48 * Register event listeners.
46 * The dimension of the image should be known before calling this method.
47 */ 49 */
48 public install() { 50 public install() {
51 this.updateImageScale(this.scaleFactor);
52
49 new ResizeObserver(() => { 53 new ResizeObserver(() => {
50 this.setImageScale(this.scaleFactor); 54 this.updateImageScale(this.scaleFactor);
51 this.recenterImageElement();
52 }).observe(this.containerElement); 55 }).observe(this.containerElement);
53 56
54 this.containerElement.addEventListener('wheel', wheelEvent => { 57 this.containerElement.addEventListener('wheel', wheelEvent => {
@@ -60,8 +63,6 @@ export default class LdZoom {
60 const pinchListener = new Hammer(this.containerElement); 63 const pinchListener = new Hammer(this.containerElement);
61 pinchListener.get('pinch').set({enable: true}); 64 pinchListener.get('pinch').set({enable: true});
62 this.installPinchHandler(pinchListener); 65 this.installPinchHandler(pinchListener);
63
64 this.setImageScale(this.scaleFactor);
65 } 66 }
66 67
67 private installPinchHandler(pinchListener: HammerManager) { 68 private installPinchHandler(pinchListener: HammerManager) {
@@ -72,7 +73,6 @@ export default class LdZoom {
72 }); 73 });
73 74
74 pinchListener.on('pinchmove', (pinchEvent: HammerInput) => { 75 pinchListener.on('pinchmove', (pinchEvent: HammerInput) => {
75 // FIXME: v-dragscroll interferes with our focus point scroll adjustment
76 const focusX = pinchEvent.center.x + this.containerElement.scrollLeft; 76 const focusX = pinchEvent.center.x + this.containerElement.scrollLeft;
77 const focusY = pinchEvent.center.y + this.containerElement.scrollTop; 77 const focusY = pinchEvent.center.y + this.containerElement.scrollTop;
78 const scaleFactor = pinchEvent.scale * startScaleFactor; 78 const scaleFactor = pinchEvent.scale * startScaleFactor;
@@ -81,32 +81,54 @@ export default class LdZoom {
81 } 81 }
82 82
83 /** 83 /**
84 * Returns the picture resolution as it should be displayed.
85 */
86 private getDisplayResolution(): Gallery.Resolution {
87 return {
88 width: this.pictureProperties.resolution.width * this.scaleFactor,
89 height: this.pictureProperties.resolution.height * this.scaleFactor,
90 };
91 }
92
93 /**
94 * Applies scaling to the DOM image element.
95 * To call after internal intermediate computations because DOM properties aren't stable.
96 */
97 private resizeImageElement() {
98 const imageDim = this.getDisplayResolution();
99 this.imageElement.width = imageDim.width;
100 this.imageElement.height = imageDim.height;
101 }
102
103 /**
84 * Centers the image element inside its container if it fits, or stick to the top and left borders otherwise. 104 * Centers the image element inside its container if it fits, or stick to the top and left borders otherwise.
85 * It's depressingly hard to do in pure CSS… 105 * It's depressingly hard to do in pure CSS…
86 */ 106 */
87 private recenterImageElement() { 107 private recenterImageElement() {
88 const marginLeft = Math.max((this.containerElement.clientWidth - this.imageElement.clientWidth) / 2, 0); 108 const imageDim = this.getDisplayResolution();
89 const marginTop = Math.max((this.containerElement.clientHeight - this.imageElement.clientHeight) / 2, 0); 109 const marginLeft = Math.max((this.containerElement.clientWidth - imageDim.width) / 2, 0);
110 const marginTop = Math.max((this.containerElement.clientHeight - imageDim.height) / 2, 0);
90 this.imageElement.style.marginLeft = `${marginLeft}px`; 111 this.imageElement.style.marginLeft = `${marginLeft}px`;
91 this.imageElement.style.marginTop = `${marginTop}px`; 112 this.imageElement.style.marginTop = `${marginTop}px`;
92 } 113 }
93 114
94 private zoom(focusX: number, focusY: number, scaleFactor: number) { 115 private zoom(focusX: number, focusY: number, scaleFactor: number) {
95 const ratioX = focusX / this.imageElement.clientWidth; 116 const imageDim = this.getDisplayResolution();
96 const ratioY = focusY / this.imageElement.clientHeight; 117 const ratioX = focusX / imageDim.width;
97 this.setImageScale(Math.min(scaleFactor, this.maxScaleFactor)); 118 const ratioY = focusY / imageDim.height;
98 this.containerElement.scrollLeft -= focusX - ratioX * this.imageElement.clientWidth; 119 this.updateImageScale(Math.min(scaleFactor, this.maxScaleFactor));
99 this.containerElement.scrollTop -= focusY - ratioY * this.imageElement.clientHeight; 120
121 const newImageDim = this.getDisplayResolution();
122 this.containerElement.scrollLeft -= focusX - ratioX * newImageDim.width;
123 this.containerElement.scrollTop -= focusY - ratioY * newImageDim.height;
100 } 124 }
101 125
102 private setImageScale(newScaleFactor: number) { 126 private updateImageScale(newScaleFactor: number) {
103 const horizontalFillRatio = this.containerElement.clientWidth / this.imageElement.naturalWidth; 127 const horizontalFillRatio = this.containerElement.clientWidth / this.pictureProperties.resolution.width;
104 const verticalFillRatio = this.containerElement.clientHeight / this.imageElement.naturalHeight; 128 const verticalFillRatio = this.containerElement.clientHeight / this.pictureProperties.resolution.height;
105 const minScaleFactor = Math.min(horizontalFillRatio, verticalFillRatio, 1.0); 129 const minScaleFactor = Math.min(horizontalFillRatio, verticalFillRatio, 1.0);
106 this.scaleFactor = Math.max(newScaleFactor, minScaleFactor); 130 this.scaleFactor = Math.max(newScaleFactor, minScaleFactor);
107 131 this.resizeImageElement();
108 this.imageElement.width = this.scaleFactor * this.imageElement.naturalWidth;
109 this.imageElement.height = this.scaleFactor * this.imageElement.naturalHeight;
110 this.recenterImageElement(); 132 this.recenterImageElement();
111 } 133 }
112} 134}