From 8a24f02782b7f2454ef67d6ece0b62b808175a21 Mon Sep 17 00:00:00 2001 From: "(quasar) nebula" Date: Mon, 27 Feb 2023 10:30:12 -0400 Subject: smooth hash link scrolling & anchor scroll offset --- src/static/client.js | 43 ++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 42 insertions(+), 1 deletion(-) (limited to 'src/static/client.js') diff --git a/src/static/client.js b/src/static/client.js index 15f21fdb..87b74004 100644 --- a/src/static/client.js +++ b/src/static/client.js @@ -444,6 +444,47 @@ if (localStorage.tryInfoCards) { addInfoCardLinkHandlers('track'); } +// Custom hash links -------------------------------------- + +function addHashLinkHandlers() { + // Instead of defining a scroll offset (to account for the sticky heading) + // in JavaScript, we interface with the CSS property 'scroll-margin-top'. + // This lets the scroll offset be consolidated where it makes sense, and + // sets an appropriate offset when (re)loading a page with hash for free! + + for (const a of document.links) { + const href = a.getAttribute('href'); + if (!href || !href.startsWith('#')) { + continue; + } + + a.addEventListener('click', handleHashLinkClicked); + } + + function handleHashLinkClicked(evt) { + if (evt.metaKey || evt.shiftKey || evt.ctrlKey || evt.altKey) { + return; + } + + const href = evt.target.getAttribute('href'); + const id = href.slice(1); + const linked = document.getElementById(id); + const box = linked.getBoundingClientRect(); + const style = window.getComputedStyle(linked); + + const scrollY = + window.scrollY + + box.top + - style['scroll-margin-top'].replace('px', ''); + + evt.preventDefault(); + history.pushState({}, '', href); + window.scrollTo({top: scrollY, behavior: 'smooth'}); + } +} + +addHashLinkHandlers(); + // Sticky content heading --------------------------------- const stickyHeadingInfo = Array.from(document.querySelectorAll('.content-sticky-heading-container')) @@ -510,7 +551,7 @@ function updateStickyHeading() { for (let i = contentHeadings.length - 1; i >= 0; i--) { const heading = contentHeadings[i]; const headingRect = heading.getBoundingClientRect(); - if (headingRect.y + headingRect.height / 1.5 < stickyBottom) { + if (headingRect.y + headingRect.height / 1.5 < stickyBottom + 20) { closestHeading = heading; break; } -- cgit 1.3.0-6-gf8a5 From 6d8f75dd5873f1427a343971edd0e0ea40b015a5 Mon Sep 17 00:00:00 2001 From: "(quasar) nebula" Date: Tue, 28 Feb 2023 19:50:01 -0400 Subject: hash link highlighting & additional skippers --- src/static/client.js | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) (limited to 'src/static/client.js') diff --git a/src/static/client.js b/src/static/client.js index 87b74004..4eb1d2ba 100644 --- a/src/static/client.js +++ b/src/static/client.js @@ -452,6 +452,8 @@ function addHashLinkHandlers() { // This lets the scroll offset be consolidated where it makes sense, and // sets an appropriate offset when (re)loading a page with hash for free! + let wasHighlighted; + for (const a of document.links) { const href = a.getAttribute('href'); if (!href || !href.startsWith('#')) { @@ -469,6 +471,17 @@ function addHashLinkHandlers() { const href = evt.target.getAttribute('href'); const id = href.slice(1); const linked = document.getElementById(id); + + if (!linked) { + return; + } + + // Hide skipper box right away, so the layout is updated on time for the + // math operations coming up next. + const skipper = document.getElementById('skippers'); + skipper.style.display = 'none'; + setTimeout(() => skipper.style.display = ''); + const box = linked.getBoundingClientRect(); const style = window.getComputedStyle(linked); @@ -480,6 +493,27 @@ function addHashLinkHandlers() { evt.preventDefault(); history.pushState({}, '', href); window.scrollTo({top: scrollY, behavior: 'smooth'}); + linked.focus({preventScroll: true}); + + const maxScroll = + document.body.scrollHeight + - window.innerHeight; + + if (scrollY > maxScroll && linked.classList.contains('content-heading')) { + if (wasHighlighted) { + wasHighlighted.classList.remove('highlight-hash-link'); + } + + wasHighlighted = linked; + linked.classList.add('highlight-hash-link'); + linked.addEventListener('animationend', function handle(evt) { + if (evt.animationName === 'highlight-hash-link') { + linked.removeEventListener('animationend', handle); + linked.classList.remove('highlight-hash-link'); + wasHighlighted = null; + } + }); + } } } -- cgit 1.3.0-6-gf8a5