You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Next.js中移动端导航栏打开时禁用页面滚动的实现方案

在Next.js中实现移动端导航打开时禁用页面滚动

Next.js的服务端渲染特性确实会导致直接操作document对象时出现问题,以下是几种实用的解决方案:

方法一:在导航组件内结合useState与useEffect处理

利用useEffect会在组件客户端挂载后执行的特性,在菜单状态变化时动态修改body的滚动属性:

import { useState, useEffect } from 'react';
import styles from './MobileNav.module.css';

const MobileNav = () => {
  const [isMenuOpen, setIsMenuOpen] = useState(false);

  useEffect(() => {
    // 根据菜单状态切换body的滚动限制
    if (isMenuOpen) {
      document.body.classList.add(styles.noScroll);
    } else {
      document.body.classList.remove(styles.noScroll);
    }

    // 组件卸载时恢复默认状态,避免影响其他页面
    return () => {
      document.body.classList.remove(styles.noScroll);
    };
  }, [isMenuOpen]);

  return (
    <div className={styles.navContainer}>
      <button 
        onClick={() => setIsMenuOpen(!isMenuOpen)}
        className={styles.toggleBtn}
      >
        {isMenuOpen ? '关闭菜单' : '打开菜单'}
      </button>
      
      {isMenuOpen && (
        <div className={styles.menuOverlay}>
          {/* 菜单选项内容 */}
          <ul>
            <li>首页</li>
            <li>关于我们</li>
            <li>联系我们</li>
          </ul>
        </div>
      )}
    </div>
  );
};

export default MobileNav;

对应的CSS(MobileNav.module.css):

.noScroll {
  overflow: hidden;
}

.navContainer {
  /* 导航容器样式 */
}

.toggleBtn {
  /* 切换按钮样式 */
}

.menuOverlay {
  /* 菜单遮罩样式 */
  position: fixed;
  top: 0;
  left: 0;
  width: 100%;
  height: 100vh;
  background: white;
  z-index: 100;
}

方法二:通过React Context管理全局菜单状态

如果导航组件是全局布局的一部分(比如放在_app.js中),可以用Context把菜单状态提升到全局,统一处理滚动禁用:

1. 创建导航Context

// contexts/NavContext.js
import { createContext, useContext, useState, useEffect } from 'react';

const NavContext = createContext();

export const NavProvider = ({ children }) => {
  const [isMenuOpen, setIsMenuOpen] = useState(false);

  useEffect(() => {
    document.body.classList.toggle('no-scroll', isMenuOpen);
    return () => {
      document.body.classList.remove('no-scroll');
    };
  }, [isMenuOpen]);

  return (
    <NavContext.Provider value={{ isMenuOpen, setIsMenuOpen }}>
      {children}
    </NavContext.Provider>
  );
};

export const useNav = () => useContext(NavContext);

2. 在_app.js中包裹全局组件

// pages/_app.js
import { NavProvider } from '../contexts/NavContext';
import '../styles/globals.css';

function MyApp({ Component, pageProps }) {
  return (
    <NavProvider>
      <Component {...pageProps} />
    </NavProvider>
  );
}

export default MyApp;

3. 在导航组件中使用Context

import { useNav } from '../contexts/NavContext';
import styles from './MobileNav.module.css';

const MobileNav = () => {
  const { isMenuOpen, setIsMenuOpen } = useNav();

  return (
    <div className={styles.navContainer}>
      <button 
        onClick={() => setIsMenuOpen(!isMenuOpen)}
        className={styles.toggleBtn}
      >
        {isMenuOpen ? '关闭菜单' : '打开菜单'}
      </button>
      
      {isMenuOpen && (
        <div className={styles.menuOverlay}>
          {/* 菜单内容 */}
        </div>
      )}
    </div>
  );
};

export default MobileNav;

方法三:动态导入导航组件并禁用SSR

如果你的导航组件不需要服务端渲染,可以用next/dynamic动态导入并关闭SSR,这样组件只会在客户端渲染,可直接安全操作document:

// 导入动态组件
import dynamic from 'next/dynamic';

const MobileNav = dynamic(() => import('../components/MobileNav'), {
  ssr: false,
});

export default function Layout({ children }) {
  return (
    <div>
      <MobileNav />
      <main>{children}</main>
    </div>
  );
}

导航组件内的逻辑和方法一一致即可。


内容的提问来源于stack exchange,提问作者cl118

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.05 23:50:12