内部目录选定 | 青训营笔记

108 阅读3分钟

这是我参与第四届青训营笔记创作活动的第28天

(本文在此声明,此文为本作者另一个账号所写,参与青训营后发现发现青训营手机号填的不是那个,特此将笔记转载过来)

什么是内部目录选定呢?

内部目录选定就是根据一篇文章里的富文本标签进行选中标题作为目录内容,然后在内部放置一个目录,目录里面选中的标题随着页面滚动到的位置而改变。

这种功能一般用于各种读写文章的网站里面,方便读者了解一篇文章的结构以及阅读到了哪里。

获取富文本DOM结构

要想得到一个文章里面的所有标题级别的标签,我们首先要获得这个文章里面的所有的子标签,进行判断才能知道哪一个标签是标题,对标题录入目录中。

这里为了方便理解,我就直接把富文本用几个标签代替。

<template>
    //这不是富文本标签,但意思是这个意思
    <div id='rich-text'>
        <h1>这是第一个一级标签</h1>
        <h2>这是第一个二级标签</h2>
        <h3>这是第一个三级标签</h3>
        <p>这是一个普通的段落标签</p>
        <h1>这是第二个一级标签</h1>
    </div>
</template>

js部分:

//数据源
data() {
    return {
        dom:[]
    }
}
//mounted生命周期函数
let structure = document.getElementById('rich-text').childNodes   //获取id为rich-text的标签的所有子节点
for(let i=0;i<structure.length;i++){
  if(structure[i].nodeName=='H1'||structure[i].nodeName=='H2'||structure[i].nodeName=='H3'||structure[i].nodeName=='H4'||structure[i].nodeName=='H5'||structure[i].nodeName=='H6'){
      let obj = {}
      obj.index = i;     //存储这个标题在所有子标签里的下标,以后会有用
      obj.title = structure[i].innerHTML; //存取该标题里的内容
      obj.label = structure[i].nodeName.slice(1,2);   //存取该标题是几级标题
      obj.blue=false;             //让该标题具有一个blue属性,后面会有用
      this.dom.push(obj)            //写进数据源
  }
}

获取到了富文本里的所有标题级别的标签后我们就可以拿数据源里取到的数据进行页面的渲染啦。

这是我参与第四届青训营笔记创作活动的第30天

之前已经获取到了所有我们要用到的DOM结构内容,那么接下来我们就可以对页面进行渲染了。

第一步我们要对目录所在的盒子有一个初步的构造,然后用v-for循环item,打上花括号进行渲染即可。

html:

<div class="list-part-four" :class="fix==true?'mm':''">
   <div class="list-part-four-title">
       <span>目录</span>
   </div>
   <div class="list-part-four-bar"></div>
   <div class="list-part-four-items">
       <div class="list-part-four-item" v-for="(item,index) in dom" :key="index">
            <div v-if="item.blue==true" class="asm"></div>
            <span v-if="item.label=='1'" class="hh1">{{item.text}}</span>
            <span v-if="item.label=='2'" class="hh2">{{item.text}}</span>
            <span v-if="item.label=='3'" class="hh3">{{item.text}}</span>
            <span v-if="item.label=='4'" class="hh4">{{item.text}}</span>
            <span v-if="item.label=='5'" class="hh5">{{item.text}}</span>
            <span v-if="item.label=='6'" class="hh6">{{item.text}}</span>
       </div>      
   </div>
</div>

css:

