如何让shadcn/Radix导航菜单居中且避免浏览器窗口溢出
解决Shadcn导航菜单居中后溢出窗口的问题
核心问题
你给NavigationMenuViewport套了flex justify-center容器强制菜单居中,但未处理内容过宽时的溢出情况。以下是两种可行解决方案:
方案一:利用Radix内置自动定位(推荐)
Radix UI的NavigationMenuPrimitive.Content支持自动检测视口边界并调整位置,只需调整组件属性即可实现居中+避溢出效果:
修改NavigationMenuContent组件
const NavigationMenuContent = React.forwardRef< React.ElementRef<typeof NavigationMenuPrimitive.Content>, React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.Content> >(({ className, ...props }, ref) => ( <NavigationMenuPrimitive.Content ref={ref} align="center" // 开启居中对齐逻辑 sideOffset={8} // 调整菜单与触发器的间距 className={cn( 'data-[motion^=from-]:animate-in data-[motion^=to-]:animate-out data-[motion^=from-]:fade-in data-[motion^=to-]:fade-out data-[motion=from-end]:slide-in-from-right-52 data-[motion=from-start]:slide-in-from-left-52 data-[motion=to-end]:slide-out-to-right-52 data-[motion=to-start]:slide-out-to-left-52 md:absolute md:w-auto', className )} {...props} /> ));
修改NavigationMenuViewport组件
移除外层强制居中的容器,让Radix自行处理定位:
const NavigationMenuViewport = React.forwardRef< React.ElementRef<typeof NavigationMenuPrimitive.Viewport>, React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.Viewport> >(({ className, ...props }, ref) => ( <NavigationMenuPrimitive.Viewport ref={ref} className={cn( 'filter-card box-shadow-card origin-top-center absolute top-full mt-1.5 h-[var(--radix-navigation-menu-viewport-height)] w-full overflow-hidden rounded-md border bg-popover text-popover-foreground shadow-lg data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-90 md:w-[var(--radix-navigation-menu-viewport-width)]', className )} forceMount {...props} /> ));
方案二:自定义溢出检测逻辑
若需保留自定义居中容器,可添加动态计算逻辑,实时检测菜单是否溢出视口并调整偏移:
修改NavigationMenuViewport组件
const NavigationMenuViewport = React.forwardRef< React.ElementRef<typeof NavigationMenuPrimitive.Viewport>, React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.Viewport> >(({ className, ...props }, ref) => { const viewportRef = React.useRef<HTMLDivElement>(null); const [offset, setOffset] = React.useState(0); React.useEffect(() => { const updateOffset = () => { if (!viewportRef.current) return; const viewport = viewportRef.current; const windowWidth = window.innerWidth; const viewportWidth = viewport.offsetWidth; const viewportLeft = viewport.getBoundingClientRect().left; // 计算左右侧溢出量 const overflowRight = viewportLeft + viewportWidth - windowWidth; const overflowLeft = -viewportLeft; if (overflowRight > 0) { // 向右偏移避免右侧溢出 setOffset(-overflowRight); } else if (overflowLeft > 0) { // 向左偏移避免左侧溢出 setOffset(overflowLeft); } else { // 无溢出时保持居中 setOffset(0); } }; updateOffset(); window.addEventListener('resize', updateOffset); return () => window.removeEventListener('resize', updateOffset); }, []); return ( <div className={cn('absolute top-full flex justify-center')}> <NavigationMenuPrimitive.Viewport ref={(el) => { viewportRef.current = el; if (typeof ref === 'function') ref(el); else if (ref) ref.current = el; }} style={{ transform: `translateX(${offset}px)` }} className={cn( 'filter-card box-shadow-card origin-top-center relative mt-1.5 h-[var(--radix-navigation-menu-viewport-height)] w-full overflow-hidden rounded-md border bg-popover text-popover-foreground shadow-lg data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-90 md:w-[var(--radix-navigation-menu-viewport-width)]', className )} forceMount {...props} /> </div> ); });
方案说明
- 方案一利用Radix内置能力,代码简洁、兼容性好,优先推荐使用
- 方案二适合需自定义布局的场景,通过监听窗口尺寸变化动态调整菜单位置
内容的提问来源于stack exchange,提问作者Korer
相关产品推荐
相关产品推荐

