1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495 |
- import { useCallback, useEffect, useRef, useState } from 'react';
- const defaultOptions: Options = {
- stepDown: (s) => s / 1.5,
- stepUp: (s) => s * 1.5,
- min: 0.13,
- max: 2.25,
- };
- interface Options {
- /**
- * Allows you to specify how the step up will be handled so you can do fractional steps based on previous value.
- */
- stepUp: (scale: number) => number;
- stepDown: (scale: number) => number;
- /**
- * Set max and min values. If stepUp/down overshoots these bounds this will return min or max but internal scale value
- * will still be what ever the step functions returned last.
- */
- min?: number;
- max?: number;
- }
- /**
- * Keeps state and returns handlers that can be used to implement zooming functionality ideally by using it with
- * 'transform: scale'. It returns handler for manual buttons with zoom in/zoom out function and a ref that can be
- * used to zoom in/out with mouse wheel.
- */
- export function useZoom({ stepUp, stepDown, min, max } = defaultOptions) {
- const ref = useRef<HTMLElement>(null);
- const [scale, setScale] = useState(1);
- const onStepUp = useCallback(() => {
- if (scale < (max ?? Infinity)) {
- setScale(stepUp(scale));
- }
- }, [scale, stepUp, max]);
- const onStepDown = useCallback(() => {
- if (scale > (min ?? -Infinity)) {
- setScale(stepDown(scale));
- }
- }, [scale, stepDown, min]);
- const onWheel = useCallback(
- function (event: Event) {
- // Seems like typing for the addEventListener is lacking a bit
- const wheelEvent = event as WheelEvent;
- // Only do this with special key pressed similar to how google maps work.
- // TODO: I would guess this won't work very well with touch right now
- if (wheelEvent.ctrlKey || wheelEvent.metaKey) {
- event.preventDefault();
- setScale(Math.min(Math.max(min ?? -Infinity, scale + Math.min(wheelEvent.deltaY, 2) * -0.01), max ?? Infinity));
- if (wheelEvent.deltaY < 0) {
- const newScale = scale + Math.max(wheelEvent.deltaY, -4) * -0.015;
- setScale(Math.max(min ?? -Infinity, newScale));
- } else if (wheelEvent.deltaY > 0) {
- const newScale = scale + Math.min(wheelEvent.deltaY, 4) * -0.015;
- setScale(Math.min(max ?? Infinity, newScale));
- }
- }
- },
- [min, max, scale]
- );
- useEffect(() => {
- if (!ref.current) {
- return;
- }
- const zoomRef = ref.current;
- // Adds listener for wheel event, we need the passive: false to be able to prevent default otherwise that
- // cannot be used with passive listeners.
- zoomRef.addEventListener('wheel', onWheel, { passive: false });
- return () => {
- if (zoomRef) {
- zoomRef.removeEventListener('wheel', onWheel);
- }
- };
- }, [onWheel]);
- return {
- onStepUp,
- onStepDown,
- scale: Math.max(Math.min(scale, max ?? Infinity), min ?? -Infinity),
- isMax: scale >= (max ?? Infinity),
- isMin: scale <= (min ?? -Infinity),
- ref,
- };
- }
|