首页
/ Bulma 固定导航栏实战:is-fixed-top/is-fixed-bottom 双步定位与 dropup 菜单的完整实现解析

Bulma 固定导航栏实战:is-fixed-top/is-fixed-bottom 双步定位与 dropup 菜单的完整实现解析

2026-09-05 18:09:47作者:秋阔奎Evelyn

Bulma 允许将 navbar 组件固定(fix)在页面顶部底部,这一功能由两个修饰符类配合完成:给 navis-fixed-topis-fixed-bottom,再给 <html><body> 加对应的 has-navbar-fixed-top / has-navbar-fixed-bottom 补偿页面内边距。对于底部固定导航栏,Bulma 还提供了配套的 dropup(向上弹出)菜单能力。本文基于仓库中的功能公告(docs/_posts/2017-11-01-fixed-navbar.md)、官方文档页与 SASS 源码,完整讲解固定导航栏的使用步骤、dropup 实现原理、触控/桌面细分变体、JS 动态切换方案,以及 E2E 测试对这些行为的具体断言,读完你可以直接在自己的项目中落地一个固定顶栏或底部导航栏,并理解其背后的 CSS 机制。

一、功能背景:fixed navbar 与 dropup 是一次发布的一对功能

仓库中的功能公告博文(2017-11-01,docs/_posts/2017-11-01-fixed-navbar.md)说明了这一特性最初发布时的定位:

  • 可以把 navbar 固定(fix)到页面顶部底部
  • 作为额外bonus,底部导航栏需要一个新的 dropup 功能——下拉菜单位于导航栏下方,对底部导航栏并不合适,因此同步实现了向上弹出的 dropup 菜单。

公告中给出的官方文档锚点(/documentation/components/navbar/#fixed-navbar)对应当前仓库中的文档页 docs/documentation/components/navbar.html,其中 “Fixed navbar” 小节将用法概括为一个两步流程(2-step process)

  1. navbar 组件添加 is-fixed-topis-fixed-bottom
<nav class="navbar is-fixed-top">
</nav>
  1. <html><body> 元素添加对应的 has-navbar-fixed-tophas-navbar-fixed-bottom,为页面提供合适的 padding,避免固定导航栏遮挡首屏/尾屏内容:
<html class="has-navbar-fixed-top">
</html>

第二步之所以必要,是因为 position: fixed 的元素会脱离文档流。从源码看,Bulma 的补偿方案非常直接——sass/components/navbar.scss 中:

html,
body {
  &.#{iv.$class-prefix}has-navbar-fixed-top {
    padding-top: cv.getVar("navbar-height");
  }

  &.#{iv.$class-prefix}has-navbar-fixed-bottom {
    padding-bottom: cv.getVar("navbar-height");
  }
}

即:把 html/body 的上(或下)内边距设为一个 navbar-height。该值由 Sass 变量 $navbar-height: 3.25rem 定义(sass/components/navbar.scss),并注册为 CSS 变量 --navbar-height,同时作用于 nav.navbarmin-height。若导航栏实际高度与 3.25rem 不同(例如自定义了更高的高度),需要同步调整该 CSS 变量,否则补偿的 padding 会不匹配。

二、一个完整的底部固定导航栏示例

官方文档页自带一个可交互的 “Show/Hide bottom navbar” 演示,其 DOM 结构来自示例包含文件 docs/_includes/docs/examples/navbar-bottom.html。去掉文档站特有的按钮逻辑后,核心结构如下:

