加载中...
当前视口: 1920px × 600px场景: 基础左对齐导航
打开链接功能特性
- ✅ 吸顶固定 -
sticky top-0滚动时固定在页面顶部 - ✅ 自动高亮 - 根据滚动位置自动高亮当前章节
- ✅ 平滑滚动 - 点击导航项平滑滚动到目标章节
- ✅ 自动居中 - 活动导航项自动滚动到视图中心
- ✅ 主题支持 - 亮色 (light) 和暗色 (dark) 两种主题
- ✅ 对齐方式 - 支持左对齐、居中、右对齐
- ✅ 尺寸变体 - 小 (small) 和大 (large) 两种尺寸
- ✅ 活动指示器 - 底部渐变下划线动画
- ✅ 性能优化 - 滚动事件防抖 (50ms),减少计算开销
- ✅ 响应式设计 - 移动端可横向滚动,隐藏滚动条
Props 参数
AnchorNavigationProps
| Prop | 类型 | 默认值 | 必需 | 说明 |
|---|---|---|---|---|
data | AnchorNavigationData | - | ✅ | 导航数据配置 |
classNames | Partial<Record<SemanticName, string>> | {} | - | 语义化类名覆盖 |
ref | React.Ref<HTMLDivElement> | - | - | 根容器 ref |
AnchorNavigationData 配置
| 参数 | 类型 | 默认值 | 必需 | 说明 |
|---|---|---|---|---|
sectionIds | Array<{ targetId: string; label: string }> | - | ✅ | 锚点列表 |
alignment | 'start' | 'center' | 'end' | 'start' | - | 导航项水平对齐方式 |
theme | 'light' | 'dark' | 'light' | - | 主题模式 |
size | 'small' | 'large' | 'small' | - | 导航大小 |
sectionIds 配置
sectionIds: [
{
targetId: 'features', // 对应页面中 <section id="features">
label: 'Features' // 导航项显示文本
},
{
targetId: 'pricing',
label: 'Pricing'
}
]重要: 确保页面中存在对应 id 的元素,否则点击导航项不会滚动。
使用示例
最简示例
import { AnchorNavigation } from '@anker-in/headless-ui/biz'
<>
<AnchorNavigation
data={{
sectionIds: [
{ targetId: 'features', label: 'Features' },
{ targetId: 'specs', label: 'Specifications' },
{ targetId: 'reviews', label: 'Reviews' }
]
}}
/>
{/* 页面内容 */}
<section id="features">Features content...</section>
<section id="specs">Specifications content...</section>
<section id="reviews">Reviews content...</section>
</>居中对齐 + 大尺寸
<AnchorNavigation
data={{
sectionIds: [
{ targetId: 'about', label: 'About Us' },
{ targetId: 'team', label: 'Our Team' },
{ targetId: 'careers', label: 'Careers' },
{ targetId: 'contact', label: 'Contact' }
],
alignment: 'center',
size: 'large'
}}
/>暗色主题
<AnchorNavigation
data={{
sectionIds: [
{ targetId: 'gallery', label: 'Gallery' },
{ targetId: 'videos', label: 'Videos' },
{ targetId: 'press', label: 'Press Kit' }
],
theme: 'dark'
}}
/>自定义样式
<AnchorNavigation
data={{
sectionIds: [...],
alignment: 'end'
}}
classNames={{
root: 'shadow-lg', // 添加阴影
content: 'py-4', // 增加内边距
item: 'text-lg font-black' // 自定义文本样式
}}
/>语义化 ClassNames 参数
通过 classNames prop 覆盖特定部分样式:
| 参数名 | 描述 | 使用说明 |
|---|---|---|
root | 根容器 | sticky 固定容器,用于自定义整体样式 |
content | 导航项容器 | flex 容器,控制导航项的布局和间距 |
item | 单个导航项 | button 元素,自定义导航项的样式 |
工作原理
1. 滚动监听与高亮
使用自定义 Hook useAnchorPosition 监听滚动事件:
const useAnchorPosition = (sectionIds: string[], offset = 100) => {
const [activeId, setActiveId] = useState(sectionIds[0] || '')
useEffect(() => {
const handleScroll = debounce(() => {
const scrollPosition = window.scrollY + offset + 10
// 从后往前遍历,找到第一个 offsetTop <= scrollPosition 的 section
for (let i = sectionIds.length - 1; i >= 0; i--) {
const section = document.getElementById(sectionIds[i])
if (section && scrollPosition >= section.offsetTop) {
setActiveId(sectionIds[i])
break
}
}
}, 50)
window.addEventListener('scroll', handleScroll)
return () => window.removeEventListener('scroll', handleScroll)
}, [sectionIds, offset])
return activeId
}关键参数:
offset = 100: 偏移量,控制何时认为进入某个章节debounce 50ms: 防抖优化,避免频繁计算
2. 点击导航项滚动
onClick={() => {
const targetElement = document.getElementById(item.targetId)
if (targetElement) {
const navHeight = rootRef.current.offsetHeight // 导航栏高度
const targetPosition = targetElement.getBoundingClientRect().top + window.scrollY - navHeight
window.scrollTo({
top: targetPosition,
behavior: 'smooth'
})
}
}滚动位置计算:
targetPosition = 目标元素距离文档顶部的距离 - 导航栏高度这样滚动后,目标章节会紧贴导航栏下方。
3. 导航栏自动滚动
当活动项改变时,自动滚动导航栏使活动项居中:
const autoScrollToActiveItem = (activeIdIndex: number) => {
const button = sectionRefs.current[activeIdIndex]
const container = containerRef.current
const scrollLeft = button.offsetLeft - container.offsetWidth / 2 + button.offsetWidth / 2
container.scrollTo({
left: scrollLeft,
behavior: 'smooth'
})
}响应式行为
容器样式
// 移动端
px-4 // 左右边距 16px
// 平板
tablet:px-8 // 32px
// 笔记本/桌面
laptop:px-16 desktop:px-16 // 64px
// 超大屏
lg-desktop:px-[calc(50%-832px)] // 最大内容宽度 1664px对齐方式(仅平板及以上生效)
alignment: 'start' → tablet:justify-start
alignment: 'center' → tablet:justify-center
alignment: 'end' → tablet:justify-end移动端: 始终为 flex 布局,可横向滚动,无对齐限制。
尺寸变体
| 尺寸 | gap | py (按钮垂直边距) |
|---|---|---|
| small | gap-3 (12px) | py-3 (12px) |
| large | gap-6 (24px) | py-4 (16px) |
主题样式
Light Theme (默认)
// 容器
bg-white
// 未激活项
text-[#4A4C56]
// 激活项
text-[#080A0F]
after:bg-brand after:w-full after:opacity-100 // 底部下划线Dark Theme
// 容器
bg-[#1E2024]
// 未激活项
text-[#8A8D92]
// 激活项
text-white
after:bg-brand after:w-full after:opacity-100活动指示器动画
.anchor-navigation-item::after {
position: absolute;
bottom: 0;
left: 0;
height: 4px;
width: 0; /* 默认宽度为 0 */
opacity: 0; /* 默认透明 */
transition: all 0.3s;
background: var(--brand);
}
/* 激活时 */
.active::after {
width: 100%; /* 宽度变为 100% */
opacity: 1; /* 完全不透明 */
}无障碍性
键盘支持
- ✅ 使用
<button>元素实现导航项,原生支持键盘访问 - ✅ 支持 Tab 键在导航项之间切换焦点
- ✅ 支持 Enter 和 Space 键触发导航跳转
ARIA 属性
- 导航项语义清晰,使用
label作为按钮文本 - 建议为根容器添加
role="navigation"和aria-label="page sections" - 建议为活动项添加
aria-current="true"
最佳实践
<nav role="navigation" aria-label="Page sections">
<button
aria-current={isActive ? 'true' : undefined}
onClick={...}
>
{item.label}
</button>
</nav>性能优化
- ✅ 滚动事件防抖: 使用
es-toolkit的debounce,50ms 延迟 - ✅ 避免重复计算:
useCallback缓存autoScrollToActiveItem函数 - ✅
useImperativeHandle: 正确暴露 ref,避免多次 render - ✅ 条件渲染: 仅在
scrollTo方法存在时执行(兼容性处理) - ✅ 隐藏滚动条:
[&::-webkit-scrollbar]:hidden提升视觉体验
常见问题
为什么点击导航项不滚动?
确保页面中存在对应 id 的元素:
<!-- ✅ 正确 -->
<section id="features">...</section>
<!-- ❌ 错误:ID 不匹配 -->
<section id="feature">...</section> <!-- 注意单复数 -->如何调整高亮的触发时机?
修改 useAnchorPosition 的 offset 参数(默认 100px):
// 在组件内部调用时传入自定义 offset
const activeId = useAnchorPosition(sectionIds, 150) // 更早触发高亮注意: 当前实现中 offset 硬编码为 100,需要修改组件代码。
移动端导航项被截断怎么办?
确保导航项文本简短,或使用 size="small" 减小间距:
data={{
sectionIds: [
{ targetId: 'faq', label: 'FAQ' }, // ✅ 简短
{ targetId: 'support', label: 'Help' } // ✅ 简短
],
size: 'small' // 减小间距
}}如何自定义活动指示器颜色?
覆盖 CSS 变量 --brand:
:root {
--brand: #ff6600; /* 自定义品牌色 */
}或直接覆盖 after 伪元素:
classNames={{
item: 'after:bg-blue-500' // Tailwind 颜色
}}导航栏固定后遮挡内容怎么办?
为页面内容添加上边距,等于导航栏高度:
// 小尺寸导航栏高度约 48px
<div className="pt-12">
<section id="features">...</section>
</div>
// 大尺寸导航栏高度约 64px
<div className="pt-16">
<section id="features">...</section>
</div>如何在 Next.js 中使用?
确保导航栏在客户端渲染(因为使用了 window 和 document):
'use client' // Next.js App Router
import { AnchorNavigation } from '@anker-in/headless-ui/biz'
export default function Page() {
return <AnchorNavigation data={{...}} />
}支持嵌套章节吗?
当前实现不支持嵌套章节(如 1.1, 1.2)。如需支持,需要自定义逻辑:
- 扁平化所有章节 ID
- 自定义高亮逻辑
相关资源
- Storybook 文档 - 查看更多交互示例
- 完整 README 文档 - 详细的 API 文档和使用指南
- Figma 设计稿 - 查看设计规范
- 源代码 - GitHub 源代码
Last updated on