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

如何在React中基于屏幕尺寸条件显示列表项实现带更多菜单的导航

React 自适应「更多」导航栏实现方案

核心实现逻辑

核心是不依赖固定断点,实时计算导航容器剩余宽度,判断哪些导航项可以显示,哪些放进下拉菜单,相比固定断点适配的兼容性更强,能适配不同内容长度的导航项。

具体实现步骤

  • 第一步:用useRef挂载导航容器、所有导航项、「更多」按钮的DOM引用,提前渲染所有导航项的不可见快照,用来获取每个导航项的实际渲染宽度
  • 第二步:计算导航容器总可用宽度,要提前减去「更多」按钮的预留宽度和间距,累加导航项宽度,直到累加值超过可用宽度,临界值之前的导航项作为可见项,之后的全部作为「更多」菜单的下拉项
  • 第三步:监听窗口resize事件,搭配防抖函数优化触发频率,每次尺寸变化都重新计算可见项和下拉项的分组
  • 第四步:渲染时区分可见导航项和「更多」下拉菜单,下拉菜单用常规的React状态控制显隐即可

代码示例片段

import { useRef, useState, useEffect } from 'react'

// 导航项配置
const navItems = [
  { id: '1', label: '首页', path: '/' },
  { id: '2', label: '新闻', path: '/news' },
  { id: '3', label: '赛事', path: '/games' },
  { id: '4', label: '数据', path: '/stats' },
  { id: '5', label: '视频', path: '/videos' },
  { id: '6', label: '社区', path: '/community' },
  // 可继续添加更多导航项
]

const ResponsiveNav = () => {
  const navContainerRef = useRef<HTMLDivElement>(null)
  const moreBtnRef = useRef<HTMLButtonElement>(null)
  const [visibleItems, setVisibleItems] = useState(navItems)
  const [hiddenItems, setHiddenItems] = useState<typeof navItems>([])
  const [isMoreOpen, setIsMoreOpen] = useState(false)

  // 计算导航项分组的核心函数
  const calculateVisibleItems = () => {
    if (!navContainerRef.current || !moreBtnRef.current) return
    // 获取容器总宽度
    const containerWidth = navContainerRef.current.clientWidth
    // 获取更多按钮实际宽度
    const moreBtnWidth = moreBtnRef.current.offsetWidth
    // 可用宽度 = 容器宽度 - 更多按钮宽度 - 预留安全间距
    const availableWidth = containerWidth - moreBtnWidth - 20

    let totalWidth = 0
    const tempVisible: typeof navItems = []
    const tempHidden: typeof navItems = []

    // 获取提前渲染的不可见导航项的宽度
    const itemElements = navContainerRef.current.querySelectorAll('.nav-item-snapshot')
    itemElements.forEach((el, index) => {
      // 计算单个导航项总宽度(含左右间距)
      const itemWidth = el.offsetWidth + 16
      if (totalWidth + itemWidth <= availableWidth) {
        totalWidth += itemWidth
        tempVisible.push(navItems[index])
      } else {
        tempHidden.push(navItems[index])
      }
    })

    setVisibleItems(tempVisible)
    setHiddenItems(tempHidden)
  }

  // 防抖函数,避免resize时高频触发计算
  const debounce = (fn: Function, delay: number) => {
    let timer: NodeJS.Timeout
    return (...args: any[]) => {
      clearTimeout(timer)
      timer = setTimeout(() => fn.apply(this, args), delay)
    }
  }

  useEffect(() => {
    calculateVisibleItems()
    const debouncedCalculate = debounce(calculateVisibleItems, 100)
    window.addEventListener('resize', debouncedCalculate)
    return () => window.removeEventListener('resize', debouncedCalculate)
  }, [])

  return (
    <div ref={navContainerRef} className="nav-container relative flex items-center h-14 px-4 overflow-hidden">
      {/* 可见导航项 */}
      {visibleItems.map(item => (
        <a key={item.id} href={item.path} className="mx-2 whitespace-nowrap">
          {item.label}
        </a>
      ))}
      {/* 更多按钮,仅存在隐藏项时显示 */}
      {hiddenItems.length > 0 && (
        <div className="relative ml-2">
          <button ref={moreBtnRef} onClick={() => setIsMoreOpen(!isMoreOpen)}>
            更多
          </button>
          {/* 下拉菜单 */}
          {isMoreOpen && (
            <div className="absolute right-0 top-full w-36 bg-white shadow rounded py-1 z-10">
              {hiddenItems.map(item => (
                <a key={item.id} href={item.path} className="block px-4 py-2 hover:bg-gray-100">
                  {item.label}
                </a>
              ))}
            </div>
          )}
        </div>
      )}
      {/* 不可见的导航项快照,仅用来计算宽度,不参与交互 */}
      <div className="absolute left-0 top-0 opacity-0 pointer-events-none">
        {navItems.map(item => (
          <span key={item.id} className="nav-item-snapshot mx-2 whitespace-nowrap">
            {item.label}
          </span>
        ))}
      </div>
    </div>
  )
}

export default ResponsiveNav

注意事项

  • 计算宽度时要预留足够的安全边距,避免刚好卡在临界宽度时出现导航项来回抖动的问题
  • 如果导航项是动态获取的,需要把导航项列表作为useEffect的依赖项,导航项更新时重新计算分组
  • 服务端渲染场景下,首次渲染可以先默认显示前N个导航项,等客户端 hydration 完成后再重新计算调整,避免 hydration 不匹配报错

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.25 22:06:04