正如你现在所看到右侧的文章目录一般;
记录一下怎么生成文章目录、跳转、监听滚动到哪个目录。
思路
大概思路:
在文章页面渲染完毕后,即vue的mounted()事件中,获取文章中
http://blog.zhanghaoran.ren文章页面渲染顺序如下:
- 由服务端返回文章内容(markdown格式);和标签等其它信息
- 渲染文章内容到页面
- 由marked库将服务端传回的markdown内容解析并生成html格式数据;
- 由highlight.js库对渲染在页面上的内容进行代码高亮;
- 获取页面中图片、给文章内链接设置在新标签页打开;
- 获取当前dom中 文章内容区域所有h标签,生成文章目录树。
获取文章目录
<template>
<div class="main">
...
<div class="articleContent" v-html="content" ref="content" id="content">
</div>
<toc :active="active" :headers="toc"></toc>
...
</div>
</template>
<script>
export default{
data() {
return{
active: 0,
toc: '',
content: '文章内容,html格式'
}
}
mounted() {
this.toc = this.get_toc(this.content);
},
methods: {
get_toc (content) {
// 获取h1-h4
let re = /<h([1-4])[^>]+>/g
let result = []
while (true) {
let match = re.exec(content)
if (!match) {
break
}
result.push(match[0])
}
result = result.map(header=>{
return {
class: header.split('h')[1].split(' ')[0],
text: header.split('"')[1]
}
})
// 拿到文章h1-h4目录
return result
},
}
}
</script>
渲染文章目录
<template>
<ul id='toc'>
<li v-for='(header,i) in headers' :style='styleArr[i]' :class='i===active?"active":""'>
<a @click="jumpTo(header.text)">{{header.text.replace(/-\d/, '')}}</a>
</li>
</ul>
</template>
<script>
export default {
name: 'toc',
data () {
return {
}
},
props: ['headers', 'active'],
computed: {
styleArr: function () {
let headers = this.headers
return headers.map(header => {
return {
// padding 层级
paddingLeft: `${(header.class - 1) * 10}px`,
// 层级 字体大小
fontSize: header.class <= 2 ? '16px' : header.class <= 4 ? '15px' : header.class <= 6 ? '15px' : '15px'
}
})
}
},
methods:{
// 跳转目录
jumpTo(objId){
try{
document.documentElement.scrollTop = this.getElementPageTop(document.getElementById(objId));
}catch (e) {
console.log(e)
}
},
// 获取点击的目录距离文档顶部位置
getElementPageTop(element){
try{
var actualTop=element.offsetTop;
var parent=element.offsetParent;
while(parent!=null){
actualTop+=parent.offsetTop+parent.clientTop;
parent=parent.offsetParent;
}
return actualTop - 66;
}catch (e) {
console.log(e)
}
},
}
}
</script>
<style scoped>
#toc{
padding-left:10px;
transition:all .3s;
width:95%;
color:rgb(97,97,97);
list-style-type:none;
background-color:#fff;
}
#toc > li{
text-align:left;
cursor:pointer;
letter-spacing: 1px;
padding: 0 5px;
}
#toc > li > a{
color:#333;
width: 100%;
display: block;
overflow: hidden;
text-overflow:ellipsis;
white-space: nowrap;
}
#toc > li > a:hover{
color: #1a1a1a;
font-weight: bolder;
}
#toc > li.active a{
color: #1a1a1a;
font-weight: bolder;
}
</style>
监听滚动事件,给可视窗中第一个目录添加active样式
<script>
export default{
data() {
return{
active: 0,
toc: '',
content: '文章内容,html格式',
hs: []
}
}
mounted() {
this.hs = this.hs.length ? this.hs : [].slice.call(document.getElementById('content').querySelectorAll('h1,h2,h3,h4'))
window.addEventListener('scroll', this.throttle(this.activeHeaders));
},
methods: {
// 节流, 频繁跳转目录和滚动页面会造成页面卡顿,故设置为4秒执行一次
throttle (fn, delay = 4000) {
// 设置变量默认为true
let flag = true;
// 为了保证this指向,返回一个箭头函数
return (...args) => {
// 判断如果已经在执行就直接return
if (!flag) return;
// 否则就是没有执行,将状态赋值为false
flag = false;
// 设置定时器,设置时间
setTimeout(() => {
// 调用apply方法确保this指向问题
fn.apply(this, args);
// 最后将状态重新更改为true,以便程序下次执行
flag = true;
}, delay)
};
},
activeHeaders () {
if(document.documentElement.clientWidth>=1000){
let hs = this.hs;
for (let h of hs) {
let scrollTop = document.documentElement.scrollTop + 56;
let offsetTop = h.offsetTop;
let innerHeight = window.innerHeight;
if (offsetTop < scrollTop + innerHeight && offsetTop > scrollTop) {
this.active = hs.indexOf(h)
break
}
}
}
},
},
beforeDestroy() {
window.removeEventListener('scroll', this.throttle)
},
}
</script>
真开心,终于有文章目录了,写了好久….