首页 / 中文教程 / 教程

Bricks 自适应桌面导航栏(溢出自动收纳到 More)

这篇教程教你:当桌面端导航栏溢出时,自动添加一个「More」菜单项。

Ray Chan·2026-08-12·约 4 分钟

这篇教程教你:当桌面端导航栏溢出时,自动添加一个「More」菜单项。

引言

UI/UX 文献都建议导航菜单项越少越好。但做 Web 的人都知道,总有些客户坚持要在导航栏里塞一堆菜单项,结果为了在各种断点上避免溢出,样式调起来简直是噩梦。

其实有另一种办法:与其写一堆媒体查询,不如集成这段小脚本。只要你的菜单在窗口缩放时发生溢出,脚本就会自动在导航栏末尾创建一个「More」项,并把溢出的菜单项收进它的子菜单里。来看看怎么实现。

DOM 结构

按照下面的 DOM 树搭建:

一定要按上图把 menu-wrappermenu-innermenu-crop 这三个类加到正确的元素上。

menu-wrapper 元素需要 100% 宽度。如果你想要盒装(boxed)导航栏,再加上 max-width:

menu-inner 块需要启用 flexbox,并使用 flex-direction: row

JavaScript

把下面的 JavaScript 代码复制/粘贴到需要生效的地方。举个例子,如果你的菜单是全局菜单、所有页面都显示,可以把代码粘贴到 Bricks Settings → Custom Code 标签页 → Body (footer) scripts

window.addEventListener('load', () => {

   // Variables
   const wrapper = document.querySelector('.menu-wrapper');
   if (!wrapper) {
      return console.log('Resizable Desktop Menu - There is no menu-wrapper class on this page. Please double check you correctly assigned the classes to each element');
   }
   const inner = wrapper.querySelector('.menu-inner');
   if (!inner) {
      return console.log('Resizable Desktop Menu - There is no menu-inner class on this page. Please double check you correctly assigned the classes to each element');
   }
   const menu = inner.querySelector('.menu-crop');
   if (!menu) {
      return console.log('Resizable Desktop Menu - There is no menu-crop class on this page. Please double check you correctly assigned the classes to each element');
   }
   const menuUl = menu.querySelector('ul.bricks-nav-menu');
   const menuItems = menu.querySelectorAll("ul.bricks-nav-menu > li.menu-item").length;
   let hiddenItems = [];
   let moreItem;
   let moreIsVisible = false;

   // debounce so calculation doesn't happen every millisecond when resizing
   const debounce = (fn, threshold) => {
      var timeout;
      threshold = threshold || 200;
      return function debounced() {
         clearTimeout(timeout);
         var args = arguments;
         var _this = this;

         function delayed() {
            fn.apply(_this, args);
         }
         timeout = setTimeout(delayed, threshold);
      };
   };

   // Check if overflow
   const isOverflown = (element) => {
      return element.scrollWidth > element.clientWidth;
   }

   // Create elements function
   const createElement = (el, options, append) => {
      let element = document.createElement(el);

      if (!options) return element;
      let entries = Object.entries(options)
      let data = entries.map(([key, val] = entry) => {
         element.setAttribute(key, val);
      });

      if (!append) return element
      return append.appendChild(element);
   }

   // Create More Item
   const createMoreMenu = (menuUl) => {

      // Create Elemets
      const tag = createElement('li', {
         'id': 'moreItem',
         'class': 'bricks-menu-item'
      }, menuUl);
      const active = createElement('a', {}, tag);
      const text = document.createTextNode("More");
      active.appendChild(text);
      const qty = createElement('span', {
         'id': 'moreQty'
      }, active);
      const ulTag = createElement('ul', {
         'id': 'moreSubMenu',
         'role': 'menu',
         'class': 'sub-menu'
      }, tag);

      moreIsVisible = true;
   }

   // Remove Item
   const removeItem = () => {
      // Stop the function is NOT overflown
      if (!isOverflown(inner) || hiddenItems.length == menuItems) return;

      // Declare Variables
      let menuLi = menu.querySelectorAll("ul.bricks-nav-menu > li.menu-item");
      let lastItem = menuLi[menuLi.length - 1];

      // Add node to hidden array
      hiddenItems.push(lastItem);

      // Remove node from the DOM
      if (lastItem) menuUl.removeChild(lastItem);

      // Insert the More Button
      if (!moreIsVisible) createMoreMenu(menuUl);

      // Append the item to the sub menu
      let subMenu = menu.querySelector('#moreSubMenu');
      if (subMenu) subMenu.insertBefore(lastItem, subMenu.firstChild);

      // Update the Qty Number
      let subMenuQty = menu.querySelector('#moreQty');
      subMenuQty.innerHTML = hiddenItems.length;

      // Keep removing items if the inner container is overflown
      removeItem();
   }

   // Add Item
   const addItem = () => {
      if (hiddenItems.length < 1) return;

      // Remove the More Item
      if (moreIsVisible) {
         let moreItem = menuUl.querySelector("#moreItem");
         moreItem.remove();
         moreIsVisible = false;
      }

      // Readd all hidden items as visible
      if (hiddenItems.length > 0) {
         hiddenItems.slice().reverse().forEach(item => {
            menuUl.insertBefore(item, moreItem);
         });

         // reset the hidden array
         hiddenItems = [];
      }

      // Remove item is the inner container is overflown
      if (isOverflown(inner)) removeItem();
   }

   // Init
   if (isOverflown(inner)) removeItem();

   // Resize
   window.addEventListener('resize', debounce(() => {
      isOverflown(inner) ? removeItem() : addItem();
   }, 0))

})

CSS

把下面的 CSS 片段粘贴到脚本运行的地方。同样,全局菜单可以放在 Bricks Settings → Custom Code 标签页 → Custom CSS

.menu-inner > * {
   white-space: nowrap;
}

#moreItem a {
   display: flex;
   align-items: flex-start;
   flex-wrap: nowrap;
   flex-direction: row;
}

span#moreQty {
   margin-left: 0.3rem;
   background-color: var(--bricks-color-primary, blue);
   padding: 0.3rem 0.45rem;
   font-size: 9px;
   color: #fff;
   border-radius: 50%;
   vertical-align: super;
   font-weight: 800;
   width: 15px;
   height: 15px;
   display: flex;
   align-items: center;
   justify-content: center;
}

参考资源

debounce 函数(防抖)

https://www.freecodecamp.org/news/javascript-debounce-example/

JS 对象中的键值对

https://javascript.info/keys-values-entries

理解 offsetWidth、clientWidth、scrollWidth

https://stackoverflow.com/questions/21064101/understanding-offsetwidth-clientwidth-scrollwidth-and-height-respectively

结论

脚本的核心逻辑:用 scrollWidth > clientWidth 判断 menu-inner 是否溢出;溢出就从菜单末尾逐个摘掉菜单项收进 #moreSubMenu 子菜单,并创建带数量徽章(#moreQty)的「More」项;窗口拉宽不再溢出时再按逆序放回。resize 事件用防抖(默认阈值 200ms)避免频繁计算。搭配 CSS 里的 white-space: nowrap 防止菜单项换行即可。

需要帮忙?

用 Bricks 建站?我接客户项目。

从快速营销站到完整的 Bricks 建站,再到从 Elementor 迁移——我都做过。本站每篇教程都来自真实项目经验。告诉我你的需求,一个工作日内回复。

  • Bricks 建站与改版
  • Elementor / Divi → Bricks 迁移
  • Bricks → Astro / headless 性能升级
  • 速度优化,PageSpeed 95+ 目标