前言:在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>
  1. class="view":给scroll-view组件添加了一个名为view的类。
  2. :scroll-y="isScroll":这是一个动态绑定属性,将父组件中的isScroll属性的值传递给scroll-view组件的scroll-y属性。这个属性控制scroll-view组件是否可以在垂直方向上滚动。
  3. @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样式设置粘性定位,使得导航栏固定在屏幕上方
  1. position: sticky; - 设置元素为粘性定位,即当滚动到特定位置时会固定在屏幕上
  2. top元素动态绑定,定位在屏幕的相对位置
  3. 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>

感谢观看捏

Logo

北京人形旗下天工造物具身智能开源社区,聚焦具身天工与慧思开物两大平台

更多推荐