[uniapp学习日志]带你实现uniapp导航栏滚动出现消失(附源码)
前言:在UniApp开发中,实现导航栏的滚动出现与消失效果是一项常见的需求,特别是在用户浏览页面时,希望能够最大化展示内容区域。本篇学习日志将介绍如何通过监听页面滚动事件,动态控制导航栏的显示与隐藏
我的目标实现效果:
同时存在页面滑动onPageScroll()和scroll-view的@scroll,当页面page发生滚动时会触发自定义导航栏的出现和隐藏(参考下图演示)。
当导航栏处于完全完全展开(完全隐藏)的状态时,开启scroll-view的滚动;当导航栏处于变化状态时,关闭scroll-view的滚动(参考下图演示)
更多的细节我们后文继续讲述...


开始之前的前置知识 :
(1)首先要关闭掉小程序默认的导航栏
进入uniapp项目的page.json,找到pages{}和你想要实现的vue页面(我的是index.vue),添加"navigationStyle": "custom"(将页面导航栏设置为自定义)
"pages": [ //pages数组中第一项表示应用启动页,参考:https://uniapp.dcloud.io/collocation/pages
{
"path": "pages/index/index",
"style": {
"navigationStyle": "custom",
"navigationBarTitleText": "uni-app"
}
}
(2)进入index.vue界面,引用自定义完成的导航栏
导航栏和scroll-view内部内容根据需求完善
<script lang="ts" setup>
import CustomNavbar from './components/CustomNavbar.vue'
import { ref } from 'vue'
const isScroll = ref('') //初始关闭scroll-view
</script>
- class="view":给scroll-view组件添加了一个名为view的类。
- :scroll-y="isScroll":这是一个动态绑定属性,将父组件中的isScroll属性的值传递给scroll-view组件的scroll-y属性。这个属性控制scroll-view组件是否可以在垂直方向上滚动。
- @scroll="onScroll":这是另一个事件监听器,当scroll-view组件滚动时,会调用父组件中的onScroll方法。
我们核心运用的是@scroll监听滚动和:scroll-y ="isScroll”动态开关
<template>
<!-- 自定义导航栏引入 -->
<CustomNavbar />
<!-- scrollview结构 -->
<scroll-view
:scroll-y="isScroll"
class="view"
@scroll="onScroll"
>
<!--内部渲染的内容 -->
</scroll-view>
</template>
page.js使用弹性布局,diplay:flex,并设置为锤子方向,高度设置为180vh使得出现页面上下滑动。class = "view" 为<scroll-view></scroll-view>设置高度占据除了<CustomNavbar />以外的空间。
<style lang="scss">
page {
background-color: #f0d6d6;
height: 180vh;
display: flex;
flex-direction: column;
.view {
flex: 1;
height: 0;
}
}
</style>
此时我们的页面大概是这样的

(3) onPageScroll()监听页面滚动变化
简单使用onPageScroll() 页面生命周期监听滚动事件
<script lang="ts" setup>
onPageScroll(() => {
console.log('触发页面滚动')
})
</script>

调用onPageScroll(e) 获取滚动事件,并从滚动事件中获取e.scrollTop的值
e.scrollTop的值代表当前距离页面顶部的距离。
如果只是需求,当页面滑动到页面上端时,使得某个区块出现,可使用onPageScroll()监控e.scrollTop的值判断距离,并使用:stype动态绑定组件的透明度即可(仅提供思路)
<script lang="ts" setup>
onPageScroll((e) => {
console.log('触发onPageScroll', e)
})
</script>

(4) @Scroll()监听组件滚动变化
定义onScroll方法监听scroll-view的滑动事件,效果基本同上,此处仅提供代码参考
<script lang="ts" setup>
const onScroll = (e: Event) => {
console.log('e.detail.scrollTop', e.detail.scrollTop)
}
</script>

(5)css样式设置粘性定位,使得导航栏固定在屏幕上方
- position: sticky; - 设置元素为粘性定位,即当滚动到特定位置时会固定在屏幕上
- top元素动态绑定,定位在屏幕的相对位置
- z-index: 设置元素的堆叠顺序为2
<style lang="scss">
.customNavbar {
display: flex;
flex-direction: column;
position: sticky;
// top: -10vh;
z-index: 2;
.crack {
margin: 0 0 -1rpx 0;
}
}
page {
background-color: #f0d6d6;
height: 180vh;
display: flex;
flex-direction: column;
.view {
flex: 1;
height: 0;
}
}
</style>
<script lang ="ts" setup>
const getTop = () => {
return 0
}
</script>
<template>
<view class="customNavbar" :style="{ top: getTop() + 'rpx' }">
<view class="crack"><CustomNavbar /></view>
<titleButton :titleList="titleList" />
</view>
<scroll-view
:refresher-triggered="triggered"
@refresherrefresh="onrefresherrefresh"
@scrolltolower="onScrolltolower"
:scroll-y="isScroll"
class="view"
@scroll="onScroll"
>
<!-- refresher-enabled="true" -->
<videoSwiper ref="guessRef" />
</scroll-view>
</template>

实现效果源码以及详细解析:
(1)<script>实现部分:
- const isScroll = ref('') 控制scroll-view开关 //初始关闭
- let scrollTopArr: number[] = [] //数组push(e.scrollTop)
- const temp = ref(0) 代表滑动的大方向 //0-->上滑 1-->下滑
- const move = ref<number>(-1) move === -1 表示不处于导航栏变化状态(导航栏出现与消失状态);move >= 0 表示处于导航栏变化状态;move具体的值表示当前距离 site 点的移动距离
- const site = ref<number>(0) 记录上滑下滑方向时的改变点(e.scrollTop值)
<script lang="ts" setup>
const isScroll = ref('') //控制scroll-view开关 //初始关闭
let scrollTopArr: number[] = [] //数组push(e.scrollTop)
const temp = ref(0) //代表滑动的大方向 //0-->上滑 1-->下滑
const move = ref<number>(-1) //举例上滑下滑改变移动了多少 0-20内关闭scroll滚动
const site = ref<number>(0) //记录方向改变点
</script>
onPageScroll() 监听滑动方向变化,导航栏状态
<script lang="ts" setup>
onPageScroll((e) => {
// console.log('触发onPageScroll', e)
//数组保存scrollTop的值
var scrollTop = e.scrollTop
scrollTopArr.push(scrollTop)
//此时len的值为6
var len = scrollTopArr.length
//scrollTopArr只保留5个,避免数组太大
len > 5 ? (scrollTopArr = scrollTopArr.slice(-5)) : scrollTopArr
//页面滑动一定距离开启scroll-view 滚动(初始scroll-view关闭)
scrollTopArr[len - 2] > 150 ? (isScroll.value = 'false') : isScroll
//if (scrollTopArr[len - 2] < scrollTopArr[len - 3]) 处于上滑状态(手指上滑) temp===0
//if (scrollTopArr[len - 2] > scrollTopArr[len - 3]) 处于下滑状态(手指下滑) temp===1
if (scrollTopArr[len - 2] < scrollTopArr[len - 3]) {
console.log('页面下滑 down temp===1')
if (temp.value === 0 && move.value === -1) {
//如果temp.value===0 表示上一瞬间处于上滑状态 大方向为上滑
//这一瞬间 (scrollTopArr[len - 2] < scrollTopArr[len - 3]) 处于下滑状态
//move.value=== -1 ,当前导航栏没有处于变化状态
//此时滑动方向改变,需要使得导航栏处于变化状态
temp.value = 1 //表示现在在下滑1 //修改滑动方向
site.value = scrollTop //记录滚动方向改变瞬间的位置
move.value = 0 // 开启move记录相对 site 点 移动距离
isScroll.value = '' //关闭scroll-view组件滚动
console.log('滚动方向改变', site.value)
}
if (temp.value === 1 && move.value >= 0) {
//如果temp.value===1 表示上一瞬间处于下滑状态 大方向为下滑
//这一瞬间 (temp.value === 0 && move.value === -1) 处于下滑状态
//move.value >= 0 ,当前导航栏处于变化状态
//此时导航栏处于变化状态,需要记录move的值,同时禁用scroll-view滑动
//导航栏未完成变化,始终禁止scroll-view滑动
move.value = site.value - scrollTopArr[len - 2]
isScroll.value = ''
console.log('当前据改变位置site滚动距离', move.value)
if (move.value >= 150) {
//如果 move.value >= 150 (我的单位是rpx,由绑定的:stype top 的单位决定)
//150 具体数值为你的导航栏长度和手机边框安全距离决定
//此时 导航栏完全收缩成最终状态,变化结束(参考开头第二张演示图)
move.value = -1 //自上次方向改变的位置site的移动距离清空 //表示不处于变化状态
site.value = 0 // 清空 方向转变点的距离
isScroll.value = 'true' // 启动scrollview滚动
console.log('move.value >= 150 启动scrollview滚动')
}
} else if (temp.value === 0 && move.value !== -1) {
//关键在这
//此时的大方向为下滑
//但是我们的小方向为上滑 (scrollTopArr[len - 2] < scrollTopArr[len - 3])
//场景 : 初始上滑,改变方向下滑,进入导航栏变化状态
//但是没有一直下滑直至导航栏彻底收缩到最小,反悔了,在导航栏收缩到最小之前上滑了
//此时大方向下滑(temp = 0),小方向为下滑,此时不改变 temp 的值
//变化状态没有结束 , 禁用scroll-view
move.value = scrollTopArr[len - 2] - site.value //始终是正数,试一次看一眼输出就好懂了
isScroll.value = ''
console.log('小方向当前据改变位置site滚动距离', move.value)
isScroll.value = ''
if (move.value < 0) {
//move.value == 0 ,回到最初滑动方向改变点 site点 的位置
//move.value < 0 , 此时滑动方向与改变前方向一致,为上滑状态
//导航栏结束变化状态
temp.value = 0 //
move.value = -1
site.value = 0
isScroll.value = 'true'
console.log('move.value < 0 启动scrollview滚动')
}
}
}
////方向转换 原理同上
else if (scrollTopArr[len - 2] > scrollTopArr[len - 3]) {
console.log('页面下滑 down temp-->1')
if (temp.value === 1 && move.value === -1) {
temp.value = 0 //表示现在在下滑1
site.value = scrollTop //记录改变滚动方向时候的位置
move.value = 0 //记录移动距离
isScroll.value = '' //关闭组件滚动
console.log('滚动方向改变', site.value)
}
if (temp.value === 0 && move.value >= 0) {
move.value = scrollTopArr[len - 2] - site.value
console.log('当前据改变位置site滚动距离', move.value)
isScroll.value = ''
if (move.value >= 150) {
move.value = -1 //自上次方向改变的位置site的移动距离清空
site.value = 0 //上一次方向改变的位置
isScroll.value = 'true' //启动scrollview滚动
console.log('move.value >= 150 启动scrollview滚动')
}
} else if (temp.value === 1 && move.value !== -1) {
//关键在这里
move.value = site.value - scrollTopArr[len - 2]
isScroll.value = ''
console.log('小方向当前据改变位置site滚动距离', move.value)
if (move.value < 0) {
temp.value = 1
move.value = -1
site.value = 0
isScroll.value = 'true'
console.log('move.value < 0 启动scrollview滚动')
}
}
}
})
</script>
onScroll() 监听滑动变化,滑动方向与temp.value(滑动大方向)不一致时,关闭scroll-view滑动
<script lang="ts" setup>
let scrollviewTopArr: number[] = []
const onScroll = (e: Event) => {
// console.log('e.detail.scrollTop', e.detail.scrollTop)
var scrollviewTop = e.detail.scrollTop
scrollviewTopArr.push(scrollviewTop)
let len = scrollviewTopArr.length
len > 5 ? (scrollviewTopArr = scrollviewTopArr.slice(-5)) : scrollviewTopArr
if (scrollviewTopArr[len - 2] > scrollviewTopArr[len - 3]) {
console.log('scrollview 上滑 --> 0 ')
if (temp.value === 1) {
isScroll.value = ''
}
} else if (scrollviewTopArr[len - 2] < scrollviewTopArr[len - 3]) {
console.log('scrollview 下滑 --> 1')
if (temp.value === 0) {
isScroll.value = ''
}
}
}
</script>
getTop()方法,从move.value和temp.value 中获取到动态渲染 粘性布局的top值,决定导航栏相对位置
<script lang="ts" setup>
const getTop = () => {
// 下拉 //0-->上滑 1-->下滑 move (-1,30)
let top = 0
move.value < 0 && temp.value == 1 ? (top = 0) : top
move.value >= 0 && move.value <= 150 && temp.value == 1 ? (top = move.value - 150) : top
//上滑
move.value < 0 && temp.value === 0 ? (top = -150) : top
move.value >= 0 && move.value <= 150 && temp.value === 0 ? (top = -move.value) : top
return top
}
<script>
(2)<template>部分
<template>
//动态渲染 粘性布局 top 的值
<view class="customNavbar" :style="{ top: getTop() + 'rpx' }">
<view class="crack"><CustomNavbar /></view>
<titleButton :titleList="titleList" />
</view>
<scroll-view
:scroll-y="isScroll"
class="view"
@scroll="onScroll"
>
<videoSwiper ref="guessRef" />
</scroll-view>
</template>
(3)<style>部分
<style lang="scss">
.customNavbar {
display: flex;
flex-direction: column;
position: sticky;
// top: -10vh;
z-index: 2;
.crack {
margin: 0 0 -1rpx 0;
}
}
.contain {
// width: 100vw;
height: auto;
display: flex;
justify-content: flex-start;
flex-direction: column;
}
page {
background-color: #f0d6d6;
height: 180vh;
display: flex;
flex-direction: column;
.view {
flex: 1;
height: 0;
}
}
</style>
感谢观看捏
更多推荐
所有评论(0)