Taro实现自动显示/隐藏的导航栏

接上文Taro实现兼容H5的沉浸式页面

我们来实现一个自动显示/隐藏的导航栏

目标

  1. 页面向下滚动,导航栏跟随滚动进行隐藏
  2. 页面向上滚动到顶,导航栏逐渐显示
  3. 当次向上滚动距离超过30px,导航栏组件显示

分析

实现第一、二点并不难,有的同学可能会想到sticky布局,但是第三点就没办法了。

我的实现方法还是用fixed布局 +页面滚动监听

思路如下:

1.导航栏是固定在页面顶部的,用fixed布局没问题

2.导航栏需要跟随页面滚动逐渐隐藏/显示,就需要监听页面滚动事件,获取页面滚动距离调整导航栏位置,可以用transform实现,顺便用transition实现延迟动效

3.当次向上滚动距离超过30px,设置导航栏位移距离为0,使其完全展示

代码

组件使用了fower这款css-in-js插件,仅对css书写方法有影响,看不懂可以先了解一下

可嵌入自定义导航栏的沉浸式组件

tsx 复制代码
import { View } from '@fower/taro'
import { isH5 } from '@/utils'
import Taro, { usePageScroll } from '@tarojs/taro'
import { useState } from 'react'

const statusBarHeight = isH5 ? 0 : Taro.getWindowInfo()?.statusBarHeight ?? 0
/**
 * 沉浸式页面顶部
 * @param props
 * @returns
 */
const ImmersionTop = (props: {
  navigationHeight?: number
  backgroundColor?: string
  transitionTime?: number
  children: any
}) => {
  const navigationHeight = props.navigationHeight || 50
  // 导航栏过渡动画时间,默认300ms
  const transitionTime = props.transitionTime || 300
  const totalHeight = statusBarHeight + navigationHeight
  // 若状态栏文字颜色为白色,需要设置背景颜色
  const backgroundColor = props.backgroundColor || 'white'
  const children = props.children
  // 导航栏垂直位移距离
  const [navigationTranslateY, setNavigationTranslateY] = useState(0)
  // 当前页面滚动高度
  const [scrollTop, setScrollTop] = useState(0)
  const SHOW_SCROLL_DELTA_Y = 30
  // 监听页面滚动,根据滚动距离,设置导航栏的transform: translateY()
  usePageScroll((info) => {
    const deltaY = info.scrollTop - scrollTop
    if (deltaY < -SHOW_SCROLL_DELTA_Y) {
      // 单次向上滚动距离超过30px,就显示导航栏
      setNavigationTranslateY(0)
    } else {
      // 保证位移距离在0~navigationHeight之间
      let newTranTranslateY = navigationTranslateY + deltaY
      if (newTranTranslateY < 0) {
        newTranTranslateY = 0
      } else if (newTranTranslateY > navigationHeight) {
        newTranTranslateY = navigationHeight
      }
      setNavigationTranslateY(newTranTranslateY)
    }
    setScrollTop(info.scrollTop)
  })
  // 导航栏固定布局
  const navigationStyles: CSSObject = {
    position: 'fixed',
    left: '0px',
    right: '0px',
    top: statusBarHeight + 'px',
    zIndex: 1,
    height: navigationHeight + 'px',
    // 通过transform: translateY()将导航栏移动到状态栏下方,并设置transform动画
    transform: `translateY(-${navigationTranslateY}px)`,
    transition: `transform ${transitionTime}ms ease-out`,
    display: 'flex',
    alignItems: 'center',
    backgroundColor,
    borderBottom: '1px solid #e5e5e5'
  }
  return (
    <>
      <StatusBarPosition
        height={statusBarHeight}
        backgroundColor={backgroundColor}
      />
      <View css={navigationStyles}>{children}</View>
      {/* 导航栏高度占位,置于正常文档流之中 */}
      <View css={{ height: totalHeight + 'px' }} />
    </>
  )
}

// 状态栏高度占位,固定布局,层级在导航栏之上,避免导航栏遮挡,不会覆盖系统状态信息
const StatusBarPosition = (props: {
  height: number
  backgroundColor: string
}) => {
  const style: CSSObject = {
    position: 'fixed',
    left: '0px',
    right: '0px',
    top: '0px',
    zIndex: 2,
    height: props.height + 'px',
    backgroundColor: props.backgroundColor
  }
  return <View css={style} />
}

export default ImmersionTop

页面导航栏组件

tsx 复制代码
import ImmersionTop from '@/components/ImmersionNavigation'
import { Image, Text, View } from '@fower/taro'
import SettingIcon from '@/assets/icons/index/setting.png'
import { isH5 } from '@/utils'

const SettingButton = () => {
  return (
    <View mr-10px p-10px flex>
      <Image src={SettingIcon} circle-20px />
    </View>
  )
}

export default function NavigationBar({}) {
  const profile =
    'https://pubfile.bluemoon.com.cn/group1/new/scrm/961483605c85131353b062f1c8f60104.jpeg'
  return (
    <ImmersionTop>
      <Image src={profile} circle-40px ml-10px />
      <Text ml-auto mr-auto>
        Y
      </Text>
      {isH5 ? <SettingButton /> : <View circle-30px mr-10px />}
    </ImmersionTop>
  )
}

页面组件

tsx 复制代码
import { View } from '@fower/taro'
import NavigationBar from './components/Navigation'

definePageConfig({
  navigationBarTitleText: '首页',
  navigationStyle: 'custom',
  navigationBarTextStyle: 'black'
})
const List = () =>
  Array.from({ length: 100 }).map((_, index) => (
    <View key={index}>列表项-{index + 1}</View>
  ))
export default function Index() {
  return (
    <View>
      <NavigationBar />
      <View css={{ lineHeight: 2 }}>
        <List />
      </View>
    </View>
  )
}

可以在这里拉取示例代码:gitee.com/qq742037091...

实现效果

结束

相关推荐
咔咔库奇1 小时前
【TypeScript】命名空间、模块、声明文件
前端·javascript·typescript
兩尛1 小时前
订单状态定时处理、来单提醒和客户催单(day10)
java·前端·数据库
又迷茫了1 小时前
vue + element-ui 组件样式缺失导致没有效果
前端·javascript·vue.js
哇哦Q2 小时前
原生HTML集合
前端·javascript·html
SoWhat~2 小时前
随遇随记篇
前端·javascript
孟健2 小时前
重磅首发:国产AI编程助手Trae实测!免费用上Claude是什么体验?
前端·aigc·visual studio code
爱上大树的小猪2 小时前
【前端SEO】使用Vue.js + Nuxt 框架构建服务端渲染 (SSR) 应用满足SEO需求
前端·javascript·vue.js
Java陈序员2 小时前
TypeScript 快速上⼿
前端·typescript
小肚肚肚肚肚哦2 小时前
函数式编程中各种封装的对比以及封装思路解析
前端·设计模式·架构
奇舞精选2 小时前
在 Chrome 浏览器里获取用户真实硬件信息的方法
前端·chrome