fix:解决移动端 Dialog 滚动问题
This commit is contained in:
18
app/hooks/useBodyScrollLock.js
Normal file
18
app/hooks/useBodyScrollLock.js
Normal 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]);
|
||||
}
|
||||
@@ -6,11 +6,38 @@ import { Dialog as DialogPrimitive } from "radix-ui"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
import {CloseIcon} from "@/app/components/Icons";
|
||||
import { useBodyScrollLock } from "../../app/hooks/useBodyScrollLock";
|
||||
|
||||
function Dialog({
|
||||
open: openProp,
|
||||
defaultOpen,
|
||||
onOpenChange,
|
||||
...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({
|
||||
|
||||
@@ -4,6 +4,7 @@ import * as React from "react"
|
||||
import { Drawer as DrawerPrimitive } from "vaul"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
import { useBodyScrollLock } from "../../app/hooks/useBodyScrollLock"
|
||||
|
||||
const DrawerScrollLockContext = React.createContext(null)
|
||||
|
||||
@@ -12,36 +13,12 @@ const DrawerScrollLockContext = React.createContext(null)
|
||||
* 既锁定滚动又保留视觉位置;overlay 上 ontouchmove preventDefault 防止背景触摸滚动。
|
||||
*/
|
||||
function useScrollLock(open) {
|
||||
const savedScrollYRef = React.useRef(0)
|
||||
const onOverlayTouchMove = React.useCallback((e) => {
|
||||
e.preventDefault()
|
||||
}, [])
|
||||
|
||||
React.useEffect(() => {
|
||||
if (!open || typeof document === "undefined") return
|
||||
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])
|
||||
// 统一使用 app 级 hook 处理 body 滚动锁定 & 恢复,避免多处实现导致位移/跳顶问题
|
||||
useBodyScrollLock(open)
|
||||
|
||||
return React.useMemo(
|
||||
() => (open ? { onTouchMove: onOverlayTouchMove } : null),
|
||||
|
||||
Reference in New Issue
Block a user