diff --git a/crates/mdbook-html/front-end/templates/toc.js.hbs b/crates/mdbook-html/front-end/templates/toc.js.hbs index 570165e9..9d26f3da 100644 --- a/crates/mdbook-html/front-end/templates/toc.js.hbs +++ b/crates/mdbook-html/front-end/templates/toc.js.hbs @@ -78,10 +78,6 @@ window.customElements.define('mdbook-sidebar-scrollbox', MDBookSidebarScrollbox) // --------------------------------------------------------------------------- // Support for dynamically adding headers to the sidebar. -// This is a debugging tool for the threshold which you can enable in the console. -// eslint-disable-next-line prefer-const -let mdbookThresholdDebug = false; - (function() { // This is used to detect which direction the page has scrolled since the // last scroll event. @@ -108,6 +104,101 @@ let mdbookThresholdDebug = false; // I'm not sure why eslint seems to have a false positive here. // eslint-disable-next-line prefer-const let headerToggles = []; + // This is a debugging tool for the threshold which you can enable in the console. + let thresholdDebug = false; + + // Updates the threshold based on the scroll position. + function updateThreshold() { + const scrollTop = window.pageYOffset || document.documentElement.scrollTop; + const windowHeight = window.innerHeight; + const documentHeight = document.documentElement.scrollHeight; + + // The number of pixels below the viewport, at most documentHeight. + // This is used to push the threshold down to the bottom of the page + // as the user scrolls towards the bottom. + const pixelsBelow = Math.max(0, documentHeight - (scrollTop + windowHeight)); + // The number of pixels above the viewport, at least defaultDownThreshold. + // Similar to pixelsBelow, this is used to push the threshold back towards + // the top when reaching the top of the page. + const pixelsAbove = Math.max(0, defaultDownThreshold - scrollTop); + // How much the threshold should be offset once it gets close to the + // bottom of the page. + const bottomAdd = Math.max(0, windowHeight - pixelsBelow - defaultDownThreshold); + let adjustedBottomAdd = bottomAdd; + + // Adjusts bottomAdd for a small document. The calculation above + // assumes the document is at least twice the windowheight in size. If + // it is less than that, then bottomAdd needs to be shrunk + // proportional to the difference in size. + if (documentHeight < windowHeight * 2) { + const maxPixelsBelow = documentHeight - windowHeight; + const t = 1 - pixelsBelow / maxPixelsBelow; + const clamp = Math.max(0, Math.min(1, t)); + adjustedBottomAdd *= clamp; + } + + let scrollingDown = true; + if (scrollTop < lastKnownScrollPosition) { + scrollingDown = false; + } + + if (scrollingDown) { + // When scrolling down, move the threshold up towards the default + // downwards threshold position. If near the bottom of the page, + // adjustedBottomAdd will offset the threshold towards the bottom + // of the page. + const amountScrolledDown = scrollTop - lastKnownScrollPosition; + const adjustedDefault = defaultDownThreshold + adjustedBottomAdd; + threshold = Math.max(adjustedDefault, threshold - amountScrolledDown); + } else { + // When scrolling up, move the threshold down towards the default + // upwards threshold position. If near the bottom of the page, + // quickly transition the threshold back up where it normally + // belongs. + const amountScrolledUp = lastKnownScrollPosition - scrollTop; + const adjustedDefault = defaultUpThreshold - pixelsAbove + + Math.max(0, adjustedBottomAdd - defaultDownThreshold); + threshold = Math.min(adjustedDefault, threshold + amountScrolledUp); + } + + if (documentHeight <= windowHeight) { + threshold = 0; + } + + if (thresholdDebug) { + const id = 'mdbook-threshold-debug-data'; + let data = document.getElementById(id); + if (data === null) { + data = document.createElement('div'); + data.id = id; + data.style.cssText = ` + position: fixed; + top: 50px; + right: 10px; + background-color: 0xeeeeee; + z-index: 9999; + pointer-events: none; + `; + document.body.appendChild(data); + } + data.innerHTML = ` +
| documentHeight | ${documentHeight.toFixed(1)} |
| windowHeight | ${windowHeight.toFixed(1)} |
| scrollTop | ${scrollTop.toFixed(1)} |
| pixelsAbove | ${pixelsAbove.toFixed(1)} |
| pixelsBelow | ${pixelsBelow.toFixed(1)} |
| bottomAdd | ${bottomAdd.toFixed(1)} |
| adjustedBottomAdd | ${adjustedBottomAdd.toFixed(1)} |
| scrollingDown | ${scrollingDown} |
| threshold | ${threshold.toFixed(1)} |