diff --git a/packages/app-desktop/gui/note-viewer/index.html b/packages/app-desktop/gui/note-viewer/index.html index ee3b1128941..b3da3148152 100644 --- a/packages/app-desktop/gui/note-viewer/index.html +++ b/packages/app-desktop/gui/note-viewer/index.html @@ -381,6 +381,10 @@ return Math.max(0, contentElement.scrollHeight - contentElement.clientHeight); } + function maxScrollLeft() { + return Math.max(0, contentElement.scrollWidth - contentElement.clientWidth); + } + // The body element needs to have a fixed height for the content to be scrollable function updateBodyHeight() { document.getElementById('joplin-container-body').style.height = window.innerHeight + 'px'; @@ -394,6 +398,67 @@ return m ? Math.min(1, contentElement.scrollTop / m) : 0; } + // If zoom factor is not 1, Electron/Chromium calculates scrollTop incorrectly. + // This is automatically set. + let zoomFactorIsNotOne = false; + // When custom smooth scrolling is ongoing, remainedScrollDx/Dy keep the remaining + // amount of scrolling. + let remainedScrollDx = 0, remainedScrollDy = 0, remainedScrollTimerId = null; + + function resetSmoothScroll() { remainedScrollDx = 0; remainedScrollDy = 0; } + + // To avoid Electron/Chromium's scrolling bug when zoom fator is not 1, + // Custom scrolling is implemented. This is used only when zoom factor is not 1. + // If smoothly argument is true, smooth scrolling is performed. + // See https://github.com/laurent22/joplin/pull/5606#issuecomment-964293459 + function customScroll(wheelEvent, smoothly) { + const linePixels = 100 / 3; + const pagePixelsX = Math.max(linePixels, contentElement.clientWidth); + const pagePixelsY = Math.max(linePixels, contentElement.clientHeight); + let pixelsPerUnitX = 1, pixelsPerUnitY = 1; // for WheelEvent.DOM_DELTA_PIXEL + if (wheelEvent.deltaMode === WheelEvent.DOM_DELTA_LINE) { + pixelsPerUnitX = pixelsPerUnitY = linePixels; + } else if (wheelEvent.deltaMode === WheelEvent.DOM_DELTA_PAGE) { + pixelsPerUnitX = pagePixelsX; + pixelsPerUnitY = pagePixelsY; + } + if (!smoothly) { + if (wheelEvent.deltaX) { + const dx = wheelEvent.deltaX * pixelsPerUnitX; + contentElement.scrollLeft = Math.max(0, Math.min(maxScrollLeft(), contentElement.scrollLeft + dx)); + } + if (wheelEvent.deltaY) { + const dy = wheelEvent.deltaY * pixelsPerUnitY; + contentElement.scrollTop = Math.max(0, Math.min(maxScrollTop(), contentElement.scrollTop + dy)); + } + } else { + if (Math.sign(remainedScrollDx) !== Math.sign(wheelEvent.deltaX)) remainedScrollDx = 0; + if (Math.sign(remainedScrollDy) !== Math.sign(wheelEvent.deltaY)) remainedScrollDy = 0; + remainedScrollDx += wheelEvent.deltaX * pixelsPerUnitX; + remainedScrollDy += wheelEvent.deltaY * pixelsPerUnitY; + const maxDx = Math.max(8.5, Math.min(pagePixelsX, Math.abs(remainedScrollDx)) / 5); + const maxDy = Math.max(8.5, Math.min(pagePixelsY, Math.abs(remainedScrollDy)) / 5); + const f = () => { + if (remainedScrollTimerId) { + clearTimeout(remainedScrollTimerId); + remainedScrollTimerId = null; + } + if (remainedScrollDx) { + const dx = Math.max(-maxDx, Math.min(maxDx, remainedScrollDx)); + remainedScrollDx -= dx; + contentElement.scrollLeft = Math.max(0, Math.min(maxScrollLeft(), contentElement.scrollLeft + dx)); + } + if (remainedScrollDy) { + const dy = Math.max(-maxDy, Math.min(maxDy, remainedScrollDy)); + remainedScrollDy -= dy; + contentElement.scrollTop = Math.max(0, Math.min(maxScrollTop(), contentElement.scrollTop + dy)); + } + if (remainedScrollDx || remainedScrollDy) remainedScrollTimerId = setTimeout(f, 20); + }; + f(); + } + } + contentElement.addEventListener('wheel', webviewLib.logEnabledEventHandler(e => { // When zoomFactor is not 1 (using an HD display is a typical case), // DOM element's scrollTop is incorrectly calculated after wheel scroll events @@ -401,8 +466,14 @@ // To avoid this problem, prevent the upstream from calculating scrollTop and // calculate by yourself by accumulating wheel events. // https://github.com/laurent22/joplin/pull/5496 - contentElement.scrollTop = Math.max(0, Math.min(maxScrollTop(), contentElement.scrollTop + e.deltaY)); - e.preventDefault(); + // When the Electron/Chromium bug is fixed, remove this listener. + + // If scrollTop ever has a fraction part, zoomFactor is not 1. + if (zoomFactorIsNotOne || !Number.isInteger(contentElement.scrollTop)) { + zoomFactorIsNotOne = true; + customScroll(e, true); + e.preventDefault(); + } })); contentElement.addEventListener('scroll', webviewLib.logEnabledEventHandler(e => { @@ -499,6 +570,9 @@ window.addEventListener('resize', webviewLib.logEnabledEventHandler(() => { updateBodyHeight(); + // When zoomFactor is changed, resize event happens. + zoomFactorIsNotOne = false; + resetSmoothScroll(); })); // Prevent middle-click as that would open the URL in an Electron window