ui: prevent layout shift when mobile search overlay opens

- SearchPanel: focus input with { preventScroll: true } so the browser
  doesn't auto-scroll the underlying page when the overlay mounts.
- SearchPanel: add overscroll-contain on the scroll container so
  reaching the panel's edges does not chain into the body on Android.
- PublicLayout: lock background scroll while the mobile search overlay
  is open using the iOS-compatible position-fixed + restore pattern,
  so the page underneath cannot move at all.
This commit is contained in:
TerryM
2026-05-30 01:02:18 +08:00
parent d19f2f9efa
commit cb14cb166a
2 changed files with 35 additions and 2 deletions

View File

@@ -70,7 +70,11 @@ export function SearchPanel({
const langParam = useMemo(() => langQuery(lang), [lang]); const langParam = useMemo(() => langQuery(lang), [lang]);
useEffect(() => { useEffect(() => {
inputRef.current?.focus(); // Avoid scroll-into-view: browsers default-scroll the focused element
// into the viewport, which moves the underlying page when the search
// overlay opens from a scrolled position. `preventScroll` keeps the page
// exactly where it was.
inputRef.current?.focus({ preventScroll: true });
}, []); }, []);
useEffect(() => { useEffect(() => {
@@ -133,7 +137,7 @@ export function SearchPanel({
}; };
return ( return (
<div className="ark-header-menu-enter fixed inset-x-0 bottom-0 top-[64px] z-50 overflow-y-auto bg-[#0f0f13] md:hidden"> <div className="ark-header-menu-enter fixed inset-x-0 bottom-0 top-[64px] z-50 overflow-y-auto overscroll-contain bg-[#0f0f13] md:hidden">
<div className="border-t border-white/10 px-5 pb-6 pt-3 max-[360px]:px-3"> <div className="border-t border-white/10 px-5 pb-6 pt-3 max-[360px]:px-3">
<div className="flex h-12 items-center gap-2"> <div className="flex h-12 items-center gap-2">
<div className="flex h-11 min-w-0 flex-1 items-center gap-2 rounded-full border border-ark-gold bg-[#191921] px-3 shadow-[0_0_0_2px_rgba(245,180,53,0.12)]"> <div className="flex h-11 min-w-0 flex-1 items-center gap-2 rounded-full border border-ark-gold bg-[#191921] px-3 shadow-[0_0_0_2px_rgba(245,180,53,0.12)]">

View File

@@ -331,6 +331,35 @@ export function PublicLayout() {
}; };
}, [open]); }, [open]);
// Lock background scroll while the mobile search overlay is open.
// Uses the iOS-compatible position-fixed pattern so the underlying page
// doesn't move at all (overflow:hidden alone is not enough on iOS Safari).
useEffect(() => {
if (!mobileSearchOpen) return;
const scrollY = window.scrollY;
const body = document.body;
const prev = {
position: body.style.position,
top: body.style.top,
left: body.style.left,
right: body.style.right,
width: body.style.width,
};
body.style.position = "fixed";
body.style.top = `-${scrollY}px`;
body.style.left = "0";
body.style.right = "0";
body.style.width = "100%";
return () => {
body.style.position = prev.position;
body.style.top = prev.top;
body.style.left = prev.left;
body.style.right = prev.right;
body.style.width = prev.width;
window.scrollTo(0, scrollY);
};
}, [mobileSearchOpen]);
return ( return (
<div className="min-h-full flex flex-col"> <div className="min-h-full flex flex-col">
<DocumentMeta /> <DocumentMeta />