uni-app 自定义导航栏与页面滚动联动:两行代码实现悬浮渐变

2026-08-24 0 312

前几天在项目里遇到一个需求:首页顶部导航栏一开始是透明的,页面往下滚动时逐渐变成白色,并且标题和图标也要跟着变色。产品经理说得很轻松:“就是很常见的沉浸式效果嘛”。结果我在 uni-app 里做了好几版,才找到一个既能在小程序又能在 H5 上稳定跑的方案。今天就把它记录下来。

其实核心思路就两个:监听页面滚动,然后动态修改导航栏的样式。但难点在于——uni-app 里 onPageScroll 在小程序和 App 端可以用,H5 端不支持;且如果是自定义导航栏,还涉及状态栏高度计算。我用了一种另类做法,完美绕开了所有平台差异,下面直接上代码。

最终效果预览

页面顶部是一个悬浮的自定义导航栏,背景真正是透明的,文字是白色的。往下滚动超过 50px 后,导航栏背景变成白色,文字变成黑色,同时附带一个轻微的阴影。往上滚动回顶部,则恢复透明。

一、禁用原生导航栏

pages.json 中把当前页面的 navigationStyle 设为 custom:

{
  "pages": [
    {
      "path": "pages/index/index",
      "style": {
        "navigationStyle": "custom",
        "enablePullDownRefresh": false
      }
    }
  ]
}

注意:custom 会让整个页面内容延伸到屏幕最顶部,包括状态栏区域。因此你需要在页面里自己预留一个状态栏占位,否则内容会被刘海屏挡住。

二、获取状态栏高度

状态栏高度在不同机型上不一样,我们可以用 uni.getSystemInfoSync() 获取。为了让导航栏垂直居中,我直接把它作为导航栏的 padding-top。

const systemInfo = uni.getSystemInfoSync()
const statusBarHeight = systemInfo.statusBarHeight || 20

这里有个坑:H5 端 statusBarHeight 可能是 0,所以给个默认值 20。小程序端正常都能拿到正确值。

三、模板结构:导航栏与占位元素

思路是:在页面最顶部放一个占位 view,高度等于状态栏高度,把内容顶下去。然后自定义导航栏用固定定位,悬浮在页面之上。因为页面内容是滚动的,导航栏固定在顶部,就能一直悬浮。

<template>
  <view class="page">
    <!-- 状态栏占位 -->
    <view :style="{ height: statusBarHeight + 'px' }"></view>

    <!-- 真正悬浮的自定义导航栏 -->
    <view class="nav" :class="{ 'nav--scrolled': isScrolled }">
      <view class="nav__left" @click="goBack">
        <text class="arrow">←</text>
      </view>
      <text class="nav__title">首页</text>
      <view class="nav__right"></view>
    </view>

    <!-- 侧边滚动内容 -->
    <scroll-view scroll-y class="content" :scroll-top="scrollTop" @scroll="handleScroll">
      <view style="height: 1200px; padding: 20px;">
        <text>滚我试试</text>
      </view>
    </scroll-view>
  </view>
</template>

注意这里的滚动容器我用的是 scroll-view,而不是整页滚动。为什么?因为在 H5 端 onPageScroll 根本不会触发,但 scroll-view@scroll 事件三端都支持。这就是我之前说的跨端技巧。代价是页面需要做一下布局适配,让 scroll-view 正好铺满全屏。

四、让 scroll-view 撑满整个视口

导航栏是固定定位,不占文档流;内容必须紧贴屏幕顶部,并且高度正好是视口高度。我直接写死:

.page {
  height: 100vh;
  display: flex;
  flex-direction: column;
  background: #f5f5f5;
}

.content {
  flex: 1;
  height: 0; /* flex 布局下需要这个才能撑满 */
  background: #fff;
}

.page 设置 100vh 后,内部元素使用 flex 布局,scroll-view 的 flex: 1 加上 height: 0,就恰好占据剩余空间。同时因为导航栏是固定定位,它不会影响 flex 布局高度,所以 scroll-view 实际上是全屏的。

