From 4245c464a4e84d5d0716b77da1b7a76a7dfa74bf Mon Sep 17 00:00:00 2001 From: Rene Jeglinsky Date: Mon, 19 Oct 2026 20:31:26 +0200 Subject: [PATCH] fix: Improve hash scrolling restoration logic for better user experience --- .../restoreCodeGroupPreferences.js | 45 +++++++++++++++---- 1 file changed, 36 insertions(+), 9 deletions(-) diff --git a/.vitepress/lib/code-groups/restoreCodeGroupPreferences.js b/.vitepress/lib/code-groups/restoreCodeGroupPreferences.js index f7cd50bad1..fbc3e775ff 100644 --- a/.vitepress/lib/code-groups/restoreCodeGroupPreferences.js +++ b/.vitepress/lib/code-groups/restoreCodeGroupPreferences.js @@ -89,13 +89,19 @@ } const restoreHashScroll = () => { - if (hashScrollPending) { - history.replaceState(null, '', window.location.pathname + window.location.search + initialHash) - requestAnimationFrame(() => { - scrollToHash(initialHash) - hashScrollPending = false - }) - } + if (!hashScrollPending) return + + // Put the hash back into the URL as soon as we start handling it, so it + // survives even if the target element hasn't been rendered yet. + history.replaceState(null, '', window.location.pathname + window.location.search + initialHash) + + // Don't finish until the target actually exists; the page content may still + // be hydrating. Until then, keep hashScrollPending so later retries can scroll. + const target = document.getElementById(decodeURIComponent(initialHash).slice(1)) + if (!target) return + + hashScrollPending = false + requestAnimationFrame(() => scrollToHash(initialHash)) } const initialCodeGroupCount = applyToAllCodeGroups() @@ -137,13 +143,34 @@ } } + // Guarantee the hash is restored and scrolled to, even on pages that have no + // code groups (where the MutationObserver above never fires) and regardless of + // readyState when this inline head script runs. Retries each frame until the + // target has hydrated, then stops. + const pollRestore = () => { + const deadline = Date.now() + 3000 + const tick = () => { + applyToAllCodeGroups() + restoreHashScroll() + if (hashScrollPending && Date.now() < deadline) { + requestAnimationFrame(tick) + } else { + stopObserving() + } + } + requestAnimationFrame(tick) + } + if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', () => { applyToAllCodeGroups() restoreHashScroll() - stopObserving() + if (hashScrollPending) pollRestore() + else stopObserving() }) - } else if (!hashScrollPending) { + } else if (hashScrollPending) { + pollRestore() + } else { stopObserving() } })() \ No newline at end of file