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...

实现效果

结束

相关推荐
DevUI团队2 小时前
从“即兴创作”到“规格先行”,华为云码道(CodeArts)代码智能体持续深耕企业级规范驱动开发能力
前端·人工智能·后端
weixin_431600443 小时前
NestJS 入门(3):Guard 如何挡住未登录请求?
前端·后端·学习·nest.js
kyriewen3 小时前
我用Claude Code两天干完了团队两周的排期——周报发出去那一刻我就后悔了
前端·javascript·ai编程
IT_陈寒4 小时前
JavaScript类型转换把我坑惨了,这破玩意真该早点搞明白
前端·人工智能·后端
用户938515635074 小时前
Type vs Interface:读完这篇就没有面试官能难倒你了
前端·面试·typescript
油丶酸萝卜别吃5 小时前
jquery-ajax.js 说明文档
前端·javascript·jquery
windliang5 小时前
Claude Code 源码分析(九):子 Agent 如何分叉、继续与回到父会话
前端·javascript·面试
柒和远方6 小时前
V063: TS 面试必考:interface 与 type 的四大差异,与 LLM Harness 的自动化择优
前端·javascript
半个落月6 小时前
React useRef 详解:DOM 引用、持久化值与 Worker 实例
前端·react.js
阿黎梨梨6 小时前
TypeScript 类型编程:从新手到 Harness 工程实践
前端