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 { 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({
|
||||||
|
|||||||
@@ -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),
|
||||||
|
|||||||
Reference in New Issue
Block a user