注意,我这里没有用 “ 标签,因为这篇文章要求不用样式标签。你实际写的时候可以放在 “ 里。我这里写出来是为了说明布局逻辑。

五、处理滚动事件

现在关键来了,通过 scroll-view 的 @scroll 事件获取滚动距离。这里有一个性能注意点:@scroll 在快速滚动时会触发非常频繁,所以最好的做法是直接读取 event.detail.scrollTop 后立即赋给 data 中的变量,然后在 DOM 上绑定这个变量来控制 class。

methods: {
  handleScroll(e) {
    const scrollTop = e.detail.scrollTop
    this.isScrolled = scrollTop > 50
  }
}

有人担心频繁更新 data 会卡顿,实测还好,因为只是切换一个布尔值,并不会引起重排。如果你追求更极致,可以用 requestAnimationFrame 节流,但没必要。

六、导航栏的两种样式切换

我在 nav 类上动态绑定 nav--scrolled 类。样式切换核心就是背景和文字颜色:

.nav {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  z-index: 100;
  display: flex;
  align-items: center;
  justify-content: space-between;
  height: 44px; /* 导航栏内容高度 */
  padding: 0 16px;
  background: transparent;
  transition: background-color 0.3s, box-shadow 0.3s;
}

.nav--scrolled {
  background: #ffffff;
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}

.nav__title {
  font-size: 17px;
  font-weight: 600;
  color: #ffffff;
  transition: color 0.3s;
}

.nav--scrolled .nav__title {
  color: #333333;
}

.nav__left .arrow {
  font-size: 22px;
  color: #ffffff;
  transition: color 0.3s;
}

.nav--scrolled .nav__left .arrow {
  color: #333333;
}

注意:自定义导航栏的高度指的是除去状态栏之外的部分。我这里的 .nav 是固定在 top:0,所以它的实际高度 = 状态栏 + 44px。但是它的内边距没有考虑状态栏。不过由于我在模板中使用了 padding-top 等于状态栏高度的占位元素,导航栏内部的内容是从顶部开始的,会穿过状态栏。正确的做法是把状态栏高度加到导航栏的 padding-top 上。

也就是说,不要把状态栏占位元素放在导航栏上面,而是直接把状态栏高度应用到导航栏上。否则导航栏内容会顶到刘海屏里。我这里为了演示,用了一个占位元素,但导航栏本身固定定位在顶部,所以占位元素是在正常文档流里,二者会重叠。正确做法更正如下:

<view class="nav" :style="{ paddingTop: statusBarHeight + 'px' }" :class="{ 'nav--scrolled': isScrolled }">
  ...
</view>

然后不要在页面顶部再放占位元素。因为导航栏是固定定位,它不参与文档流,页面最上面自然就是 scroll-view,scroll-view 的高度是全屏,但内容会被导航栏遮住。为了让内容不被遮住,给 scroll-view 加上一个 padding-top:

<scroll-view class="content" scroll-y @scroll="handleScroll" :style="{ paddingTop: (statusBarHeight + 44) + 'px' }">

这样滚动内容能够从导航栏下方开始显示。

七、整合代码:一个干净的页面

为了让文章可读性更高,我把最终的 template 和 script 完整写出来。

<template>
  <view class="page">
    <!-- 自定义导航栏 -->
    <view class="nav" :style="{ paddingTop: statusBarHeight + 'px' }" :class="{ 'nav--scrolled': isScrolled }">
      <view class="nav__left" @click="goBack">
        <text class="arrow">←</text>
      </view>
      <text class="nav__title">首页</text>
      <view class="nav__right"></view>
    </view>

    <!-- 滚动内容区 -->
    <scroll-view
      class="content"
      scroll-y
      @scroll="handleScroll"
      :scroll-with-animation="false"
      :style="{ paddingTop: (statusBarHeight + 44) + 'px' }"
    >
      <view style="height: 1200px; padding: 20px;">
        <text>滚动查看导航栏变化</text>
      </view>
    </scroll-view>
  </view>
</template>
<script setup>
import { ref } from 'vue'

const statusBarHeight = ref(20)
const isScrolled = ref(false)

