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

实现效果

结束

相关推荐
YBN娜9 分钟前
Vue实现登录功能
前端·javascript·vue.js
阳光开朗大男孩 = ̄ω ̄=9 分钟前
CSS——选择器、PxCook软件、盒子模型
前端·javascript·css
minDuck13 分钟前
ruoyi-vue集成tianai-captcha验证码
java·前端·vue.js
小政爱学习!34 分钟前
封装axios、环境变量、api解耦、解决跨域、全局组件注入
开发语言·前端·javascript
魏大帅。39 分钟前
Axios 的 responseType 属性详解及 Blob 与 ArrayBuffer 解析
前端·javascript·ajax
花花鱼1 小时前
vue3 基于element-plus进行的一个可拖动改变导航与内容区域大小的简单方法
前端·javascript·elementui
k09331 小时前
sourceTree回滚版本到某次提交
开发语言·前端·javascript
EricWang13581 小时前
[OS] 项目三-2-proc.c: exit(int status)
服务器·c语言·前端
September_ning1 小时前
React.lazy() 懒加载
前端·react.js·前端框架
web行路人1 小时前
React中类组件和函数组件的理解和区别
前端·javascript·react.js·前端框架