这是我参与「第四届青训营 」笔记创作活动的第4天
书接上文(仿掘金项目之实现文章预览|青训营笔记 - 掘金 (juejin.cn))
目录内容收集
前面成功的完成了文章预览的部分,接下来实现基本的目录功能.
到这里我们获取文章目录的内容有两种途径:
第一种是我们前面获取的文章内容本身是html了,这里面已经包含了h1~6标签,也就是目录的内容.我们可以在获取文章内容时,通过正则匹配,获取到具体的h1~6标签的内容
第二种是渲染之后,我们通过ref(vue项目中)得到包含文章的dom元素,再通过getElementByTagName()中的所有h1~6标签.
这里我选择的是第一种,因为第二种当时也没想到.哈哈
现在想想,如果选第二种,渲染文章本身需要花费挺长的时间,如果再之后才匹配h标签,目录加载完成的时间就更长了.
还有个问题,我刚刚给解决了.就是怎么同时匹配所有的h标签而且保证他们在目录中的顺序呢? 这个问题其实很简单,用querySelectorAll('h1,h2,h3,h4,h5,h6)就行了.
我用的第一种,就还是接着讲第一种. 前面已经拿到文章内容了,所以现在就是通过正则匹配获取目录内容
const toc = data.match(/<(h[1-6]).*?\>.*?<\/[hH][1-6]>/g);
注意:我并不能保证我的这个正则通用所有情况,要根据自己dom节点的具体情况来写正则
提醒这一点是因为我前面使用别人的正则来匹配内容,但是他那个正则只能匹配没有属性的h标签,就这么个小问题,我想了好久才发现
通过上面的匹配,我们可以获取到如下内容
从这里可以看出我们已经得到目录的内容了.
仔细观察掘金的文章目录,可以发现有些目录是有缩进的
这个其实就是根据h标签的不同来做的缩进,所以我们不仅需要拿到目录内容,还需要知道是h几标签 再通过一次正则得到具体的需要内容.
let arr = toc.map((item) => {
let data = item.match(/^<[Hh](\d).*?>(.*?)</);
return { id: data[1], content: data[2] };
});
console.log(arr);
目录内容渲染
得到相应的数据,接下来就是使用数据渲染
<ul ref="nav" class="nav">
<li v-for="(i, index) in list" :key="index" :title="i.content">
<div :style="{ marginLeft: size(i.id) }">
{{ i.content }}
</div>
</li>
</ul>
在li里面加的一层div就是用来做目录缩进的 这里是通过margin值的不同来实现缩进
//动态计算缩进大小
const size = (num) => {
return num * 10 + "px";
};
只有最终的成品图了,放一张看看效果吧.
目录跳转功能实现
到这里我们已经实现了目录内容收集和显示,接下来是最基本的点击跳转功能
实现目录跳转也有两种方式,一种是在学html时就接触过的a标签锚点跳转,我一开始也是选择这种方式,但是我的跳转只会白屏...我认为是vue-router产生的影响,但是学艺不精,并不知道具体问题是啥,所以我选的第二种.
第二种就是通过scrollTo实现跳转功能
const jump = (index) => {
let target = document.getElementById(index).offsetTop;
if (target) {
window.scrollTo({
top: target - 80,
});
}
};
给前面用于渲染的li标签添加上点击事件
<li v-for="(i, index) in list" :key="index" :title="i.content" @click="jump(index)" >
这里通过匹配id来获取对应的h标签在页面中的位置,再通过srcollTo到达该位置.
这里还有个问题,啥时候给h标签加的id?
const toc = data.match(/<(h[1-6]).*?\>.*?<\/[hH][1-6]>/g);
toc.forEach((item, index) => {
let _toc = `<div class="jump-site" id=${index}>${item}</div>`;
data = data.replace(item, _toc);
});
大家应该还记得前面我们还获取过所有的h标签,所以通过循环,我们给每一个h标签外面包一个div元素,给这个元素加上id,然后替换原内容中的对应的h标签.
所以为啥不直接在h标签中添加id属性呢?
这里我也是参考了别人的实现,我认为是担心h标签本身就有id属性
为了不造成影响,自己添加一层div确实更好.
现在我们就实现了点击跳转功能 点击li元素,通过index获取对应id的在页面中的位置,然后通过srcollTo到达,实现目录跳转功能.
这里我直接使用index,纯属偷懒,真正项目中不该这样使用
最好添加个给id属性那里加个前缀啥的,比如'jump-1'
当前目录高亮效果
这里先实现点击高亮效果,滚动高亮改天再写,点击高亮非常容易
<li v-for="(i, index) in list" :key="index" :title="i.content" @click="jump(index)" :class="activeIndex === index ? 'active' : ''">
给li元素动态添加class
const jump = (index) => {
activeIndex.value = index; //重点
let target = document.getElementById(index).offsetTop;
if (target) {
window.scrollTo({
top: target - 80,
});
}
};
通过点击事件改变activeIndex,再通过三元表达式判断是不是当前的index是不是等于activeIndex,是就添加active类,改变样式
先到这里,后面写滚动高亮,那才是最麻烦的