.catalog-card{背景:白色;边界半径:8px;盒子阴影:03px8px6pxrgba(7,17,27,0.05);填充:20px24px;width:100%;margin-top:25px;box-sizing:border-box;}.??catalog-card-header{text-align:left!important;margin-bottom:15px;display:flex;justify-内容:空间之间;对齐项目:中心;}.catalog-icon{字体大小:18px;margin-right:10px;颜色:dodgerblue;}.catalog-card-headerdiv>span{字体大小:17px;color:#4c4948;}.progress{color:#a9a9a9;font-style:italic;font-size:140%;}.catalog-content{max-height:calc(100vh-120px);overflow:auto;margin-right:-24px;padding-right:20px;}.catalog-item{color:#666261;margin:5px0;line-height:28px;cursor:pointer;transition:all0.2sease-in-out;字体-尺寸:14px;填充:2px6px;显示:-webkit-box;溢出:隐藏;文本溢出:省略号;-webkit-line-clamp:1;-webkit-box-orient:vertical;&:hover{color:#1892ff;}}.active{background-color:#;color:white;&:hover{background-color:#0c82e9;color:white;}}以下文章来自web前端开发前言我在做一个博客项目KilaKilaBlog有一段时间了,经过四处寻找,我发现没有专门满足我需求的目录组件,于是决定自己动手,完成一个目录组件,满足以下预期目标:当前正在阅读并隐藏其他章节的字幕显示阅读进度父子关系,所以我们应该使用树数据结构来解决这个问题。我们遍历文章容器中的所有标签。如果我们遇到
和
这样的标签,我们创建一个节点,把它放在列表中,然后使用v-for命令生成目录。下面分析一下每个节点需要有哪些属性_前端训练。树节点应具有的属性包括:父节点的指针parent,子节点的指针listchildren。因为一个节点代表一个标题,所以还包括:标题的ID号id(v-for用的key),标题名(加上标题的序号),原标题名rawName和可见性title是Visible,当我们点击title的时候,应该会滚动到title所在的位置,所以还有一个scrollTop属性_web前端训练。当我们遍历文章容器中的所有标签时,需要判断当前遇到的标签与上一个标签的父子关系,所以必须有一个level属性来表示每个节点的级别。下面是工具实体代码:0">目录
spanclass="progress">{{progress}}脚本>从“vue”导入{reactive,ref};导出默认{name:“KilaKilaCatalog”,setup(props){lettitles=reactive(getTitles());letcurrentTitle=reactive({});letprogress=ref(0);//获取目录的标题functiongetTitles(){lettitles=[];letlevels=["h1","h2","h3"];letarticleElement=document.querySelector(props.container);if(!articleElement){returntitles;}letelements=Array.from(articleElement.querySelectorAll("*"));//调整标签等lettagNames=newSet(elements.map((el)=>el.tagName.toLowerCase()));对于(leti=levels.length-1;i>=0;i--){if(!tagNames.has(levels[i])){levels.splice(i,1);}}letserialNumbers=levels.map(()=>0);for(leti=0;i0){letlastNode=titles.at(-1);//遇到子标题if(lastNode.levelnode.level){serialNumbers.fill(0,level+1);letparent=lastNode.parent;while(parent){if(parent.level=0;i--){consttitle=titles[i];if(title.scrollTop<=window.scrollY){if(currentTitle.id===title.id)return;Object.assign(currentTitle,title);//展开节点setChildrenVisible(标题,吨rue);visibleTitles.push(title);//展开父节点letparent=title.parent;while(parent){setChildrenVisible(parent,true);visibleTitles.push(parent);parent=parent.parent;}//折叠剩余节点for(consttoftitles){if(!visibleTitles.includes(t)){setChildrenVisible(t,false);}}return;}}});//设置子节点可见性functionsetChildrenVisible(title,isVisible){for(constchildoftitle.children){child.isVisible=isVisible;}}//滚动到指定位置functionscrollToView(scrollTop){window.scrollTo({top:scrollTop,behavior:"smooth"});}return{titles,currentTitle,progress,scrollToView};},props:{container:{type:String,default:".post-body.article-content",},},};.catalog-card{背景:白色;边界半径:8px;盒子阴影:03px8px6pxrgba(7,17,27,0.05);填充:20px24px;width:100%;margin-top:25px;box-sizing:border-box;}.??catalog-card-header{text-align:left!important;margin-bottom:15px;display:flex;justify-内容:空间之间;对齐项目:中心;}.catalog-icon{字体大小:18px;margin-right:10px;颜色:dodgerblue;}.catalog-card-headerdiv>span{字体大小:17px;color:#4c4948;}.progress{color:#a9a9a9;font-style:italic;font-size:140%;}.catalog-content{max-height:calc(100vh-120px);overflow:auto;margin-right:-24px;padding-right:20px;}.catalog-item{color:#666261;margin:5px0;line-height:28px;cursor:pointer;transition:all0.2sease-in-out;字体-尺寸:14px;填充:2px6px;显示:-webkit-box;溢出:隐藏;文本溢出:省略号;-webkit-line-clamp:1;-webkit-box-orient:vertical;&:hover{color:#1892ff;}}.active{background-color:#;color:white;&:hover{background-color:#0c82e9;color:white;}}