diff --git a/web/app.js b/web/app.js index e5255bdfe..1b27611b2 100644 --- a/web/app.js +++ b/web/app.js @@ -26,7 +26,7 @@ import { MAX_SCALE, MIN_SCALE, noContextMenuHandler, - normalizeWheelEventDelta, + normalizeWheelEventDirection, parseQueryString, PresentationModeState, ProgressBar, @@ -237,6 +237,7 @@ const PDFViewerApplication = { contentDispositionFilename: null, triggerDelayedFallback: null, _saveInProgress: false, + _wheelUnusedTicks: 0, // Called once when the document is loaded. async initialize(appConfig) { @@ -1849,6 +1850,22 @@ const PDFViewerApplication = { _boundEvents.windowBeforePrint = null; _boundEvents.windowAfterPrint = null; }, + + accumulateWheelTicks(ticks) { + // If the scroll direction changed, reset the accumulated wheel ticks. + if ( + (this._wheelUnusedTicks > 0 && ticks < 0) || + (this._wheelUnusedTicks < 0 && ticks > 0) + ) { + this._wheelUnusedTicks = 0; + } + this._wheelUnusedTicks += ticks; + const wholeTicks = + Math.sign(this._wheelUnusedTicks) * + Math.floor(Math.abs(this._wheelUnusedTicks)); + this._wheelUnusedTicks -= wholeTicks; + return wholeTicks; + }, }; let validateFileURL; @@ -2506,13 +2523,34 @@ function webViewerWheel(evt) { const previousScale = pdfViewer.currentScale; - const delta = normalizeWheelEventDelta(evt); + const delta = normalizeWheelEventDirection(evt); + let ticks = 0; + if ( + evt.deltaMode === WheelEvent.DOM_DELTA_LINE || + evt.deltaMode === WheelEvent.DOM_DELTA_PAGE + ) { + // For line-based devices, use one tick per event, because different + // OSs have different defaults for the number lines. But we generally + // want one "clicky" roll of the wheel (which produces one event) to + // adjust the zoom by one step. + if (Math.abs(delta) >= 1) { + ticks = Math.sign(delta); + } else { + // If we're getting fractional lines (I can't think of a scenario + // this might actually happen), be safe and use the accumulator. + ticks = PDFViewerApplication.accumulateWheelTicks(delta); + } + } else { + // pixel-based devices + const PIXELS_PER_LINE_SCALE = 30; + ticks = PDFViewerApplication.accumulateWheelTicks( + delta / PIXELS_PER_LINE_SCALE + ); + } - const MOUSE_WHEEL_DELTA_PER_PAGE_SCALE = 3.0; - const ticks = delta * MOUSE_WHEEL_DELTA_PER_PAGE_SCALE; if (ticks < 0) { PDFViewerApplication.zoomOut(-ticks); - } else { + } else if (ticks > 0) { PDFViewerApplication.zoomIn(ticks); } diff --git a/web/ui_utils.js b/web/ui_utils.js index c24ee9907..3ad6e8ae2 100644 --- a/web/ui_utils.js +++ b/web/ui_utils.js @@ -636,13 +636,18 @@ function getPDFFileNameFromURL(url, defaultFilename = "document.pdf") { return suggestedFilename || defaultFilename; } -function normalizeWheelEventDelta(evt) { +function normalizeWheelEventDirection(evt) { let delta = Math.sqrt(evt.deltaX * evt.deltaX + evt.deltaY * evt.deltaY); const angle = Math.atan2(evt.deltaY, evt.deltaX); if (-0.25 * Math.PI < angle && angle < 0.75 * Math.PI) { // All that is left-up oriented has to change the sign. delta = -delta; } + return delta; +} + +function normalizeWheelEventDelta(evt) { + let delta = normalizeWheelEventDirection(evt); const MOUSE_DOM_DELTA_PIXEL_MODE = 0; const MOUSE_DOM_DELTA_LINE_MODE = 1; @@ -1017,6 +1022,7 @@ export { scrollIntoView, watchScroll, binarySearchFirstItem, + normalizeWheelEventDirection, normalizeWheelEventDelta, animationStarted, WaitOnType,