如何在 React 中精准禁用指定 div 的滚动与交互(非全局屏蔽)

1次阅读

如何在 React 中精准禁用指定 div 的滚动与交互(非全局屏蔽)

本文详解在 react 应用中,当移动端侧边栏(如导航栏)展开时,如何仅禁用内容区域()的滚动和点击行为,同时保持侧边栏自身可滚动、页面其他区域功能正常——避免误用 body 级滚动锁定导致体验降级。

本文详解在 React 应用中,当移动端侧边栏(如导航栏)展开时,如何仅禁用内容区域(`

`)的滚动和点击行为,同时保持侧边栏自身可滚动、页面其他区域功能正常——避免误用 `body` 级滚动锁定导致体验降级。

在构建响应式 React 应用(尤其是移动端抽屉式侧边栏)时,一个常见需求是:当侧边栏(isOpen === true)覆盖在主内容区上时,主内容区域应完全失去交互能力(不可点击、不可滚动),但侧边栏本身仍需支持内部滚动,且整个页面的 body 滚动不应被冻结(否则会丢失 viewport 位置、触发布局抖动,或影响侧边栏内长列表的滑动体验)。

直接对 .content.behind 应用 pointer-events: none + overflow: hidden 看似合理,但在实践中常失效——原因在于:

  • pointer-events: none 可阻止点击/悬停,但无法阻止键盘滚动(空格键、方向键)或触屏拖拽滚动
  • overflow: hidden 仅隐藏溢出内容,若元素本身具有 scroll 行为(如设置了 overflow-y: auto),其滚动能力仍存在,只是视觉上被裁剪;
  • 更关键的是:滚动事件由底层滚动容器(如 div#content)捕获,而非父级 body;单纯样式控制无法拦截原生滚动行为。

✅ 正确解法:组合 CSS 控制 + 结构化遮罩层(Overlay)

推荐采用「透明遮罩层」方案——它语义清晰、兼容性好、无副作用,且天然支持精准作用域控制:

✅ 推荐实现(React 函数组件示例)

import { useEffect, useRef } from 'react';  export default function Layout({ isOpen, setIsOpen }: {    isOpen: boolean;    setIsOpen: (open: boolean) => void;  }) {   const contentRef = useRef<HTMLDivElement>(null);   const overlayRef = useRef<HTMLDivElement>(null);    // 关键:侧边栏激活时,禁用 content 区域的滚动能力(非仅视觉隐藏)   useEffect(() => {     if (!contentRef.current || !overlayRef.current) return;      const content = contentRef.current;     const overlay = overlayRef.current;      if (isOpen) {       // 1. 移除 content 的滚动能力(阻断所有滚动输入源)       content.style.overflow = 'hidden';       // 2. 插入全屏遮罩,拦截指针事件 & 防止穿透       overlay.style.display = 'block';       overlay.style.pointerEvents = 'auto';       // 3. 可选:降低内容可见度,强化视觉反馈       content.style.opacity = '0.6';     } else {       content.style.overflow = '';       overlay.style.display = 'none';       content.style.opacity = '';     }   }, [isOpen]);    return (     <>       <Navbar isOpen={isOpen} setIsOpen={setIsOpen} />        {/* 遮罩层:覆盖 content 区域,拦截所有交互 */}       <div          ref={overlayRef}         style={{           position: 'fixed',           top: 0,           left: 0,           right: 0,           bottom: 0,           zIndex: 999,           pointerEvents: 'none', // 默认不拦截,仅在 isOpen 时启用           transition: 'opacity 0.3s linear'         }}       />        {/* 主内容区:需具备自身滚动能力 */}       <div          ref={contentRef}         className={isOpen ? "content behind" : "content"}         style={{           maxHeight: '100vh',           overflowY: 'auto', // ✅ 内容区必须声明可滚动           padding: '1rem',           boxSizing: 'border-box'         }}       >         {/* 你的页面内容 */}         <h1>主内容区域</h1>         <p>滚动此处查看效果...</p>         {[...Array(50)].map((_, i) => (           <div key={i} style={{ height: '80px', background: i % 2 ? '#f5f5f5' : '#fff', margin: '4px 0' }}>             内容项 #{i}           </div>         ))}       </div>     </>   ); }

? 样式补充(CSS Module 或全局样式)

.content {   /* 基础滚动容器 */   max-height: 100vh;   overflow-y: auto;   padding: 1rem;   box-sizing: border-box; }  .content.behind {   opacity: 0.6;   /* 注意:不设置 pointer-events: none!由遮罩层统一处理 */ }  /* 遮罩层默认隐藏 */ #overlay {   display: none;   position: fixed;   top: 0;   left: 0;   width: 100%;   height: 100%;   z-index: 999;   background: rgba(0, 0, 0, 0.01); /* 极浅色,确保可点击但无视觉干扰 */   pointer-events: none;   transition: background 0.3s ease; }  #overlay.visible {   display: block;   pointer-events: auto;   background: rgba(0, 0, 0, 0.05); /* 轻微加深,增强禁用感 */ }

⚠️ 关键注意事项

  • 不要修改 body 的 overflow:全局禁用会导致页面跳动、丢失 scroll position,且侧边栏内滚动也会失效;
  • 遮罩层 z-index 必须高于 content 但低于 sidebar(例如 sidebar: z-50, overlay: z-40, content: z-30),确保层级正确;
  • pointer-events: auto on overlay 是核心:它拦截了所有鼠标/触控事件,使底层 content 完全“不可达”;
  • overflow: hidden on content 是双重保险:防止键盘滚动或 programmatic scroll(如 element.scrollTo())意外触发;
  • 移动端兼容性:该方案在 ios safari / android chrome 均稳定生效,无需 -webkit-overflow-scrolling: touch 等过时属性。

✅ 总结

精准禁用某 div 的滚动与交互,本质是事件拦截 + 行为禁用的组合策略。相比侵入式 hooks(如 useScrollBlock)或全局 body 锁定,遮罩层方案更可控、可预测、易调试。它将“禁用状态”显式声明为 ui 层级操作,符合 React 的声明式哲学,也便于后续扩展(如添加淡入动画、点击透传逻辑等)。

text=ZqhQzanResources