Merge pull request #19239 from overleaf/dp-scroll-wheel-zoom

Implement pdf mouse wheel zooming

GitOrigin-RevId: d69570bb5a54970072d6f602792ddb1159343423
This commit is contained in:
David 2024-07-04 11:01:53 +01:00 committed by Copybot
parent 3e81e212f3
commit 4fe66c0189
2 changed files with 92 additions and 0 deletions

View file

@ -16,6 +16,7 @@ import { debugConsole } from '@/utils/debugging'
import { usePdfPreviewContext } from '@/features/pdf-preview/components/pdf-preview-provider'
import { useFeatureFlag } from '@/shared/context/split-test-context'
import usePresentationMode from '../hooks/use-presentation-mode'
import useMouseWheelZoom from '../hooks/use-mouse-wheel-zoom'
type PdfJsViewerProps = {
url: string
@ -467,6 +468,8 @@ function PdfJsViewer({ url, pdfFile }: PdfJsViewerProps) {
[initialised, setZoom]
)
useMouseWheelZoom(pdfJsWrapper, setScale)
const requestPresentationMode = usePresentationMode(
pdfJsWrapper,
page,

View file

@ -0,0 +1,89 @@
import { useCallback, useEffect, useRef } from 'react'
import PDFJSWrapper from '../util/pdf-js-wrapper'
import { useFeatureFlag } from '@/shared/context/split-test-context'
// 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 isEnabled = useFeatureFlag('pdf-controls')
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 && isEnabled) {
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, isEnabled, performZoom])
}