在React项目里,基于滚动速度控制导航栏显隐是提升页面交互体验的实用功能,相比仅根据滚动位置判断显隐,它能更贴合用户的操作习惯,避免导航栏频繁切换带来的视觉干扰。

核心实现思路
要实现基于滚动速度的控制,核心是先计算出当前的滚动速度,再根据速度阈值决定导航栏的显示或隐藏状态。滚动速度的计算需要记录两次滚动事件的时间戳和滚动位置,通过位置差除以时间差得到速度值。
滚动速度计算逻辑
我们可以在滚动事件中记录上一次的滚动位置和触发时间,每次新的滚动事件触发时,计算当前和上一次的位置差、时间差,进而得到滚动速度。为了避免频繁触发重渲染,还需要结合防抖或节流处理。
自定义Hook结构设计
我们将所有逻辑封装到一个自定义Hook中,对外暴露导航栏的显隐状态即可,这样可以在多个组件中复用该逻辑,也符合React Hook的逻辑复用原则。
完整代码实现
下面是完整的自定义Hook实现代码,包含速度计算、状态更新和性能优化逻辑:
import { useState, useEffect, useRef, useCallback } from 'react';
/**
* 基于滚动速度控制导航栏显隐的自定义Hook
* @param {number} speedThreshold 速度阈值,超过该值判定为快速滚动,默认200px/s
* @param {number} hideDelay 快速滚动后隐藏导航栏的延迟时间,默认300ms
* @returns {boolean} 导航栏是否显示
*/
const useScrollNavVisibility = (speedThreshold = 200, hideDelay = 300) => {
// 导航栏显隐状态
const [isNavVisible, setIsNavVisible] = useState(true);
// 记录上一次滚动位置
const lastScrollY = useRef(0);
// 记录上一次滚动触发时间
const lastScrollTime = useRef(Date.now());
// 存储隐藏导航栏的定时器
const hideTimer = useRef(null);
// 存储节流定时器
const throttleTimer = useRef(null);
// 处理滚动事件的逻辑
const handleScroll = useCallback(() => {
// 节流处理,每100ms最多执行一次速度计算
if (throttleTimer.current) return;
throttleTimer.current = setTimeout(() => {
throttleTimer.current = null;
const currentScrollY = window.scrollY;
const currentTime = Date.now();
// 计算时间差,避免除零错误
const timeDiff = currentTime - lastScrollTime.current || 1;
// 计算滚动速度,单位px/s
const scrollSpeed = Math.abs(currentScrollY - lastScrollY.current) / timeDiff * 1000;
// 清除之前的隐藏定时器
if (hideTimer.current) {
clearTimeout(hideTimer.current);
hideTimer.current = null;
}
// 判断滚动速度是否超过阈值
if (scrollSpeed > speedThreshold) {
// 快速滚动,延迟隐藏导航栏
hideTimer.current = setTimeout(() => {
setIsNavVisible(false);
}, hideDelay);
} else {
// 慢速滚动或停止滚动,显示导航栏
setIsNavVisible(true);
}
// 更新上一次的滚动位置和触发时间
lastScrollY.current = currentScrollY;
lastScrollTime.current = currentTime;
}, 100);
}, [speedThreshold, hideDelay]);
useEffect(() => {
// 初始化滚动位置和触发时间
lastScrollY.current = window.scrollY;
lastScrollTime.current = Date.now();
// 添加滚动事件监听
window.addEventListener('scroll', handleScroll, { passive: true });
return () => {
// 组件卸载时移除监听和定时器
window.removeEventListener('scroll', handleScroll);
if (hideTimer.current) clearTimeout(hideTimer.current);
if (throttleTimer.current) clearTimeout(throttleTimer.current);
};
}, [handleScroll]);
return isNavVisible;
};
export default useScrollNavVisibility;
组件中使用示例
在页面组件中引入该Hook,直接使用返回的显隐状态控制导航栏的渲染即可:
import React from 'react';
import useScrollNavVisibility from './useScrollNavVisibility';
const PageLayout = () => {
const isNavVisible = useScrollNavVisibility(200, 300);
return (
<div className="page-container">
{/* 导航栏组件 */}
<nav
className="main-nav"
style={{
position: 'fixed',
top: 0,
left: 0,
width: '100%',
height: '60px',
backgroundColor: '#fff',
boxShadow: '0 2px 8px rgba(0,0,0,0.1)',
transition: 'transform 0.3s ease',
transform: isNavVisible ? 'translateY(0)' : 'translateY(-100%)'
}}
>
导航栏内容
</nav>
{/* 页面主体内容 */}
<main style={{ marginTop: '60px', padding: '20px' }}>
{Array.from({ length: 50 }).map((_, index) => (
<p key={index} style={{ height: '40px', lineHeight: '40px', borderBottom: '1px solid #eee' }}>
列表项 {index + 1}
</p>
))}
</main>
</div>
);
};
export default PageLayout;
优化点说明
- 使用
useRef存储滚动位置、时间和定时器,避免这些值变化触发不必要的重渲染。 - 滚动事件监听添加了
passive: true配置,提升滚动事件的响应性能,避免阻塞页面滚动。 - 加入了100ms的节流逻辑,避免滚动事件高频触发导致速度计算过于频繁,减少性能消耗。
- 使用
useCallback包裹滚动处理函数,避免每次组件渲染都生成新的函数实例,减少事件监听的重复绑定。 - 导航栏显隐使用CSS的
transform属性实现动画,相比修改top或height属性,性能更好,不会触发重排。
注意事项
如果页面中存在多个滚动容器,需要修改Hook中的window为对应的容器元素,同时调整滚动位置的获取方式,比如使用container.scrollTop代替window.scrollY。另外,速度阈值和隐藏延迟可以根据实际项目的交互需求调整,找到最适合用户习惯的参数值。
React_Hook滚动速度计算导航栏显隐性能优化修改时间:2026-07-22 07:06:30