Skip to content

Commit 99f4b8c

Browse files
kwagyemanclaude
andcommitted
docs: reliably scroll to #anchor after the page settles
The Shibuya theme leaves fragment navigation to the browser, which jumps to the target once on load -- before images (many without intrinsic dimensions), web fonts, and MathJax add height above it and push it down. So deep links (including the IDE's F1) land short, and creep closer on repeat visits as assets cache. Add static/anchor-scroll.js: after DOMContentLoaded/load/fonts.ready and two short delayed passes, re-scroll to the hash target (measuring the real sticky-header height so it clears the navbar). It backs off the moment the user scrolls and re-arms on hashchange. Register it in conf.py html_js_files. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent a0eefbb commit 99f4b8c

2 files changed

Lines changed: 68 additions & 0 deletions

File tree

docs/conf.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -623,6 +623,10 @@ def _render_landing_code(src):
623623
# keep the current language), so even frozen old snapshots list every
624624
# version. See static/nav-versions.js.
625625
"nav-versions.js",
626+
# Re-scroll to the #anchor after the page settles -- images/fonts/MathJax
627+
# shift the target after the browser's one-shot fragment jump, so deep
628+
# links (e.g. IDE F1) land short. See static/anchor-scroll.js.
629+
"anchor-scroll.js",
626630
]
627631
# Add any extra paths that contain custom files (such as robots.txt or
628632
# .htaccess) here, relative to this directory. These files are copied

docs/static/anchor-scroll.js

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
/*
2+
* Reliably land on the #anchor after the page has settled.
3+
*
4+
* The Shibuya theme leaves fragment navigation to the browser, which scrolls to
5+
* the target once on load -- before images (many lack intrinsic dimensions),
6+
* web fonts, and MathJax finish, all of which add height above the target and
7+
* push it down. The browser already scrolled, so you land short; on repeat
8+
* visits the cached assets settle sooner and you land progressively closer.
9+
*
10+
* This re-applies the scroll after the layout settles (load, fonts, and a
11+
* couple of delayed passes), measuring the real sticky-header height so the
12+
* target clears the navbar. The passes are finite and it backs off the moment
13+
* the user scrolls, so it never fights a deliberate scroll.
14+
*/
15+
(function () {
16+
"use strict";
17+
18+
function target() {
19+
var hash = window.location.hash;
20+
if (!hash || hash === "#") return null;
21+
var id;
22+
try { id = decodeURIComponent(hash.slice(1)); } catch (e) { id = hash.slice(1); }
23+
return document.getElementById(id) || document.getElementsByName(id)[0] || null;
24+
}
25+
26+
var lock = true; // allowed to correct until the user takes over
27+
28+
function release() { lock = false; }
29+
30+
["wheel", "touchstart", "keydown"].forEach(function (ev) {
31+
window.addEventListener(ev, release, { passive: true });
32+
});
33+
34+
function settle() {
35+
if (!lock) return;
36+
var el = target();
37+
if (!el) return;
38+
var head = document.querySelector(".sy-head");
39+
var offset = head ? head.getBoundingClientRect().height : 56;
40+
var y = window.pageYOffset + el.getBoundingClientRect().top - offset - 12;
41+
if (y < 0) y = 0;
42+
try { window.scrollTo({ top: y, behavior: "auto" }); }
43+
catch (e) { window.scrollTo(0, y); }
44+
}
45+
46+
function run() {
47+
if (!window.location.hash) return;
48+
lock = true;
49+
settle();
50+
window.addEventListener("load", function () { requestAnimationFrame(settle); }, { once: true });
51+
if (document.fonts && document.fonts.ready) { document.fonts.ready.then(settle); }
52+
setTimeout(settle, 250);
53+
setTimeout(settle, 700);
54+
}
55+
56+
if (document.readyState === "loading") {
57+
document.addEventListener("DOMContentLoaded", run);
58+
} else {
59+
run();
60+
}
61+
62+
// Clicking an in-page "#anchor" link restarts the correction.
63+
window.addEventListener("hashchange", run);
64+
})();

0 commit comments

Comments
 (0)