From b86d96f2ed5dd4f17b047e8aba22512400484bb3 Mon Sep 17 00:00:00 2001 From: pacien Date: Sun, 26 Apr 2020 06:02:33 +0200 Subject: viewer/LdPicture: implement mousewheel zoom GitHub: closes #153 --- viewer/src/services/ldzoom.ts | 92 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 92 insertions(+) create mode 100644 viewer/src/services/ldzoom.ts (limited to 'viewer/src/services/ldzoom.ts') 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 @@ +/* ldgallery - A static generator which turns a collection of tagged +-- pictures into a searchable web gallery. +-- +-- Copyright (C) 2020 Pacien TRAN-GIRARD +-- +-- This program is free software: you can redistribute it and/or modify +-- it under the terms of the GNU Affero General Public License as +-- published by the Free Software Foundation, either version 3 of the +-- License, or (at your option) any later version. +-- +-- This program is distributed in the hope that it will be useful, +-- but WITHOUT ANY WARRANTY; without even the implied warranty of +-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +-- GNU Affero General Public License for more details. +-- +-- You should have received a copy of the GNU Affero General Public License +-- along with this program. If not, see . +*/ + +// polyfill still required for IE and Safari, see https://caniuse.com/#feat=resizeobserver +import ResizeObserver from 'resize-observer-polyfill'; + +/** + * Mousewheel picture zoom helper. + */ +export default class LdZoom { + readonly containerElement: HTMLDivElement; + readonly imageElement: HTMLImageElement; + readonly maxScaleFactor: number; + readonly zoomSpeed: number; + scaleFactor: number; + + constructor( + containerElement: HTMLDivElement, imageElement: HTMLImageElement, + maxScaleFactor: number, zoomSpeed: number + ) { + this.containerElement = containerElement; + this.imageElement = imageElement; + this.maxScaleFactor = maxScaleFactor; + this.zoomSpeed = zoomSpeed; + this.scaleFactor = imageElement.clientWidth / imageElement.naturalWidth; + } + + public install() { + new ResizeObserver(() => { + this.setImageScale(this.scaleFactor); + this.recenterImageElement(); + }).observe(this.containerElement); + + this.containerElement.addEventListener('wheel', wheelEvent => { + wheelEvent.preventDefault(); + this.zoom(wheelEvent); + }); + + // TODO: handle pinch-to-zoom. + + this.recenterImageElement(); + } + + /** + * Centers the image element inside its container if it fits, or stick to the top and left borders otherwise. + * It's depressingly hard to do in pure CSS… + */ + private recenterImageElement() { + const marginLeft = Math.max((this.containerElement.clientWidth - this.imageElement.clientWidth) / 2, 0); + const marginTop = Math.max((this.containerElement.clientHeight - this.imageElement.clientHeight) / 2, 0); + this.imageElement.style.marginLeft = `${marginLeft}px`; + this.imageElement.style.marginTop = `${marginTop}px`; + } + + private zoom(wheelEvent: WheelEvent) { + const ratioX = wheelEvent.offsetX / this.imageElement.clientWidth; + const ratioY = wheelEvent.offsetY / this.imageElement.clientHeight; + + const zoomDelta = -Math.sign(wheelEvent.deltaY) * this.zoomSpeed; + this.setImageScale(Math.min(this.scaleFactor + zoomDelta, this.maxScaleFactor)); + + this.containerElement.scrollLeft -= wheelEvent.offsetX - ratioX * this.imageElement.clientWidth; + this.containerElement.scrollTop -= wheelEvent.offsetY - ratioY * this.imageElement.clientHeight; + } + + private setImageScale(newScaleFactor: number) { + const horizontalFillRatio = this.containerElement.clientWidth / this.imageElement.naturalWidth; + const verticalFillRatio = this.containerElement.clientHeight / this.imageElement.naturalHeight; + const minScaleFactor = Math.min(horizontalFillRatio, verticalFillRatio, 1.0); + this.scaleFactor = Math.max(newScaleFactor, minScaleFactor); + + this.imageElement.width = this.scaleFactor * this.imageElement.naturalWidth; + this.imageElement.height = this.scaleFactor * this.imageElement.naturalHeight; + this.recenterImageElement(); + } +} -- cgit v1.2.3