overleaf/services/web/frontend/js/features/pdf-preview/hooks/use-mouse-wheel-zoom.ts
David 277ac91f0e Merge pull request #19390 from overleaf/dp-pdf-tools-teardown
Remove `pdf-controls` feature flag and old controls

GitOrigin-RevId: 807ee0aa6384df354809f4d59b10d00dadef898c
2024-07-22 08:04:22 +00:00

86 lines
2.9 KiB
TypeScript

import { useCallback, useEffect, useRef } from 'react'
import PDFJSWrapper from '../util/pdf-js-wrapper'
// We need this to work for both a traditional mouse wheel and a touchpad "pinch to zoom".
// From experimentation, trackpads tend to fire a lot of events with small deltaY's where
// as a mouse wheel will fire fewer events but sometimes with a very high deltaY if you
// move the wheel quickly.
// The divisor is set to a value that works for the trackpad with the maximum value ensuring
// that the scale doesn't suddenly change drastically from moving the mouse wheel quickly.
const MAX_SCALE_FACTOR = 1.2
const SCALE_FACTOR_DIVISOR = 20
export default function useMouseWheelZoom(
pdfJsWrapper: PDFJSWrapper | null | undefined,
setScale: (scale: string) => void
) {
const isZoomingRef = useRef(false)
const performZoom = useCallback(
(event: WheelEvent, pdfJsWrapper: PDFJSWrapper) => {
// First, we calculate and set the new scale
const scrollMagnitude = Math.abs(event.deltaY)
const scaleFactorMagnitude = Math.min(
1 + scrollMagnitude / SCALE_FACTOR_DIVISOR,
MAX_SCALE_FACTOR
)
const previousScale = pdfJsWrapper.viewer.currentScale
const scaleChangeDirection = Math.sign(event.deltaY)
const approximateScaleFactor =
scaleChangeDirection < 0
? scaleFactorMagnitude
: 1 / scaleFactorMagnitude
const newScale =
Math.round(previousScale * approximateScaleFactor * 100) / 100
const exactScaleFactor = newScale / previousScale
// Set the scale directly to ensure it is set before we do the scrolling below
pdfJsWrapper.viewer.currentScale = newScale
setScale(`${newScale}`)
// Then we need to ensure we are centering the zoom on the mouse position
const containerRect = pdfJsWrapper.container.getBoundingClientRect()
const top = containerRect.top
const left = containerRect.left
// Positions relative to pdf viewer
const currentMouseX = event.clientX - left
const currentMouseY = event.clientY - top
pdfJsWrapper.container.scrollBy({
left: currentMouseX * exactScaleFactor - currentMouseX,
top: currentMouseY * exactScaleFactor - currentMouseY,
behavior: 'instant',
})
},
[setScale]
)
useEffect(() => {
if (pdfJsWrapper) {
const wheelListener = (event: WheelEvent) => {
if (event.metaKey || event.ctrlKey) {
event.preventDefault()
if (!isZoomingRef.current) {
isZoomingRef.current = true
performZoom(event, pdfJsWrapper)
setTimeout(() => {
isZoomingRef.current = false
}, 5)
}
}
}
pdfJsWrapper.container.addEventListener('wheel', wheelListener)
return () => {
pdfJsWrapper.container.removeEventListener('wheel', wheelListener)
}
}
}, [pdfJsWrapper, setScale, performZoom])
}