From 05dc9211cc7a0661a09574ca64e5b0d1b77ff637 Mon Sep 17 00:00:00 2001 From: Erik Demaine Date: Wed, 8 Aug 2018 00:22:06 -0400 Subject: [PATCH] ScrollSpy for table of contents (#1557) * ScrollSpy for table of contents * Add reference * Incorporate comments * Simplify headingsCache and change ScrollSpy reference * Add comment * Fix lint errors * Switch back to throlling, update comments --- website/siteConfig.js | 5 +++- website/static/css/custom.css | 14 +++++++++++ website/static/js/scrollspy.js | 46 ++++++++++++++++++++++++++++++++++ 3 files changed, 64 insertions(+), 1 deletion(-) create mode 100644 website/static/js/scrollspy.js diff --git a/website/siteConfig.js b/website/siteConfig.js index 803b822c..9d15b734 100644 --- a/website/siteConfig.js +++ b/website/siteConfig.js @@ -66,7 +66,10 @@ const siteConfig = { markdownPlugins: [require('./remarkableKatex'), require('./empty_thead')], - scripts: ['https://buttons.github.io/buttons.js'], + scripts: [ + 'https://buttons.github.io/buttons.js', + '/js/scrollspy.js', + ], stylesheets: ['https://cdn.jsdelivr.net/npm/katex@0.10.0-beta/dist/katex.min.css'], separateCss: ['static/static', 'static\\static'], diff --git a/website/static/css/custom.css b/website/static/css/custom.css index b06a9ee9..c0612657 100644 --- a/website/static/css/custom.css +++ b/website/static/css/custom.css @@ -1,3 +1,17 @@ +ul.toc-headings > li { + padding-bottom: 0px; /* moved to li > a */ +} + +.toc-headings > li > a { + display: block; + padding: 4px; +} + +.toc-headings > li > a.active { + background-color: rgba(27, 31, 35, 0.05); + font-weight: bold; +} + .fixedHeaderContainer header img { height: 80%; } diff --git a/website/static/js/scrollspy.js b/website/static/js/scrollspy.js new file mode 100644 index 00000000..0b2987c2 --- /dev/null +++ b/website/static/js/scrollspy.js @@ -0,0 +1,46 @@ +// Inspired by ScrollSpy as in e.g. Bootstrap + +(function() { + const OFFSET = 10; + let timer; + let headingsCache; + const findHeadings = () => headingsCache ? headingsCache : + document.querySelectorAll('.toc-headings > li > a'); + const onScroll = () => { + if (timer) { // throttle + return; + } + timer = setTimeout(() => { + timer = null; + let found = false; + const headings = findHeadings(); + for (let i = 0; i < headings.length; i++) { + // if !found and i is the last element, highlight the last + let current = !found; + if (!found && i < headings.length - 1) { + const next = headings[i + 1].href.split('#')[1]; + const nextHeader = document.getElementById(next); + const top = nextHeader.getBoundingClientRect().top; + // The following tests whether top + scrollTop + // (the top of the header) is greater than scrollTop + // (where scrollTop = window.pageYOffset, the top of + // the window), with OFFSET pixels of slop. + current = top > OFFSET; + } + if (current) { + found = true; + headings[i].className = "active"; + } else { + headings[i].className = ""; + } + } + }, 100); + }; + document.addEventListener('scroll', onScroll); + document.addEventListener('resize', onScroll); + document.addEventListener('DOMContentLoaded', () => { + // Cache the headings once the page has fully loaded. + headingsCache = findHeadings(); + onScroll(); + }); +})();