Skip to Content
@anker-in/headless-ui 2.0 is released 🎉

AnchorNavigation (锚点导航)

用于长页面内部导航,支持自动高亮当前章节、平滑滚动和吸顶固定。【✅ 已发布】

加载中...
当前视口: 1920px × 600px场景: 基础左对齐导航
打开链接

功能特性

  • 吸顶固定 - sticky top-0 滚动时固定在页面顶部
  • 自动高亮 - 根据滚动位置自动高亮当前章节
  • 平滑滚动 - 点击导航项平滑滚动到目标章节
  • 自动居中 - 活动导航项自动滚动到视图中心
  • 主题支持 - 亮色 (light) 和暗色 (dark) 两种主题
  • 对齐方式 - 支持左对齐、居中、右对齐
  • 尺寸变体 - 小 (small) 和大 (large) 两种尺寸
  • 活动指示器 - 底部渐变下划线动画
  • 性能优化 - 滚动事件防抖 (50ms),减少计算开销
  • 响应式设计 - 移动端可横向滚动,隐藏滚动条

Props 参数

AnchorNavigationProps

Prop类型默认值必需说明
dataAnchorNavigationData-导航数据配置
classNamesPartial<Record<SemanticName, string>>{}-语义化类名覆盖
refReact.Ref<HTMLDivElement>--根容器 ref

AnchorNavigationData 配置

参数类型默认值必需说明
sectionIdsArray<{ 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 布局,可横向滚动,无对齐限制。

尺寸变体

尺寸gappy (按钮垂直边距)
smallgap-3 (12px)py-3 (12px)
largegap-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; /* 完全不透明 */ }

无障碍性

键盘支持

  • ✅ 使用 &lt;button&gt; 元素实现导航项,原生支持键盘访问
  • ✅ 支持 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-toolkitdebounce,50ms 延迟
  • 避免重复计算: useCallback 缓存 autoScrollToActiveItem 函数
  • useImperativeHandle: 正确暴露 ref,避免多次 render
  • 条件渲染: 仅在 scrollTo 方法存在时执行(兼容性处理)
  • 隐藏滚动条: [&::-webkit-scrollbar]:hidden 提升视觉体验

常见问题

为什么点击导航项不滚动?

确保页面中存在对应 id 的元素:

<!-- ✅ 正确 --> <section id="features">...</section> <!-- ❌ 错误:ID 不匹配 --> <section id="feature">...</section> <!-- 注意单复数 -->

如何调整高亮的触发时机?

修改 useAnchorPositionoffset 参数(默认 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 中使用?

确保导航栏在客户端渲染(因为使用了 windowdocument):

'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
  • 自定义高亮逻辑

相关资源

Last updated on