如何避免 Radix UI Dropdown Menu 打开时锁定页面滚动?
在 DropdownMenu.Root 上设置 modal={false},可以关闭下拉菜单自身的模态滚动锁定,让背景页面保持可滚动。如果页面仍受到其他组件或样式的滚动限制,还需要继续排查。
为什么打开菜单会锁定滚动?
页面原本可以滚动,打开 Dropdown Menu 后 body 滚动被锁定,这与组件默认启用模态模式时的行为一致。
Radix UI 的 DropdownMenu.Root 默认使用 modal={true},如官方 API 文档所示。底层 Menu 实现在模态模式下启用外部滚动锁定,在非模态模式下关闭该限制,详见 Radix Menu 源码。
由于问题没有提供组件代码或依赖版本,无法确定项目中是否还有其他滚动锁定逻辑,可以先检查这个配置。
如何修改?
将 modal={false} 放在 DropdownMenu.Root 上。下面以独立包 @radix-ui/react-dropdown-menu 为例:
import * as DropdownMenu from '@radix-ui/react-dropdown-menu';
export default function ActionsMenu() {
return (
<DropdownMenu.Root modal={false}>
<DropdownMenu.Trigger asChild>
<button type="button">打开菜单</button>
</DropdownMenu.Trigger>
<DropdownMenu.Portal>
<DropdownMenu.Content
sideOffset={6}
style={{
minWidth: 160,
padding: 8,
backgroundColor: 'white',
border: '1px solid #ddd',
borderRadius: 6,
}}
>
<DropdownMenu.Item>查看详情</DropdownMenu.Item>
<DropdownMenu.Item>编辑</DropdownMenu.Item>
</DropdownMenu.Content>
</DropdownMenu.Portal>
</DropdownMenu.Root>
);
}
关键改动是:
<DropdownMenu.Root modal={false}>
modal 是 Root 的配置项,应传给根组件。如果 Dropdown Menu 经过封装,确认该属性已传给底层 DropdownMenu.Root。
设置后仍然无法滚动怎么办?
可以按下面的顺序排查:
- 确认属性已生效:检查封装组件是否遗漏了
modal,或在内部将它固定为true。 - 检查其他模态组件。如果菜单位于 Dialog 等组件中,外层组件可能仍在锁定页面滚动。修改菜单的
modal不会解除其他组件施加的限制。 - 在浏览器开发者工具中,对比菜单打开前后的
html、body和实际滚动容器,检查是否出现overflow: hidden、position: fixed等限制滚动的样式。 - 如果项目监听了
wheel或touchmove,检查相关逻辑是否调用了preventDefault(),从而阻止默认滚动。
验证时,使用内容高度超过视口的页面,并将指针放在菜单外的背景区域测试滚动,以区分背景页面滚动和菜单内部滚动。
使用非模态模式需要注意什么?
modal={false} 会影响 body CSS 之外的行为,包括焦点约束和菜单外部交互。相关实现显示,非模态模式也会关闭焦点陷阱和外部指针事件限制。修改后,应检查键盘导航、按 Escape 关闭菜单,以及点击菜单外部时的行为是否符合需求。
优先通过组件提供的 modal 配置解决问题。直接强制覆盖 body 的 overflow,可能无法解除事件层面的滚动拦截,也可能干扰其他弹层的正常行为。
备注:内容仅供参考。