<nav class="navbar is-fixed-bottom has-shadow">
  <div class="navbar-brand">
    <a class="navbar-item">Logo</a>
    <a role="button" class="navbar-burger" aria-label="menu" aria-expanded="false" data-target="navbarBottomExample">
      <span aria-hidden="true"></span>
      <span aria-hidden="true"></span>
      <span aria-hidden="true"></span>
      <span aria-hidden="true"></span>
    </a>
  </div>

  <div id="navbarBottomExample" class="navbar-menu">
    <div class="navbar-start">
      <a class="navbar-item">Home</a>

      <!-- 底部导航栏使用 dropup:has-dropdown + has-dropdown-up -->
      <div class="navbar-item has-dropdown has-dropdown-up is-hoverable">
        <a class="navbar-link">Docs</a>
        <div class="navbar-dropdown">
          <a class="navbar-item">Overview</a>
          <a class="navbar-item is-selected">Jobs</a>
          <a class="navbar-item">Contact</a>
          <hr class="navbar-divider">
          <a class="navbar-item">Report an issue</a>
        </div>
      </div>
    </div>

    <div class="navbar-end">
      <div class="navbar-item">
        <div class="buttons">
          <a class="button is-primary"><strong>Sign up</strong></a>
          <a class="button is-light">Log in</a>
        </div>
      </div>
    </div>
  </div>
</nav>

配套的页面根元素:

<html class="has-navbar-fixed-bottom">

要点说明:

  • has-shadow:固定导航栏通常会压在滚动内容之上,加 has-shadow 可加一条细阴影增强层次。从源码看,is-fixed-bottom 下阴影方向被专门翻转——$navbar-box-shadow-size: 0 0.125em 0 0(向下)在底部固定时改为 0 -0.125em 0 0(向上),见 sass/components/navbar.scss
  • dropup 修饰符 has-dropdown-up 必须加在带 has-dropdown 的父级 navbar-item 上,而不是 navbar-dropdown 上,这是官方文档页 “Dropup” 小节的明确要求;
  • 移动端 navbar-burger 的展开仍依赖 JS 切换 is-active(Bulma 官方包不包含任何 JavaScript,文档页仅提供实现示例),这与固定定位无关,但底部导航栏同样需要这套逻辑。

三、dropup 菜单:has-dropdown-up 的 CSS 级实现

底部导航栏的菜单没有向下展开的空间,has-dropdown-up 通过一组定位与箭头翻转规则解决。核心实现位于 sass/components/navbar.scss(桌面断点内):

&.#{iv.$class-prefix}has-dropdown-up {
  .#{iv.$class-prefix}navbar-link::after {
    transform: rotate(135deg) translate(0.25em, -0.25em);
  }

  .#{iv.$class-prefix}navbar-dropdown {
    border-bottom-color: cv.getVar("navbar-dropdown-border-color");
    border-bottom-style: cv.getVar("navbar-dropdown-border-style");
    border-bottom-width: cv.getVar("navbar-dropdown-border-width");
    border-radius: cv.getVar("navbar-dropdown-radius")
      cv.getVar("navbar-dropdown-radius") 0 0;
    border-top: none;
    bottom: 100%;
    box-shadow: 0 -0.5em 0.5em hsla(..., 0.1);
    top: auto;
  }
}

对照默认 dropdown 的样式(sass/components/navbar.scss)可以看到 dropup 做了五处镜像调整:

