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

使用Headless UI Disclosure打开侧边菜单时,如何禁用Body滚动以避免双Y轴滚动条

解决Headless UI Disclosure菜单打开时禁用Body滚动的方案

这个问题太常见了,双滚动条确实拉低用户体验,我给你分享几个实际项目中用过的靠谱方案:

方案一:利用Disclosure状态+useEffect精准控制Body样式

核心思路是监听Disclosure的open状态,动态修改Body的CSS属性,同时处理滚动位置防止页面跳转:

import { Disclosure } from '@headlessui/react';
import { useEffect, useState } from 'react';

function SideMenu() {
  const [scrollPosition, setScrollPosition] = useState(0);

  return (
    <Disclosure>
      {({ open }) => {
        useEffect(() => {
          if (open) {
            // 记录当前滚动位置,避免关闭菜单后页面跳顶
            setScrollPosition(window.scrollY);
            // 禁用Body滚动,同时固定位置防止内容偏移
            document.body.style.overflow = 'hidden';
            document.body.style.position = 'fixed';
            document.body.style.top = `-${scrollPosition}px`;
            document.body.style.width = '100%';
            
            // 可选:弥补滚动条消失导致的页面宽度变化
            const scrollBarWidth = window.innerWidth - document.documentElement.clientWidth;
            document.body.style.paddingRight = `${scrollBarWidth}px`;
          } else {
            // 恢复Body默认样式
            document.body.style.overflow = '';
            document.body.style.position = '';
            document.body.style.top = '';
            document.body.style.width = '';
            document.body.style.paddingRight = '';
            // 回到之前的滚动位置
            window.scrollTo(0, scrollPosition);
          }
        }, [open]);

        return (
          <>
            <Disclosure.Button className="p-2">打开侧边菜单</Disclosure.Button>
            <Disclosure.Panel className="overflow-auto w-64 h-screen bg-white">
              {/* 这里放菜单内容,比如导航项 */}
              <div className="p-4">菜单选项1</div>
              <div className="p-4">菜单选项2</div>
              {/* 更多内容... */}
            </Disclosure.Panel>
          </>
        );
      }}
    </Disclosure>
  );
}

方案二:结合Tailwind CSS条件类简化操作

如果你用Tailwind,可以把状态提升到父组件,直接给Body或根容器加条件类:

// 根组件(比如App.js)
import { useState } from 'react';
import SideMenu from './SideMenu';

function App() {
  const [isMenuOpen, setIsMenuOpen] = useState(false);

  return (
    <div className={isMenuOpen ? 'overflow-hidden' : ''}>
      <SideMenu onOpenChange={setIsMenuOpen} />
      {/* 页面其他内容 */}
    </div>
  );
}

// SideMenu组件
import { Disclosure } from '@headlessui/react';

function SideMenu({ onOpenChange }) {
  return (
    <Disclosure onOpenChange={onOpenChange}>
      {({ open }) => (
        <>
          <Disclosure.Button>打开菜单</Disclosure.Button>
          <Disclosure.Panel className="overflow-auto w-64 h-screen">
            {/* 菜单内容 */}
          </Disclosure.Panel>
        </>
      )}
    </Disclosure>
  );
}

优化细节:避免样式闪烁

如果用useEffect偶尔出现短暂的双滚动条闪烁,可以换成useLayoutEffect,它会在DOM渲染完成前执行,样式同步更及时:

import { useLayoutEffect } from 'react';

// 把之前的useEffect替换成useLayoutEffect
useLayoutEffect(() => {
  // 同样的样式控制逻辑
}, [open]);

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.27 19:04:05