/* 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 .
*/
import { PictureProperties, Resolution } from '@/@types/gallery';
import { importHammer } from '@/plugins/asyncLib';
import { CSSProperties, Ref } from 'vue';
/**
* Mousewheel and pinch zoom handler.
*/
export const useLdZoom = (
imageStyle: Ref,
containerElement: HTMLDivElement,
imageElement: HTMLImageElement,
pictureProperties: PictureProperties,
maxScaleFactor = 10,
scrollZoomSpeed: number = 1 / 7,
) => {
let scaleFactor = 0.0;
/**
* Register event listeners.
*/
updateImageScale(scaleFactor);
new ResizeObserver(() => {
updateImageScale(scaleFactor);
}).observe(containerElement);
containerElement.addEventListener('wheel', wheelEvent => {
wheelEvent.preventDefault();
const newScaleFactor = scaleFactor - Math.sign(wheelEvent.deltaY) * (scrollZoomSpeed * scaleFactor);
zoom(wheelEvent.offsetX, wheelEvent.offsetY, newScaleFactor);
});
importHammer().then(() => {
const pinchListener = new Hammer(containerElement);
pinchListener.get('pinch').set({ enable: true });
installPinchHandler(pinchListener);
});
return { imageStyle };
// ---
function installPinchHandler(pinchListener: HammerManager) {
let startScaleFactor = 0.0;
pinchListener.on('pinchstart', () => {
startScaleFactor = scaleFactor;
});
pinchListener.on('pinchmove', (pinchEvent: HammerInput) => {
const focusX = pinchEvent.center.x + containerElement.scrollLeft;
const focusY = pinchEvent.center.y + containerElement.scrollTop;
const scaleFactor = pinchEvent.scale * startScaleFactor;
zoom(focusX, focusY, scaleFactor);
});
}
/**
* Returns the picture resolution as it should be displayed.
*/
function getDisplayResolution(): Resolution {
return {
width: pictureProperties.resolution.width * scaleFactor,
height: pictureProperties.resolution.height * scaleFactor,
};
}
/**
* Applies scaling to the DOM image element.
* To call after internal intermediate computations because DOM properties aren't stable.
*/
function resizeImageElement() {
const imageDim = getDisplayResolution();
imageElement.width = imageDim.width;
imageElement.height = imageDim.height;
}
/**
* 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…
*/
function recenterImageElement() {
const imageDim = getDisplayResolution();
const marginLeft = Math.max((containerElement.clientWidth - imageDim.width) / 2, 0);
const marginTop = Math.max((containerElement.clientHeight - imageDim.height) / 2, 0);
imageStyle.value.marginLeft = `${marginLeft}px`;
imageStyle.value.marginTop = `${marginTop}px`;
}
function zoom(focusX: number, focusY: number, scaleFactor: number) {
const imageDim = getDisplayResolution();
const ratioX = focusX / imageDim.width;
const ratioY = focusY / imageDim.height;
updateImageScale(Math.min(scaleFactor, maxScaleFactor));
const newImageDim = getDisplayResolution();
containerElement.scrollLeft -= focusX - ratioX * newImageDim.width;
containerElement.scrollTop -= focusY - ratioY * newImageDim.height;
}
function updateImageScale(newScaleFactor: number) {
const horizontalFillRatio = containerElement.clientWidth / pictureProperties.resolution.width;
const verticalFillRatio = containerElement.clientHeight / pictureProperties.resolution.height;
const minScaleFactor = Math.min(horizontalFillRatio, verticalFillRatio, 1.0);
scaleFactor = Math.max(newScaleFactor, minScaleFactor);
resizeImageElement();
recenterImageElement();
}
};