fix:解决移动端 Dialog 滚动问题

This commit is contained in:
hzm
2026-03-12 21:53:11 +08:00
parent 7953b906a5
commit 8849b547ce
3 changed files with 49 additions and 27 deletions

View File

@@ -0,0 +1,18 @@
import { useEffect } from "react";
export function useBodyScrollLock(open) {
useEffect(() => {
if (!open) return;
const scrollY = window.scrollY;
document.body.style.position = "fixed";
document.body.style.top = `-${scrollY}px`;
return () => {
document.body.style.position = "";
document.body.style.top = "";
window.scrollTo(0, scrollY);
};
}, [open]);
}

View File

@@ -6,11 +6,38 @@ import { Dialog as DialogPrimitive } from "radix-ui"
import { cn } from "@/lib/utils" import { cn } from "@/lib/utils"
import {CloseIcon} from "@/app/components/Icons"; import {CloseIcon} from "@/app/components/Icons";
import { useBodyScrollLock } from "../../app/hooks/useBodyScrollLock";
function Dialog({ function Dialog({
open: openProp,
defaultOpen,
onOpenChange,
...props ...props
}) { }) {
return <DialogPrimitive.Root data-slot="dialog" {...props} />; const [uncontrolledOpen, setUncontrolledOpen] = React.useState(defaultOpen ?? false);
const isControlled = openProp !== undefined;
const currentOpen = isControlled ? openProp : uncontrolledOpen;
// 使用全局 hook 统一处理 body 滚动锁定 & 恢复,避免弹窗打开时页面跳到顶部
useBodyScrollLock(currentOpen);
const handleOpenChange = React.useCallback(
(next) => {
if (!isControlled) setUncontrolledOpen(next);
onOpenChange?.(next);
},
[isControlled, onOpenChange]
);
return (
<DialogPrimitive.Root
data-slot="dialog"
open={isControlled ? openProp : undefined}
defaultOpen={defaultOpen}
onOpenChange={handleOpenChange}
{...props}
/>
);
} }
function DialogTrigger({ function DialogTrigger({

View File

@@ -4,6 +4,7 @@ import * as React from "react"
import { Drawer as DrawerPrimitive } from "vaul" import { Drawer as DrawerPrimitive } from "vaul"
import { cn } from "@/lib/utils" import { cn } from "@/lib/utils"
import { useBodyScrollLock } from "../../app/hooks/useBodyScrollLock"
const DrawerScrollLockContext = React.createContext(null) const DrawerScrollLockContext = React.createContext(null)
@@ -12,36 +13,12 @@ const DrawerScrollLockContext = React.createContext(null)
* 既锁定滚动又保留视觉位置overlay 上 ontouchmove preventDefault 防止背景触摸滚动。 * 既锁定滚动又保留视觉位置overlay 上 ontouchmove preventDefault 防止背景触摸滚动。
*/ */
function useScrollLock(open) { function useScrollLock(open) {
const savedScrollYRef = React.useRef(0)
const onOverlayTouchMove = React.useCallback((e) => { const onOverlayTouchMove = React.useCallback((e) => {
e.preventDefault() e.preventDefault()
}, []) }, [])
React.useEffect(() => { // 统一使用 app 级 hook 处理 body 滚动锁定 & 恢复,避免多处实现导致位移/跳顶问题
if (!open || typeof document === "undefined") return useBodyScrollLock(open)
const scrollY = window.scrollY ?? window.pageYOffset
savedScrollYRef.current = scrollY
const prev = {
position: document.body.style.position,
top: document.body.style.top,
left: document.body.style.left,
right: document.body.style.right,
width: document.body.style.width,
}
document.body.style.position = "fixed"
document.body.style.top = `-${scrollY}px`
document.body.style.left = "0"
document.body.style.right = "0"
document.body.style.width = "100%"
return () => {
document.body.style.position = prev.position
document.body.style.top = prev.top
document.body.style.left = prev.left
document.body.style.right = prev.right
document.body.style.width = prev.width
window.scrollTo(0, savedScrollYRef.current)
}
}, [open])
return React.useMemo( return React.useMemo(
() => (open ? { onTouchMove: onOverlayTouchMove } : null), () => (open ? { onTouchMove: onOverlayTouchMove } : null),