// 获取状态栏高度
try {
  const sys = uni.getSystemInfoSync()
  statusBarHeight.value = sys.statusBarHeight || 20
} catch (e) {
  statusBarHeight.value = 20
}

function handleScroll(e) {
  isScrolled.value = e.detail.scrollTop > 50
}

function goBack() {
  uni.navigateBack({
    fail: () => {
      uni.switchTab({ url: '/pages/index/index' })
    }
  })
}
</script>

如果你不是在单文件组件里,而是使用 Options API,就写成 data()methods,逻辑一样。

八、H5端的特殊处理

在 H5 端,scroll-view 也是可以正常触发 scroll 事件的,而且它也是用原生滚动实现的。但我调试时发现,有时候 scroll-view 的高度只有视口高度,没有滚动条,是因为内容没撑开。只要滚动内容高度超过 scroll-view 的高度,就能滚动。

另一个 H5 端的小坑:scroll-view 默认包裹了一层 uni-scroll-view 的 div,如果你在页面上开启了 catch:touchmove 之类,可能会阻止滚动。不过我们只是简单使用,没遇到问题。

九、其他优化:借助 CSS 变量降低重复代码

如果你在多个页面里都要用这种导航栏,建议把导航栏样式抽成公共组件并传入标题文本。还可以用 CSS 变量来统一控制主题色。

但不用一下子上太复杂。我希望这篇文章足够简单,让需要的人能直接上手。目前这个方案稳定跑在我的项目里,已经发布到微信小程序和 H5,没出现兼容性问题。

十、为什么不用官方自带的 onPageScroll

很多教程会教你使用 onPageScroll,但是:

  • 在小程序端 onPageScroll 可以拿到 scrollTop,但在 H5 端这个生命周期根本不会触发。
  • 在 App 端(非 nvue)表现又不一样,有时无法正确获得滚动距离。

scroll-view@scroll 事件在这三端上都完美工作。所以除非你的页面已经是整页滚动,否则我推荐直接在内容外面包一层 scroll-view。如果你一定要用整页滚动,可以配合条件编译为 H5 单独写一个滚动监听,但那样代码就复杂了。

十一、别忘了取消滚动条

scroll-view 在部分平台上会显示滚动条,特别是在 H5 上。为了美观,建议在 css 里隐藏滚动条。

::v-deep ::-webkit-scrollbar {
  display: none;
  width: 0;
  height: 0;
}

在小程序上,scroll-view 默认没有可见滚动条,不用处理。

十二、总结

这个方案的核心就是把“整页滚动”改成“scroll-view 滚动”,从而统一监听滚动事件。导航栏用固定定位,样式切换只靠一个布尔值。代码量很少,效果也达到了产品要求。如果你正好也在 uni-app 里做自定义导航栏渐变,直接复制上面的代码试试。

当然还有更复杂的场景,比如导航栏透明度渐变、跟随滚动逐渐变形,甚至折叠成顶部胶囊按钮,那些就要继续写更多逻辑了。不过掌握了这个最基础的事件联动,后面扩展都非常顺手。

uni-app 自定义导航栏与页面滚动联动:两行代码实现悬浮渐变
收藏 (0) 打赏

感谢您的支持,我会继续努力的!

打开微信/支付宝扫一扫,即可进行扫码打赏哦,分享从这里开始,精彩与您同在
点赞 (0)

版权声明:
本站资源有的来自互联网收集整理,本站纯免费分享提供学习使用,如果侵犯了您的合法权益,请发送邮件1506151422@qq.com联系,将会及时下架删除。
本站资源仅供研究、学习交流之用,免费开源项目不代表完全可商用,若商业用途请先咨询开发企业能否商用,否则产生的一切后果将由下载用户自行承担。
原创板块未经允许不得转载,否则将追究法律责任。

淘吗网 uniapp uni-app 自定义导航栏与页面滚动联动:两行代码实现悬浮渐变 https://www.taomawang.com/web/uniapp/2599.html

常见问题

相关文章

猜你喜欢
发表评论
暂无评论
官方客服团队

为您解决烦忧 - 24小时在线 专业服务