我们来实现一个自动显示/隐藏的导航栏
目标
- 页面向下滚动,导航栏跟随滚动进行隐藏
- 页面向上滚动到顶,导航栏逐渐显示
- 当次向上滚动距离超过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...