From 9ac072e8d8ccb7bc43869ee826499bbc8196bfae Mon Sep 17 00:00:00 2001 From: TerryM Date: Sat, 30 May 2026 23:47:26 +0800 Subject: [PATCH] fix: jump to top on page change before post deep-link alignment Entering /browse via a popular-section card carried ?post=, which made ScrollToTop skip the reset entirely. The window stayed at the previous page's bottom scroll, so the deep-link animation visibly scrolled UP from the bottom to the target post. Now any pathname change jumps to the top first (even with ?post=), letting the destination align to the post by scrolling down from the top. Hash anchors and same-page ?post= changes are still left alone. Co-Authored-By: Claude Opus 4.8 (1M context) --- src/components/ScrollToTop.tsx | 42 ++++++++++++++++++++++++++-------- 1 file changed, 33 insertions(+), 9 deletions(-) diff --git a/src/components/ScrollToTop.tsx b/src/components/ScrollToTop.tsx index 8727d49..89d5ca4 100644 --- a/src/components/ScrollToTop.tsx +++ b/src/components/ScrollToTop.tsx @@ -1,21 +1,45 @@ -import { useEffect } from "react"; +import { useEffect, useRef } from "react"; import { useLocation } from "react-router-dom"; /** - * Resets the window to the top on every route change. React Router does not - * restore scroll on client navigation, so without this a short new page would - * clamp to wherever the previous (taller) page was scrolled — e.g. landing at - * the bottom of a category page after clicking a card far down the home grid. + * Resets the window to the top on client navigation. React Router does not + * restore scroll on its own, so without this a short new page would clamp to + * wherever the previous (taller) page was scrolled — e.g. landing at the bottom + * of a category page after clicking a card far down the home grid. * - * Skips navigations that carry a hash (`#post-`, `#categories`, …) or a - * `?post=` deep link so the target page can handle its own alignment. + * Navigating to a *different* page always jumps to the top, even when the URL + * carries `?post=`. The destination's own deep-link logic then aligns to + * that post by scrolling DOWN from the top, instead of animating UP from the + * previous page's bottom scroll — which looked like the page "scrolling up from + * the bottom" after tapping a popular-section card. + * + * A hash (`#post-`, `#categories`, …) is left alone so the target page can + * handle its own anchor alignment. A same-page `?post=` change (clicking + * another card while already on the list) is also left alone so it doesn't + * fight the in-page alignment. */ export function ScrollToTop() { const { pathname, search, hash } = useLocation(); + const prevPathname = useRef(pathname); useEffect(() => { - if (hash || new URLSearchParams(search).has("post")) return; - window.scrollTo({ top: 0, left: 0 }); + const pathnameChanged = prevPathname.current !== pathname; + prevPathname.current = pathname; + + if (hash) return; + + // Entering a new page: always start at the top (post deep-links align + // afterwards from here). + if (pathnameChanged) { + window.scrollTo({ top: 0, left: 0 }); + return; + } + + // Same page, search-only change (e.g. switching sort/filter): reset to top + // unless it's an in-page post deep-link, which handles its own alignment. + if (!new URLSearchParams(search).has("post")) { + window.scrollTo({ top: 0, left: 0 }); + } }, [pathname, search, hash]); return null;