vue实现文章目录
实现文章目录的基本思路
在Vue中实现文章目录,通常需要解析文章内容中的标题标签(如h1、h2、h3等),生成对应的目录结构,并通过点击目录项实现页面内跳转。
解析文章标题
使用DOM操作或正则表达式提取文章内容中的标题元素。可以通过querySelectorAll获取所有标题标签:
const headings = document.querySelectorAll('h1, h2, h3, h4, h5, h6');
生成目录数据结构
遍历获取的标题元素,提取文本内容和层级信息,构建目录的树形结构:

const toc = Array.from(headings).map(heading => ({
id: heading.id || generateId(heading.textContent),
text: heading.textContent,
level: parseInt(heading.tagName.substring(1)),
children: []
}));
渲染目录组件
创建Vue组件来显示生成的目录结构,使用递归组件处理多级嵌套:
<template>
<ul class="toc-list">
<li v-for="item in tocData" :key="item.id">
<a :href="`#${item.id}`">{{ item.text }}</a>
<toc-item v-if="item.children.length" :toc-data="item.children"/>
</li>
</ul>
</template>
添加平滑滚动效果
为目录链接添加平滑滚动行为,提升用户体验:

document.querySelectorAll('a[href^="#"]').forEach(anchor => {
anchor.addEventListener('click', function(e) {
e.preventDefault();
document.querySelector(this.getAttribute('href')).scrollIntoView({
behavior: 'smooth'
});
});
});
高亮当前阅读位置
监听滚动事件,计算当前视口中的标题位置,高亮对应的目录项:
window.addEventListener('scroll', () => {
const currentPosition = window.scrollY;
// 计算哪个标题在视口中
// 更新active状态
});
响应式设计考虑
为目录组件添加响应式设计,在小屏幕设备上可以折叠/展开:
@media (max-width: 768px) {
.toc-container {
position: fixed;
right: 0;
top: 0;
transform: translateX(100%);
transition: transform 0.3s ease;
}
.toc-container.active {
transform: translateX(0);
}
}
性能优化
对于长文章,考虑以下优化措施:
- 使用Intersection Observer替代scroll事件监听
- 实现虚拟滚动减少DOM节点数量
- 对目录数据进行分块加载
完整组件示例
<template>
<div class="toc-container">
<h3>目录</h3>
<ul class="toc-list">
<toc-item
v-for="item in tocData"
:key="item.id"
:item="item"
:active-id="activeId"
@click="handleItemClick"
/>
</ul>
</div>
</template>
<script>
import TocItem from './TocItem.vue';
export default {
components: { TocItem },
props: ['tocData'],
data() {
return {
activeId: null,
observer: null
};
},
mounted() {
this.setupObserver();
},
methods: {
setupObserver() {
this.observer = new IntersectionObserver(entries => {
entries.forEach(entry => {
if (entry.isIntersecting) {
this.activeId = entry.target.id;
}
});
}, { threshold: 0.5, rootMargin: '0px 0px -50% 0px' });
document.querySelectorAll('h1, h2, h3').forEach(heading => {
this.observer.observe(heading);
});
},
handleItemClick(id) {
this.activeId = id;
document.getElementById(id).scrollIntoView({ behavior: 'smooth' });
}
},
beforeDestroy() {
if (this.observer) {
this.observer.disconnect();
}
}
};
</script>






