()
| 189 | } |
| 190 | |
| 191 | function scrollToHash() { |
| 192 | if (typeof window === "undefined") return () => {}; |
| 193 | const hash = decodeHash(window.location.hash.slice(1)); |
| 194 | const debug = isDebug(); |
| 195 | if (debug) console.log("[HashScroller] scrollToHash", { hash, location: window.location.href }); |
| 196 | if (!hash) return () => {}; |
| 197 | |
| 198 | let cancelled = false; |
| 199 | const tryScroll = () => { |
| 200 | if (cancelled) return; |
| 201 | // 后退/前进还原阅读位置时(ScrollMemory 已接管),让位——否则会把页面又拽到 |
| 202 | // 锚点,盖掉用户离开时的真实滚动位置。所有滚动路径(路由变化 rAF、同页 |
| 203 | // hashchange)都汇到这里,故只在此判一次即可。同页脚注点击属「全新到达」、 |
| 204 | // ScrollMemory 不会 claim,不受影响。 |
| 205 | if (isScrollClaimed()) return; |
| 206 | const found = document.getElementById(hash); |
| 207 | if (!found) { |
| 208 | if (debug) console.warn("[HashScroller] element NOT found at scroll time", hash); |
| 209 | return; |
| 210 | } |
| 211 | // 表格锚点落在单元格上 → 归一到整张 <table>,否则会停在表格中部看不到表头 |
| 212 | const fresh = resolveBlockTarget(found); |
| 213 | const ancestor = findScrollableAncestor(fresh); |
| 214 | const elRect = fresh.getBoundingClientRect(); |
| 215 | if (debug) { |
| 216 | const isWin = ancestor instanceof Window; |
| 217 | console.log("[HashScroller] scrolling now", { |
| 218 | hash, |
| 219 | container: isWin ? "window" : "main", |
| 220 | containerScrollTop: isWin ? window.scrollY : (ancestor as HTMLElement).scrollTop, |
| 221 | containerScrollHeight: isWin |
| 222 | ? document.documentElement.scrollHeight |
| 223 | : (ancestor as HTMLElement).scrollHeight, |
| 224 | containerClientHeight: isWin |
| 225 | ? window.innerHeight |
| 226 | : (ancestor as HTMLElement).clientHeight, |
| 227 | elementTop: elRect.top, |
| 228 | elementText: fresh.textContent?.slice(0, 50), |
| 229 | }); |
| 230 | } |
| 231 | scrollElementToTop(fresh); |
| 232 | // 滚动以块起点为准(标题→标题本身),高亮则可跨多元素(标题 + 其下正文) |
| 233 | flashHighlight(resolveHighlightTargets(fresh)); |
| 234 | }; |
| 235 | |
| 236 | const onTimeout = () => { |
| 237 | if (debug) { |
| 238 | console.warn( |
| 239 | "[HashScroller] layout 未在 ~650ms 内稳定;仍尝试滚动,但位置可能因后续异步内容(图片/dynamic import)漂移", |
| 240 | { hash }, |
| 241 | ); |
| 242 | } |
| 243 | }; |
| 244 | |
| 245 | // Case A: 元素已在 DOM → 等 layout 稳定再滚(防止后续 mutations 让位置失效) |
| 246 | const existing = document.getElementById(hash); |
| 247 | if (existing) { |
| 248 | whenLayoutStable(existing, tryScroll, onTimeout); |
no test coverage detected