组件详解#12:目录导航,你的文章很长,但读者的耐心只有3秒 | Component Deep Dive #12: Table of Contents — Your Article Is Long, But Your Reader's Patience Is 3 Seconds
本文组件来自 Web Component Dictionary v2.0 · 网页组件活字典,83组件/8分类/中英双语/实时预览/单文件无依赖。
在线体验:wdsega.github.io/web-components | 购买完整版:payhip.com/b/S9pj2 ¥9.99
3000 字的文章,读者 3 秒决定看不看
你花了 4 小时写了一篇深度技术文章。发布后,平均阅读时长 32 秒。为什么?
因为这 3000 字对读者来说是一堵墙。他不知道里面有什么,不知道值不值得花时间。目录导航(Table of Contents,TOC)就是这堵墙上的地图。
几乎所有技术文档网站都有 TOC:MDN、Vue 文档、React 文档。它不是装饰,是信息架构的一部分。
组件是什么
Web Component Dictionary v2.0 中的目录导航是一个侧边栏组件,自动提取页面标题生成目录,固定吸附在页面一侧,滚动时高亮当前阅读位置。
核心特征:
- 自动提取
<h2>/<h3>标题生成目录 - 侧边栏固定(sticky),不随页面滚动消失
- 当前阅读位置高亮(Scroll Spy)
- 点击跳转 + 平滑滚动
代码拆解
HTML 结构
<div class="toc-container">
<nav class="toc">
<h3 class="toc-title">目录</h3>
<ul class="toc-list" id="tocList">
<!-- JS 动态生成 -->
</ul>
</nav>
<main class="toc-content">
<h2 id="intro">引言</h2>
<h2 id="setup">环境搭建</h2>
<h2 id="usage">使用方法</h2>
<h2 id="advanced">高级技巧</h2>
</main>
</div>
CSS:侧边栏吸顶
.toc-container {
display: flex;
gap: 40px;
max-width: 1200px;
margin: 0 auto;
padding: 20px;
}
.toc {
width: 240px;
flex-shrink: 0;
position: sticky;
top: 80px;
align-self: flex-start;
max-height: calc(100vh - 100px);
overflow-y: auto;
}
.toc-list a {
display: block;
padding: 6px 12px;
color: #888;
text-decoration: none;
font-size: 14px;
border-left: 2px solid transparent;
transition: all 0.2s ease;
}
.toc-list a.active {
color: #e94560;
border-left-color: #e94560;
background: rgba(233,69,96,0.1);
}
.toc-content {
flex: 1;
min-width: 0;
}
关键技巧:
position: sticky; top: 80px— 侧边栏吸顶,留出导航栏空间align-self: flex-start— 防止 sticky 被 flex 拉伸max-height: calc(100vh - 100px)— 目录太长时自己滚动- 活跃项用左边框高亮,视觉引导更清晰
JavaScript:动态生成 + Scroll Spy
function buildTOC() {
const tocList = document.getElementById('tocList');
const headings = document.querySelectorAll('.toc-content h2, .toc-content h3');
headings.forEach(heading => {
if (!heading.id) {
heading.id = heading.textContent
.toLowerCase().replace(/\s+/g, '-');
}
const a = document.createElement('a');
a.href = '#' + heading.id;
a.textContent = heading.textContent;
a.dataset.target = heading.id;
if (heading.tagName === 'H3') {
a.style.paddingLeft = '28px';
a.style.fontSize = '13px';
}
const li = document.createElement('li');
li.appendChild(a);
tocList.appendChild(li);
});
}
// 点击平滑跳转
tocList.addEventListener('click', e => {
if (e.target.tagName === 'A') {
e.preventDefault();
const target = document.getElementById(e.target.dataset.target);
target.scrollIntoView({ behavior: 'smooth' });
}
});
// Scroll Spy
window.addEventListener('scroll', () => {
const headings = document.querySelectorAll('.toc-content h2, .toc-content h3');
let current = '';
headings.forEach(h => {
if (window.scrollY >= h.offsetTop - 120) current = h.id;
});
document.querySelectorAll('.toc-list a').forEach(a => {
a.classList.toggle('active', a.dataset.target === current);
});
});
关键技术点深挖
sticky vs fixed
很多人用 fixed 做侧边栏。问题:fixed 脱离文档流,需要手动算 left/right,响应式布局时位置会跑偏。
sticky 的优势:保持 flex 布局中的原始位置,只”粘”在指定位置。你不需要计算 left 值——在父容器列里自然定位。响应式友好得多。
自动生成 vs 手写 HTML
自动提取的好处是零维护。但如果标题是”那些年我们追过的技术债”这种文学化风格,提取出来的目录可读性差。
推荐:自动提取 + 手动覆盖。JS 生成默认目录,支持在 HTML 里用手写的 <ul class="toc-list"> 覆盖。
移动端适配
小屏幕 240px 侧边栏太奢侈。用 @media (max-width: 768px) 把 TOC 改成顶部横排标签,或收进 <details> 折叠块:
@media (max-width: 768px) {
.toc-container { flex-direction: column; }
.toc { width: 100%; position: static; }
.toc-list { display: flex; flex-wrap: wrap; gap: 8px; }
.toc-list a { border-left: none; border-bottom: 2px solid transparent; }
.toc-list a.active { border-bottom-color: #e94560; }
}
常见坑点
坑1:id 冲突
两个 section 都叫”使用方法” → 都生成 #使用方法 → 锚点失效。解决:检查重复后追加 -2、-3。
坑2:动态内容
SPA/MDX 页面加载时标题还不存在。buildTOC() 要放在内容加载完成的回调里。
坑3:URL hash 污染
点击 TOC 链接改变 URL。用户刷新后跳到锚点位置,但 TOC 没高亮。解决:页面加载时读取 hash,主动高亮对应目录项。
变体拓展
- 折叠式层级:h3 默认隐藏,点击 h2 展开
- 进度条版:目录项旁显示阅读进度百分比
- 浮窗版:移动端右下角浮窗按钮呼出目录
本文组件来自 Web Component Dictionary v2.0 — 83个开箱即用的网页组件,导航/表单/数据展示/多媒体等8大分类,中英双语,实时预览,单文件无依赖。
🔗 在线体验:wdsega.github.io/web-components
🛒 购买完整版源码(¥9.99 一次性买断):payhip.com/b/S9pj2
Component Deep Dive #12: Table of Contents — Your Article Is Long, But Your Reader’s Patience Is 3 Seconds
3,000 Words, 3 Seconds to Decide
You spent 4 hours writing a deep technical article. Published it. Average read time: 32 seconds. Why?
Because to a reader, 3,000 words is a wall. They don’t know what’s inside. A Table of Contents is the map on that wall.
Almost every technical documentation site has a TOC: MDN, Vue docs, React docs. It’s not decoration; it’s information architecture.
What It Is
A sidebar component that auto-extracts headings, sticks to the page side, and highlights the current reading position via Scroll Spy.
Code Breakdown
HTML: A flex container with <nav class="toc"> and <main>. Headings have IDs.
CSS: position: sticky; top: 80px keeps the sidebar visible during scroll. align-self: flex-start prevents flex stretching. Active items highlight with a left border accent.
JS: buildTOC() scans <h2>/<h3> elements and builds anchor links. Click handlers use scrollIntoView({ behavior: 'smooth' }). Scroll listener updates .active class.
Key Insight: sticky > fixed
position: fixed requires manual left/right calculations that break on responsive layouts. sticky stays in its natural flex position and just sticks — no position math needed.
Auto-Generation vs Manual HTML
Auto-extraction = zero maintenance. But literary-style headings produce ugly TOC entries. Best practice: auto-generate by default, with a manual override option using pre-written <ul class="toc-list">.
Mobile Adaptation
At < 768px, restructure TOC as horizontal tag cloud or collapse into a <details> block.
Common Pitfalls
- Duplicate IDs: Two “Usage” sections collide. Add sequential suffixes.
- Dynamic content: In SPAs, headings may not exist at
DOMContentLoaded. CallbuildTOC()in content-loaded callback. - URL hash pollution: Handle URL hash on page load to highlight the correct TOC item.
Variants
- Collapsible hierarchy: h3 hidden, expand on h2 click
- Progress indicator: Reading percentage beside each item
- Floating trigger: Corner button overlay for mobile
This component is from Web Component Dictionary v2.0 — 83 ready-to-use web components across 8 categories. Bilingual, live preview, single-file, zero dependencies.
🔗 Live demo: wdsega.github.io/web-components
🛒 Buy full source code ($9.99 one-time): payhip.com/b/S9pj2