JS 生成文章目录树

原文地址: 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>

运行效果如下:

tree.gif

补充:让目录有层次感

不同级别的目录缩进也不同。比如一级标题<h1> 我们可以设置其 padding-left10px,二级标题 <h2> 需要设置 padding-left20px,依次类推。

实现此功能思路也很简单:在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) ;
}
  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值