.list-part-four{
      padding:1.6rem 1.6rem;
      background-color: #fff;
      box-sizing: border-box;
      border-radius: 0.2rem;
      .list-part-four-title{
        font-size: 1.4rem;
      }
      .list-part-four-bar{
        width: 100%;
        height: 0.01rem;
        background-color: rgb(222, 222, 222);
        margin-top: 1.5rem;
        margin-bottom: 1rem;
      }
      .list-part-four-items{
        max-height: 50rem;
        overflow-y: scroll;
        .list-part-four-item{
          padding: 0.5rem 0.6rem;
          box-sizing: border-box;
          width: 100%;
          // overflow: hidden;
          text-overflow: ellipsis;
          font-size: 1.4rem;
          position: relative;
          cursor: pointer;
          .asm{
            position: absolute;
            z-index: 99;
            left: 0rem;
            width: 0.4rem;
            height: 1.6rem;
            border-top-right-radius: 0.3rem;
            border-bottom-right-radius: 0.3rem;
            background-color: rgb(66, 156, 240);
          }
          .hh1{
          
          }
          .hh2{
            margin-left: 1.5rem;
          }
          .hh3{
            margin-left: 3rem;
          }
          .hh4{
            margin-left: 4.5rem;
          }
          .hh5{
            margin-left: 6rem;
          }
          .hh6{
            margin-left: 7.5rem;
          }
        }
        .list-part-four-item:hover{
          background-color: rgb(236, 236, 236);
        }
      }
      .list-part-four-items::-webkit-scrollbar {
        width: 4px;    
      }
      .list-part-four-items::-webkit-scrollbar-thumb {
        border-radius: 10px;
        box-shadow: inset 0 0 5px rgba(0,0,0,0.2);
        background: rgba(0,0,0,0.2);
      }
      .list-part-four-items::-webkit-scrollbar-track {
        box-shadow: inset 0 0 5px rgba(0,0,0,0.2);
        border-radius: 0;
        background: rgba(0,0,0,0.1);
      }
    }
    .mm{
      position: fixed;
      top:5rem;
      width: 28rem;
    }

效果如图:

1661257181570.png

那么,接下来我们就可以对目录里的标题进行选中,页面滚动到何处就选中目录中哪个位置的标题。

当页面也渲染完成了,那么我们就可以开始进行JS选定了目录里面的标签了。

获取到当前屏幕页面里面所有标题级标签与屏幕顶部之间的距离

window.addEventListener('scroll',(e)=>{
      var scrollTop = window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop
      var arr = []
      for(let i=0;i<this.dom.length;i++){
        if(structure[parseInt(this.dom[i].ind)].offsetTop-scrollTop>=0){
          this.dom[i].blue = true;
          arr.push(1)
          for(let j=i+1;j<this.dom.length;j++){
            this.dom[j].blue = false;
          }
          break;
        }else{
          this.dom[i].blue = false;
        }
      }
      if(arr.length==0){
        this.dom[this.dom.length-1].blue = true;
      }
    },true)

这里,我们首先获取到屏幕上方被隐藏的所有高度,即scrollTop

再对数据源里的获取到的标题dom进行循环

我们之前在dom里的每一个对象里存了一个它在富文本中所有子标签的子标签的下标,那么这里就用到了

用它的下标在DOM结构里找到它在屏幕中距离顶端的距离,一旦它在屏幕中距离顶端的距离减去屏幕上端隐藏的距离大于等于0,那么就说明它是此刻的位置是我们要选中的那个标题。

一旦我们找到了一个这样的标签,那么接下来的标签我就让它里面的blue属性全部为false,一旦执行完就break出去

同时我们还要考虑一种情况,如果所有标题所在DOM结构所在距离屏幕顶部的高度减去屏幕上方隐藏高度的距离都是负数,那么此刻数组arr的长度就为0,那么我们就默认选中最后一个标题。

同时做完这些,我们还要到html结构里面加一个

<div v-if="item.blue==true" class="asm"></div>

的标签,只要该item的blue属性为true,那么就会出现这个标签,相对于item进行定位,然后给class='asm'加上如下CSS样式:

.asm{
   position: absolute;
   z-index: 99;
   left: 0rem;
   width: 0.4rem;
   height: 1.6rem;
   border-top-right-radius: 0.3rem;
   border-bottom-right-radius: 0.3rem;
   background-color: rgb(66, 156, 240);
}

那么效果如下:

1661259324378.png

那么目录内部选中就此结束啦。