128 lines
3.9 KiB
JavaScript
128 lines
3.9 KiB
JavaScript
import React, { useState, useEffect, useRef } from 'react';
|
||
import './StickyNavExample.css';
|
||
|
||
/**
|
||
* 最小化示例:实现滚动吸顶与分类高亮效果
|
||
*/
|
||
const StickyNavExample = () => {
|
||
// 分类数据
|
||
const categories = [
|
||
{ id: 'section1', name: '分类一' },
|
||
{ id: 'section2', name: '分类二' },
|
||
{ id: 'section3', name: '分类三' },
|
||
{ id: 'section4', name: '分类四' },
|
||
];
|
||
|
||
// 状态管理
|
||
const [activeSection, setActiveSection] = useState(categories[0].id);
|
||
const isManualScrollRef = useRef(false);
|
||
const timerRef = useRef(null);
|
||
|
||
// 监听滚动事件,更新当前活动分类
|
||
useEffect(() => {
|
||
const handleScroll = () => {
|
||
// 如果是手动点击导致的滚动,则跳过自动检测
|
||
if (isManualScrollRef.current) return;
|
||
|
||
const stickyNav = document.querySelector('.sticky-nav');
|
||
const stickyHeight = stickyNav?.getBoundingClientRect().height || 0;
|
||
const triggerOffset = stickyHeight + 20; // 触发点位置
|
||
|
||
// 遍历所有分类区块,检查哪个在视口中
|
||
categories.forEach(category => {
|
||
const section = document.getElementById(category.id);
|
||
if (section) {
|
||
const rect = section.getBoundingClientRect();
|
||
// 当区块顶部进入触发点时,将其设为当前活动分类
|
||
if (rect.top <= triggerOffset && rect.bottom >= triggerOffset) {
|
||
setActiveSection(category.id);
|
||
}
|
||
}
|
||
});
|
||
};
|
||
|
||
// 添加滚动事件监听
|
||
window.addEventListener('scroll', handleScroll);
|
||
|
||
// 组件卸载时移除事件监听
|
||
return () => window.removeEventListener('scroll', handleScroll);
|
||
}, []);
|
||
|
||
// 处理分类点击事件
|
||
const handleCategoryClick = (categoryId) => {
|
||
// 清除之前的定时器
|
||
if (timerRef.current) {
|
||
clearTimeout(timerRef.current);
|
||
}
|
||
|
||
// 设置为手动滚动,暂时禁用自动检测
|
||
isManualScrollRef.current = true;
|
||
setActiveSection(categoryId);
|
||
|
||
// 滚动到对应区块
|
||
const section = document.getElementById(categoryId);
|
||
const stickyNav = document.querySelector('.sticky-nav');
|
||
|
||
if (section && stickyNav) {
|
||
const stickyHeight = stickyNav.getBoundingClientRect().height;
|
||
const sectionTop = section.offsetTop - stickyHeight;
|
||
|
||
window.scrollTo({
|
||
top: sectionTop,
|
||
behavior: 'smooth'
|
||
});
|
||
}
|
||
|
||
// 设置定时器,1秒后重新启用滚动检测
|
||
timerRef.current = setTimeout(() => {
|
||
isManualScrollRef.current = false;
|
||
}, 1000);
|
||
};
|
||
|
||
return (
|
||
<div className="container">
|
||
{/* 页面标题 */}
|
||
<h1 className="title">滚动吸顶示例</h1>
|
||
<p className="subtitle">向下滚动查看效果</p>
|
||
|
||
{/* 粘性导航区域 */}
|
||
<div className="sticky-nav">
|
||
{/* 搜索框 */}
|
||
<input
|
||
type="text"
|
||
className="search-box"
|
||
placeholder="搜索..."
|
||
/>
|
||
|
||
{/* 分类标签 */}
|
||
<div className="category-tabs">
|
||
{categories.map(category => (
|
||
<button
|
||
key={category.id}
|
||
className={activeSection === category.id ? 'active' : ''}
|
||
onClick={() => handleCategoryClick(category.id)}
|
||
>
|
||
{category.name}
|
||
</button>
|
||
))}
|
||
</div>
|
||
</div>
|
||
|
||
{/* 内容区域 */}
|
||
{categories.map(category => (
|
||
<div
|
||
key={category.id}
|
||
id={category.id}
|
||
className="content-section"
|
||
>
|
||
<h2>{category.name}</h2>
|
||
<p>这是{category.name}的内容区域。滚动时,当此区域进入视口中间位置,对应的分类标签会自动高亮。</p>
|
||
{/* 添加一些占位内容使页面可滚动 */}
|
||
<div className="placeholder"></div>
|
||
</div>
|
||
))}
|
||
</div>
|
||
);
|
||
};
|
||
|
||
export default StickyNavExample; |