如何防止移动端导航菜单展开时页面背景滚动

2次阅读

如何防止移动端导航菜单展开时页面背景滚动

react 应用中实现侧滑导航菜单时,常因 position: fixed 元素未阻断触摸事件传递,导致用户滑动菜单仍可滚动背后内容。本文提供零跳转、无闪屏的纯 css + javascript 解决方案,兼容主流移动浏览器

当移动端导航菜单(如 80vw 宽度的 fixed 侧栏)展开时,用户滑动菜单区域仍可触发底层页面滚动——这并非 bug,而是浏览器对 touchmove 事件的默认冒泡行为。单纯给

添加 overflow: hidden 会重置滚动位置(跳回顶部),破坏用户体验;而仅设置 position: fixed 或 z-index 无法阻止事件穿透。

推荐方案:css + javaScript 协同控制

1. CSS 层面:锁定 body 滚动,保持当前位置
不使用 overflow: hidden(会重置 scrollY),改用 overscroll-behavior: none 配合 position: fixed 的精巧组合:

/* 锁定 body,但保留当前滚动偏移 */ body.no-scroll {   position: fixed;   width: 100%;   top: calc(-1 * var(--scroll-y, 0px)); /* 抵消滚动位移 */   overscroll-behavior: none; }  /* 确保所有子元素不触发滚动链 */ body.no-scroll * {   overscroll-behavior: none; }

2. javascript 层面:记录并恢复滚动位置
react 组件中管理导航状态时,动态注入滚动偏移量:

import { useEffect, useRef } from 'react';  export function usePreventBodyScroll(isOpen: boolean) {   const scrollYRef = useRef(0);    useEffect(() => {     if (isOpen) {       // 记录当前滚动位置       scrollYRef.current = window.scrollY;       // 添加内联样式锁定 body       document.body.style.cssText = `         position: fixed;         width: 100%;         top: ${-scrollYRef.current}px;         overscroll-behavior: none;       `;       document.body.classlist.add('no-scroll');     } else {       // 恢复滚动位置(无跳动)       document.body.style.cssText = '';       document.body.classList.remove('no-scroll');       window.scrollTo(0, scrollYRef.current);     }   }, [isOpen]); }  // 在组件中使用 function MobileNav({ isOpen }: { isOpen: boolean }) {   usePreventBodyScroll(isOpen);   return (        ); }

3. 补充增强:阻止 touchmove 冒泡(ios 兜底)
针对 iOS safari 对 overscroll-behavior 支持较晚(iOS 16+),添加轻量级事件拦截:

useEffect(() => {   const handleTouchMove = (e: TouchEvent) => {     if (isOpen && e.target === document.body) {       e.preventDefault(); // 阻止默认滚动     }   };    if (isOpen) {     document.body.addEventListener('touchmove', handleTouchMove, { passive: false });   }   return () => {     document.body.removeEventListener('touchmove', handleTouchMove);   }; }, [isOpen]);

⚠️ 注意事项

  • 避免全局 document.body.style.overflow = ‘hidden’ —— 它会强制重置 scrollTop;
  • top: calc(-1 * var(–scroll-y)) 方案比 transform: translateY() 更稳定,避免 Safari 渲染抖动;
  • 若应用使用了 html { overflow-x: hidden },需同步为 html.no-scroll { overflow: hidden };
  • 在服务端渲染(SSR)场景下,确保 useEffect 逻辑仅在客户端执行。

该方案已在 iOS 15+/android chrome 110+ 实测通过,滑动菜单时背景完全静止,关闭后无缝恢复原滚动位置,无视觉跳变,符合 WCAG 可访问性要求。

text=ZqhQzanResources