原文地址: JS 生成文章目录树
我们在网页浏览文章时,会发现在页面上总有一块固定区域,用来展示文章的目录结构,以帮助我们快速定位到对应的内容。今天我们就用 JavaScript
来实现文章目录树的功能。
功能需求
1). 根据文章的 <h>
标签生成目录树;
2). 点击目录,页面可以翻动到对应的位置;
3). 滚动页面,对应的目录可以动态高亮;
实现思路
假设后端返回的 html
数据如下:
<div class="htmlbox">
<h1>标题一</h1>
<p>…………</p>
<h2>标题1.1</h2>
<p>…………</p>
<h2>标题1.2</h2>
<p>…………</p>
<h1>标题二</h1>
<p>…………</p>
<h2>标题2.1</h2>
<p>…………</p>
<h3>标题2.2.1</h3>
<p>…………</p>
<p>…………</p>
</div>
根据文章的 <h>
标签生成目录树
创建一个 <ul>
元素作为目录的容器,然后获取到 .htmlbox
中所有的 <h>
标签,通过遍历 <h>
获取其内容,并且生成与之对应的 <li>
标签,最后把 <li>
依次添加到 <ul>
中。
点击目录,页面翻动到对应的位置
在遍历 <h>
元素过程中,需要为其生成一个唯一的 id
,并且需要将此 id
通过自定义属性绑定在动态创建的 <li>
元素上,目的是为了在点击 <li>
的时候,可以根据此自定义属性找到对应的 <h>
标签,最后通过 scrollIntoView()
进行页面滚动。
滚动页面,对应的目录可以动态高亮
需要监听浏览器的 scroll
事件,获取滚动条距离页面顶部的位置。然后倒叙遍历 <h>
元素时,判断滚动条到浏览器顶部的距离是否 >= 当前 <h>
标签到浏览器顶部的距离。如果条件成立,我们就把此 <h>
对应的 <li>
给设置高亮。(必须要倒叙遍历,否则高亮的 <li>
永远都是第一个)。
代码实现
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>目录</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
/* 摸你文章每个段落的高度 */
p {
height: 500px;
background-color: beige;
}
/* 文章整体宽度 */
.htmlbox {
width: 700px;
}
/* 目录容器样式 */
.catalogbox {
position: fixed;
top: 10px;
right: 10px;
width: 300px;
max-height: 80vh;
}
/* 高亮目录样式 */
.current-catalog {
background-color: #0099dd;
color: #fff;
}
</style>
</head>
<body>
<div class="htmlbox">
<h1>标题一</h1>
<p>…………</p>
<h2>标题1.1</h2>
<p>…………</p>
<h2>标题1.2</h2>
<p>…………</p>
<h1>标题二</h1>
<p>…………</p>
<h2>标题2.1</h2>
<p>…………</p>
<h3>标题2.2.1</h3>
<p>…………</p>
<p>…………</p>
</div>
<script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.6.0/jquery.min.js"></script>
<script type="text/javascript">
$(function() {
// *************功能一:生成目录******************************
var eles = ['h1', 'h2', 'h3', 'h4', 'h5', 'h6']
// 获取文章里 h1 到 h6 的元素
var doms = document.querySelector('.htmlbox').querySelectorAll(eles.toString())
if (! doms || ! doms.length) {
return ;
}
// 创建目录盒子
let $ul = $('<ul class="catalogbox"></ul>')
// 目录的下标
let index = 0
for (let h of doms) {
let tag = h.nodeName.toLowerCase()
if (! eles.includes(tag)) {
continue ;
}
// 生成每个目录的id,绑定在 h 标签上
let id = `catalog_${++index}`
h.setAttribute('id', id)
// 获取 h 标签的内容
let text = h.innerHTML.replace(/<\/?[^>]+>/g, '')
// 生成 li 元素,需要绑定h 的id,以便于点击目录时可以找到对应的 h 标签
let $li = `<li catalog="${id}">${text}</li>`
$ul.append($li)
}
$('body').append($ul)
// *************功能二:点击目录滚动到对应区域******************************
$('li[catalog]').on('click', function() {
// 获取每个li 上绑定的catalog值,对应着唯一的 h 标签
let id = $(this).attr('catalog')
document.querySelector(`#${id}`).scrollIntoView({
behavior: 'smooth'
})
})
// *************功能三:目录跟随滚动高亮******************************
window.addEventListener('scroll', function() {
// 获取浏览器滚动条距离顶部的高度
let scroll = document.documentElement.scrollTop || document.body.scrollTop
for (let i = doms.length - 1; i >= 0 ; i--) {
// 倒叙遍历所有的 h 标签,如果滚动条的 scrollTop 刚刚大于 h 区域的 offsetTop,
// 将此h 标签对应的 目录 设置为高亮
if (parseInt(scroll) >= Math.ceil(doms[i].offsetTop) ) {
let id = doms[i].getAttribute("id")
$('li[catalog]').each(function() {
if ($(this).attr('catalog') === id) {
$(this).addClass('current-catalog')
} else {
$(this).removeClass('current-catalog')
}
})
break ;
}
}
})
})
</script>
</body>
</html>
运行效果如下:
补充:让目录有层次感
不同级别的目录缩进也不同。比如一级标题<h1>
我们可以设置其 padding-left
为 10px
,二级标题 <h2>
需要设置 padding-left
为 20px
,依次类推。
实现此功能思路也很简单:在js
中创建 <li>
的时候,获取与之对应的 <h>
标签中的数字,然后给 <li>
绑定一个样式,最后我们只需要在 css
中对样式进行设置即可。比如 <h1>
标签对应的 <li>
,我们可以给其添加 style="--level: 1"
,<h2>
标签对应的 <li>
,我们可以给其添加 style="--level: 2"
,然后在 css中通过 var()
函数去设置样式。
关于 var() 函数,可以看 CSS var() 函数 一文。
代码实现
js:
// 获取当前目录级别,需要根据目录级别设置css
let level = tag.replace('h', '')
// 生成 li 元素
let $li = `<li style="--level: ${level}" catalog="${id}"">${text}</li>`
css:
li[catalog] {
padding-left: calc(var(--level) * 10px) ;
}