属性 默认 dropdown dropup(has-dropdown-up
垂直定位 top: 100% bottom: 100%; top: auto;
边框 border-top(灰色顶边) border-bottomborder-top: none
圆角 底部两角 border-end-*-radius border-radius: r r 0 0(顶部两角)
阴影 0 0.5em 0.5em ...(向下) 0 -0.5em 0.5em ...(向上)
箭头 默认指向 ::after 旋转 135deg 并平移,指向上方

使用示例(摘自文档页的 dropup 代码片段):

<div class="navbar-item has-dropdown has-dropdown-up is-hoverable">
  <a class="navbar-link">
    Docs
  </a>
  <div class="navbar-dropdown">
    <a class="navbar-item">Overview</a>
  </div>
</div>

展示方式与常规 navbar dropdown 一致:父级 navbar-itemis-hoverable(悬停展开)或 is-active(常驻展开/由 JS 控制)。注意 dropup 的定位与样式规则写在 @include mx.from($navbar-breakpoint)(即桌面端)内,而移动端下 navbar 的 dropdown 本来就是直接以块级形式内嵌展示,不涉及上下弹出问题。

四、固定定位的 CSS 机制:navbar-fixed mixin 与 z-index 层级

is-fixed-top / is-fixed-bottom 的共同规则来自私有 mixin(sass/components/navbar.scss):

@mixin navbar-fixed {
  left: 0;
  position: fixed;
  right: 0;
  z-index: cv.getVar("navbar-fixed-z");
}

随后按方向分别钉住上/下边缘(sass/components/navbar.scss):

&.is-fixed-bottom,
&.is-fixed-top {
  @include navbar-fixed;
}

&.is-fixed-bottom {
  bottom: 0;
  &.has-shadow { box-shadow: ...; /* 向上阴影 */ }
}

&.is-fixed-top {
  top: 0;
}

关键取值:

  • $navbar-z: 30 是普通 navbar 的 z-index(普通状态即为 relative 定位 + z-index 30);
  • $navbar-fixed-z: 30 是固定状态沿用的 z-index,因此固定与否不会改变其层级;
  • dropdown 的 z-index 为 $navbar-dropdown-z: 20,它处于 navbar 自身的层叠上下文内,所以不会被 navbar 外部、z-index 更高的元素意外遮挡时,需要自行评估页面中其他固定元素(如 header、modal)的层级关系。

仓库的 E2E 测试对以上行为做了明确断言,可视为该实现的验收基准。docs/cypress/e2e/components/navbar.spec.js 中:

it("has correct fixed Navbar", () => {
  cy.get("#navbar-is-fixed-top").then(($) => {
    const cs = window.getComputedStyle($[0]);
    expect(cs.position).to.equal("fixed");
    expect(cs.top).to.equal("0px");
    expect(cs.zIndex).to.equal("30");
  });

  cy.get("#navbar-is-fixed-bottom").then(($) => {
    const cs = window.getComputedStyle($[0]);
    expect(cs.bottom).to.equal("0px");
    expect(cs.position).to.equal("fixed");
    expect(cs.zIndex).to.equal("30");
  });
});

即固定导航栏的计算样式必须是 position: fixedtop: 0(或 bottom: 0)、z-index: 30;同一测试文件还断言了普通 navbar 的 min-height: 52px3.25rem 在默认字号 16px 下的结果)。测试对应的 fixture 页面为 docs/cyp/components/navbar.html

五、触控与桌面细分变体:-touch / -desktophas-spaced-navbar

is-fixed-top / is-fixed-bottom全断点生效的定位。如果只想在某一类设备上固定,Bulma 提供了一组带设备后缀的变体,全部由同一 mixin 驱动:

  • 触控端(until($navbar-breakpoint),断点即 $desktop):
    • is-fixed-top-touch / is-fixed-bottom-touch 定位 navbar;
    • html/body 上使用 has-navbar-fixed-top-touch / has-navbar-fixed-bottom-touch 补 padding;
    • 固定顶栏时,移动端展开的 navbar-menu 被额外限制为 max-height: calc(100vh - navbar-height); overflow: auto,保证菜单不会超出视口(sass/components/navbar.scss)。
  • 桌面端(from($navbar-breakpoint)):
    • is-fixed-top-desktop / is-fixed-bottom-desktop 定位 navbar;
    • html/body 上使用 has-navbar-fixed-top-desktop / has-navbar-fixed-bottom-desktop 补 padding。

桌面端还有两个专门配合 is-spaced 导航栏的变体(sass/components/navbar.scss):

&.#{iv.$class-prefix}has-spaced-navbar-fixed-top {
  padding-top: calc(
    #{cv.getVar("navbar-height")} + #{cv.getVar("navbar-padding-vertical")} * 2
  );
}

