赞
踩
今天准备写一个通过博客内容生成目录的功能,在github以及码云上找了很久没有找到合适的一个插件,于是,自己造轮子
通过扫描文章的内容,找出所有的h标签,加入集合,同时给每个h标签按照从上往下的顺序加上id,比如:‘h-1’,‘h-2’
这一部分代码我借用了别人的实现
前端薛小帅的代码
中间有个小bug我改了一下,我的实现
// 将一个集合的数据变成一个树形的数据结构 toTree(data){ // 删除 所有 children,以防止多次调用 data.forEach(function (item) { delete item.children; }); // 将数据存储为 以 id 为 KEY 的 map 索引数据列 var map = {}; data.forEach(function (item) { map[item.id] = item; }); var val = []; data.forEach(function (item) { // 以当前遍历项的pid,去map对象中找到索引的id var parent = map[item.p_id]; // 好绕啊,如果找到索引,那么说明此项不在顶级当中,那么需要把此项添加到,他对应的父级中 if (parent) { (parent.children || (parent.children = [])).push(item); } else { //如果没有在map中找到对应的索引ID,那么直接把 当前的item添加到 val结果集中,作为顶级 val.push(item); } }); return val; }, /** * 生成目录 * */ makeToc(){ // 获取所有的h标签,给他们加上id,同时创建符合toTree方法要求的对象 //{ // id:'',// 抛出id // tag:'',// 抛出标签名称 // label:'',// 抛出标题 // p_id:'',// 抛出父级id // } // 定义参与目录生成的标签 const tocTags = ["H1","H2","H3","H4","H5","H6"]; // 目录树结果 const tocArr = []; // 获取所有标题标签 const headDoms = Array.from(this.$refs.aContent.childNodes).filter(item => tocTags.includes(item.tagName)); // 遍历标题标签 headDoms.forEach((item,index,arr) => { // 给标题添加id item.id = `h-${index + 1}`; // 获取当前节点前面的节点 let prevs = arr.filter((i,j) => j < index); // 过滤前面的节点为合理节点 // 如 h3节点前 只能为 h1 h2 h3 prevs = prevs.filter(i => tocTags.filter((i,j) => j <= tocTags.findIndex(i => i == item.tagName)).includes(i.tagName)); // 对前面的节点进行排序,距离自身节点近的排在前面 // 如 div > p > span > img 当前为img // 常规获取节点为 [div,p,span,img] // 排序后获取节点为 [img,span,p,div] prevs = prevs.sort((a,b) => -(a.id.replace('h-','')) - b.id.replace('h-','')); // 查询距离自身节点最近的不同于当前标签的节点 const prev = prevs.find(i => i.tagName != item.tagName); this.maxum = Math.max(this.maxum,index + 1) tocArr.push({ id:index + 1,// 抛出id tag:item.tagName,// 抛出标签名称 label:item.innerText,// 抛出标题 p_id:item.tagName == "H1" || prev == null ? 0 : Number(prev.id.replace("h-",'')),// 抛出父级id }) }) // 使用上述方法生成树 最后在el-tree的data中使用 tocData即可 this.tocData = this.toTree(tocArr); console.log(this.tocData) },
updated() {
// 需要在data里面加上flag,只触发一次里面内容
if (this.flag) {
this.makeToc()
this.flag = false
}
},
这里我做的是只渲染顶层标签,以及二级标签,三级标签
<div style="background-color: white;position: fixed;width: 16vw;"> <el-row style="font-size: 17px;border-bottom: 1px solid #f5f5f5;padding: 1em;" class="catalog"> 博客目录 </el-row> <template v-for="item in tocData"> <el-row style="padding-top: 1em;padding-bottom: 1em;" > <div class="log-back" style="padding-left: 1em;padding-right: 1em;"> <div style="font-size: 16px;cursor: pointer" @click="handleNodeClick(item.id,$event)" class="log-item">{{item.label}}</div> </div> <template v-for="a in item.children"> <div class="log-back" style="padding-left: 1em;padding-right: 1em;"> <div style="font-size: 16px;margin-left: 1em;margin-top: 1.2em;cursor: pointer" @click="handleNodeClick(a.id,$event)" class="log-item">{{a.label}}</div> </div> <template v-for="b in a.children"> <div class="log-back" style="padding-left: 1em;padding-right: 1em;"> <div style="font-size: 16px;margin-left: 2em;margin-top: 1.2em;cursor: pointer" @click="handleNodeClick(b.id,$event)" class="log-item">{{b.label}}</div> </div> </template> </template> </el-row> </template> </div>
点击哪条目录,就将这条目录的id传过来,id 对应标签h-id
handleNodeClick(data,event) {
// 实现跳转锚点
let id = data
let tag = 'h-'+id
let anchorH = document.getElementById(tag).offsetTop // 得到h标签的位置
this.$emit('changeHeight', anchorH); // 我的页面滚动通过父组件控制,所以发射出去让父组件操作
},
changeHeight(record){ let el = this.$refs.scroll.wrap let cur = el.scrollTop // el-scrollbar获取页面当前位置的属性,定位也是通过改变这个 let flag = false let step = 20 // 由于有可能是向上滑,有可能向下滑,所以需要考虑移动的正负 if (record < cur) { step = -step; flag = true }else if (record == cur) { return } // 设置动画 // 具体含义就是按照每8毫秒执行一次向上移动指定的步长,这样就是一个动画 var timer = setInterval(()=>{ if (flag) { // 说明小于0 if (cur + step < record) { // 说明移动多了,这时候我们改变移动距离,直接移动到指定位置即可 el.scrollTop = record clearInterval(timer) }else { el.scrollTop = cur + step } }else{ if (cur + step > record) { // 说明移动多了,这时候我们改变移动距离,直接移动到指定位置即可 el.scrollTop = record clearInterval(timer) }else { el.scrollTop = cur + step } } cur += step },8) },
分成两小步,第一步是将当前每个标签,以及他们对应的位置交给父级组件
还是在updated里面
updated() {
if (this.flag) {
this.makeToc()
this.flag = false
let values = []
for (let i = 1;i <=this.maxum;i++) {
let tag = 'h-'+i
let anchorH = document.getElementById(tag).offsetTop
console.log(anchorH)
values.push({id:i,height:anchorH})
}
this.$emit('refreshScroll',values)
}
},
// 父组件 ,对这些数据进行保存
refreshScroll(values){
this.values = values
},
第二步是对滚动进行监听,然后改变高亮
// 开启监听
mounted(){
window.addEventListener('scroll',this.handleScroll,true)
}
handleScroll(){
this.$emit('handleScroll') // 同样交给父组件处理
},
// 父组件 handleScroll(){ let top = this.$refs.scroll.wrap.scrollTop for (let i = 0;i < this.values.length - 1;i ++) { // 判断当前页面的位置 应该属于哪个标签,我这里是标签下的内容没有结束,目录都是这个标签高亮 if (top >= this.values[i].height - 1 && top < this.values[i + 1].height) { // -1 是我发现点击目录滑动的时候,会有<1 的误差,导致目录高亮不改变 // 让之变成 this.$refs.detail.changeItem(i) // 通过这个方法,改变目录标签高亮 } } // 特殊处理 第一个和最后一个 if (top< this.values[0].height ) this.$refs.detail.changeItem(0) if (top >= this.values[this.values.length - 1].height - 1 ) this.$refs.detail.changeItem(this.values.length - 1) },
子组件的 changeItem方法
changeItem(index){
var commentsInputs = document.getElementsByClassName('log-item') // 是背景色容器
var items = document.getElementsByClassName('log-back') // 文字容器
for (let i = 0;i < commentsInputs.length;i ++) {
commentsInputs[i].style.color = 'black'
items[i].style.background = 'white'
}
commentsInputs[index].style.color = 'red'
items[index].style.background = '#f5f5f5'
}
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。