因为 is-spaced 导航栏在 3.25rem 内容高度之外还有上下各 1rem$navbar-padding-vertical)的内边距,占位 padding 需要按 navbar-height + 2 × navbar-padding-vertical 补偿,避免顶部固定 + spaced 时仍遮挡内容。

另外,源码末尾还提供了一个与 hero 联动的组合修饰符(sass/components/navbar.scss):hero.is-fullheight-with-navbarmin-heightcalc(100vh - navbar-height),用于在存在固定顶栏时让全屏 hero 不超出视口。

六、动态切换固定状态:文档站的参考实现

官方文档页(docs/documentation/components/navbar.html 中 “Try it out!” 部分)内置了一段 vanilla JS,演示如何运行时切换底部固定导航栏——这也是公告博文中 “Check it out!” 演示的对应实现:

<script>
document.addEventListener('DOMContentLoaded', () => {
  const rootEl = document.documentElement;
  const navbarBottomEl = document.getElementById('navbarBottom');
  const fixBottomEl = document.getElementById('navbarFixBottom');

  let fixedBottom = false;
  fixBottomEl.addEventListener('click', () => {
    fixedBottom = !fixedBottom;
    if (fixedBottom) {
      rootEl.classList.add('has-navbar-fixed-bottom');
      navbarBottomEl.classList.remove('is-hidden');
    } else {
      rootEl.classList.remove('has-navbar-fixed-bottom');
      navbarBottomEl.classList.add('is-hidden');
    }
  });
});
</script>

模式总结:切换固定状态 = 同步增删两处类名——html 上的 has-navbar-fixed-bottomnav 上的 is-fixed-bottom(必要时配合 is-hidden 控制显隐)。缺少 padding 补偿那一侧时,就会出现首屏内容被遮挡的经典问题,这也是“两步流程”中第二步的意义所在。

同样,移动端 burger 菜单的展开由 JS 切换 navbar-burgernavbar-menu 上的 is-active 完成(文档页给出了 vanilla JS 与 jQuery 两种参考实现),该逻辑与固定定位正交,底部导航栏同样适用。

七、落地检查清单

综合文档与源码,实现固定导航栏时可对照以下清单:

  1. nav.navbar 添加 is-fixed-topis-fixed-bottom(需要全断点固定);仅针对单端时使用 -touch / -desktop 后缀变体;
  2. <html><body> 添加 has-navbar-fixed-top / has-navbar-fixed-bottom(或对应 -touch/-desktop 变体);导航栏带 is-spaced 时使用 has-spaced-navbar-fixed-top/bottom(桌面端);
  3. 底部导航栏的下拉菜单改为 has-dropdown has-dropdown-up,让菜单向上弹出;
  4. 验证计算样式:position: fixedtop: 0bottom: 0z-index: 30(与 docs/cypress/e2e/components/navbar.spec.js 的断言一致);
  5. 若自定义了 navbar 高度,同步调整 --navbar-height,保证补偿 padding 与真实高度一致;
  6. 检查页面上其他 fixed/sticky 元素(header、modal、toast)与 z-index 30 的相对关系,必要时提高 navbar 的 --navbar-fixed-z 或降低遮挡元素层级。

以上所有类名与默认值均以当前仓库 SASS 源码(sass/components/navbar.scss)与预编译产物(css/bulma.css)为准;文档页与公告博文中引用的演示视频、截图属于站点资源,具体视觉表现以文档站渲染为准。

登录后查看全文
热门项目推荐
相关项目推荐

项目优选

收起
kernelkernel
deepin linux kernel
C
33
18
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.12 K
2.72 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
528
588
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
906
1.83 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
854
1.34 K
docsdocs
暂无描述
Markdown
891
5.79 K
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.53 K
1.01 K
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.34 K
1.45 K
cann-learning-hubcann-learning-hub
CANN 学习中心仓,支持在线互动运行、边学边练,提供教程、示例与优化方案,一站式助力昇腾开发者快速上手。
Jupyter Notebook
988
506
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
540
384