• 搜索
  • 夜间模式
    ©2026  王较瘦?【Since 2006】 Theme by OneBlog
    搜索
    标签
    # 生活碎片/灵感时刻 # 瞎折腾/主题制作 # 生活碎片/摄影生活 # 生活碎片/游山玩水 # 贵阳市 # 生活碎片/游戏 # 重庆市 # 三亚 # 成都市 # 崇左市
  • 首页>
  • 日志>
  • 正文
  • 前端实现文章目录功能

    2025年02月21日 93 阅读 0 评论 10535 字

    在阿峰那里看到这篇不需要插件,仅仅用css与 js即可实现文章目录功能,收藏备用,兴许以后用得上。#瞎折腾/主题制作

    原文:前端实现文章目录功能-link

    效果图


    [details CSS代码]

    /** 
        前端文章目录 - css
        author:     阿锋
        link:       https://feng.pub
        version:    0.0.3
    */
    .content_toc {
        display: block;
        width: 200px;
        overflow: hidden;
        overflow-y: auto;
        position: fixed;
        bottom: 164px;
        right: 15px;
        padding: 0px;
        margin: 0px;
        background-color: var(--theme-palette-color-8);
        border-radius: 5px;
        box-shadow: 0 0 10px var(--theme-palette-color-1);
        scrollbar-width: none;
        -ms-overflow-style: none;
        z-index: 1;
        transition: all 0.5s;
    }
    
    .content_toc::-webkit-scrollbar {
        display: none;
    }
    
    .content_toc.less {
        width: 40px;
        height: 40px;
        color: var(--theme-palette-color-1);
        background-color: var(--theme-palette-color-1);
        border-radius: 20px;
        box-shadow: none;
        overflow: hidden;
    }
    
    .content_toc.less .content_toc_title h6,
    .content_toc.less .content_toc_main {
        display: none;
    }
    
    .content_toc_title {
        position: relative;
        top: 0;
        left: 0;
        right: 0;
        height: 40px;
        padding: 0;
        margin: 0;
        transition: all 0.5s;
    }
    
    .content_toc_title h6 {
        padding-left: 10px;
        color: var(--theme-palette-color-1);
        font-size: 18px;
        font-weight: 700;
        line-height: 40px;
    }
    
    .content_toc_title h6::before {
        content: '|';
        margin-right: 10px;
        color: var(--theme-palette-color-1);
        background: var(--theme-palette-color-1);
        border-radius: 5px;
    }
    
    .content_toc_title .btn {
        position: absolute;
        width: 20px;
        height: 20px;
        top: 10px;
        right: 10px;
        line-height: 20px;
        font-weight: bold;
        color: var(--theme-palette-color-7);
        background-color: var(--theme-palette-color-1);
        text-align: center;
        font-size: 12px;
        border-radius: 50%;
        box-shadow: 0 0 10px var(--theme-palette-color-6);
        cursor: pointer;
        transition: all 0.5s;
    }
    
    .content_toc_title .btn:hover,
    .content_toc_title .btn.close:hover {
        color: var(--theme-palette-color-8);
        background-color: var(--theme-palette-color-2);
        box-shadow: 0 0 10px var(--theme-palette-color-1);
    }
    
    .content_toc_title .btn.close {
        top: 0;
        right: 0;
        width: 40px;
        height: 40px;
        line-height: 40px;
        border-radius: 20px;
        color: var(--theme-palette-color-7);
        background-color: var(--theme-palette-color-1);
    }
    
    .content_toc_main {
        padding: 0;
        margin: 0;
        overflow-y: auto;
        transition: all 0.5s;
    }
    
    .content_toc_main::-webkit-scrollbar {
        display: none;
    }
    
    .content_toc_tree {
        padding: 0 10px 10px 10px;
        margin: 0;
        list-style: none;
    }
    
    .content_toc_tree li {
        position: relative;
        width: 170px;
        font-size: 14px;
        padding: 0px;
        margin-left: 10px;
        border-radius: 5px;
        color: var(--theme-palette-color-3);
        cursor: pointer;
    }
    
    .content_toc_tree li.active,
    .content_toc_tree li:hover {
        color: var(--theme-palette-color-8);
        background-color: var(--theme-palette-color-1);
    }
    
    .content_toc_tree li::before {
        content: '';
        position: absolute;
        top: 12px;
        left: -10px;
        width: 5px;
        height: 5px;
        background-color: var(--theme-palette-color-6);
        border-radius: 50%;
    }
    
    .content_toc_tree li.active::before,
    .content_toc_tree li:hover::before {
        border-width: 2px;
        background: var(--theme-palette-color-1);
    }
    
    .content_toc_tree li.level_H1 {
        padding-left: 5px;
    }
    
    .content_toc_tree li.level_H2 {
        padding-left: 5px;
    }
    
    .content_toc_tree li.level_H3 {
        padding-left: 1em;
    }
    
    .content_toc_tree li.level_H4 {
        padding-left: 2em;
    }
    
    .content_toc_tree li.level_H5 {
        padding-left: 3em;
    }
    
    .content_toc_tree li.level_H6 {
        padding-left: 4em;
    }
    
    @media screen and (max-width: 992px) {
        .content_toc {
            width: 170px;
        }
    
        .content_toc_tree {
            padding: 0 5px 10px 5px;
        }
    
        .content_toc_tree li {
            width: 150px;
        }
    }

    [/details]

    [details js代码]

    /**
     * 前端文章目录 - js
     * 
     * @author 阿锋
     * @link https://feng.pub
     * @version 0.0.3
     */
    document.addEventListener("DOMContentLoaded", () => {
        // 配置项
        // 获取内容的标题级别
        const levelTOC = 'h1, h2, h3, h4'
        // 获取到标题个数大于该数时才显示文章目录
        const toShowNum = 3
        // 文章目录块的高度偏移量(可视窗口高度减去该高度为文章目录块的高度)
        const tocOffsetHeight = 235
        // 滚动顶部偏移(滚动窗口时内容标题顶部偏移高度,防止顶部浮动导航遮挡标题)
        const topTOCOffsetHeight = 65
        // 关闭文章菜单后,圆球的高度(根据css样式设定)
        const lessHeight = 40
        // 菜单默认状态(1:打开状态;0:关闭状态)
        const defaultState = 1
        // 移动端菜单默认状态
        const mobileDefaultState = 0
    
        // 当前状态
        const initTOCState = (document.documentElement.clientWidth <= 992) ? mobileDefaultState : defaultState
        // 获取文章内容元素
        const entryContent = document.querySelector('.entry-content')
        // 获取元素中H1、H2、H3、H4、H5、H6
        const contentHeadings = entryContent.querySelectorAll(levelTOC)
        if (contentHeadings.length >= toShowNum) {
            // 文章目录HTML结构
            // TOC
            const contentTOCDiv = document.createElement('div')
            if (initTOCState === 1) {
                contentTOCDiv.className = 'content_toc'
            } else {
                contentTOCDiv.classList = 'content_toc less'
            }
            // TOC title
            const tocTitleDiv = document.createElement('div')
            tocTitleDiv.className = 'content_toc_title'
            contentTOCDiv.appendChild(tocTitleDiv)
            // TOC title h6
            const tocTitle = document.createElement('h6')
            tocTitle.innerText = '文档目录'
            tocTitleDiv.appendChild(tocTitle)
            // TOC title btn
            const contentTOCBtn = document.createElement('div')
            if (initTOCState === 1) {
                contentTOCBtn.className = 'btn'
                contentTOCBtn.innerText = '×'
                contentTOCBtn.setAttribute('title', '关闭文档目录')
            } else {
                contentTOCBtn.classList = 'btn close'
                contentTOCBtn.innerText = '目录'
                contentTOCBtn.setAttribute('title', '打开文档目录')
            }
            tocTitleDiv.appendChild(contentTOCBtn)
            // TOC main
            const contentTOCTreeDiv = document.createElement('div')
            contentTOCTreeDiv.className = 'content_toc_main'
            contentTOCDiv.appendChild(contentTOCTreeDiv)
            // TOC main tree
            const contentTOCTree = document.createElement('ul')
            contentTOCTree.className = 'content_toc_tree'
            contentHeadings.forEach((e, k) => {
                // TOC main tree li
                const toc = document.createElement('li')
                toc.className = 'level_' + e.tagName
                toc.innerText = e.textContent
                toc.dataset.toc = k
                contentTOCTree.appendChild(toc)
            });
            contentTOCTreeDiv.appendChild(contentTOCTree)
            // 追加到body
            document.querySelector('body').appendChild(contentTOCDiv)
    
            let currentState = initTOCState
            // 是否需要重设高度
            let needSetHeight = 0
    
            const action = {
                // 设置文档目录的高度
                setTOCHeight: () => {
                    // 标题块高度
                    const tocTitleHeight = tocTitleDiv.clientHeight
                    // 文档可视高度
                    const clientHeight = document.documentElement.clientHeight
                    // 文档目录树高度
                    const tocTreeHeight = contentTOCTree.clientHeight
                    // 文档目录块最大高度
                    const maxTOCHeight = clientHeight - tocOffsetHeight
                    // 文档目录块初始高度
                    const tocHeight = tocTreeHeight + tocTitleHeight > maxTOCHeight ? maxTOCHeight : tocTreeHeight + tocTitleHeight
                    // 文档目录块高度带单位px
                    const tocStyleHeight = tocHeight + 'px'
                    contentTOCDiv.dataset.height = tocStyleHeight
                    contentTOCDiv.style.height = tocStyleHeight
                    // 设置文档目录树块的高度
                    const tocMainHeight = tocHeight - tocTitleHeight
                    contentTOCTreeDiv.dataset.height = tocMainHeight + 'px'
                    contentTOCTreeDiv.style.height = tocMainHeight + 'px'
                },
                // 文档目录打开、关闭切换
                toggleTOC: (to) => {
                    if (currentState === 0) {
                        // 当前是关闭状态
                        if (to == 'open' || to == undefined) {
                            contentTOCBtn.innerText = '×'
                            contentTOCBtn.setAttribute('title', '关闭文章目录')
                            contentTOCDiv.style.height = contentTOCDiv.dataset.height
                            contentTOCDiv.classList.toggle('less')
                            contentTOCBtn.classList.toggle('close')
                            // 更改状态
                            currentState = 1
                            if (needSetHeight === 1) action.setTOCHeight()
                        }
                    } else {
                        // 当前是打开状态
                        if (to == 'close' || to == undefined) {
                            contentTOCBtn.innerText = '目录'
                            contentTOCBtn.setAttribute('title', '打开文章目录')
                            contentTOCDiv.style.height = lessHeight + 'px'
                            contentTOCDiv.classList.toggle('less')
                            contentTOCBtn.classList.toggle('close')
                            // 更改状态
                            currentState = 0
                        }
                    }
                }
            }
    
            // 默认打开状态时需设置高度
            if (initTOCState === 1) {
                action.setTOCHeight()
            } else {
                // 默认关闭状态需要重设高度
                needSetHeight = 1
            }
    
            // 监听窗口变化需要重新计算高度
            window.addEventListener('resize', () => {
                if (currentState === 1) {
                    // 当前是打开状态,直接重设高度
                    action.setTOCHeight()
                } else {
                    // 记录当前窗口已发生变化,需要重设高度
                    needSetHeight = 1
                }
            })
    
            // 绑定打开/关闭目录事件
            contentTOCBtn.addEventListener('click', e => {
                action.toggleTOC()
            })
    
            // 点击文章目录事件
            contentTOCTree.addEventListener('click', e => {
                if (e.target.tagName == 'LI') {
                    const activeToc = contentTOCTree.querySelector('.active')
                    if (activeToc) activeToc.classList.remove('active')
                    e.target.classList.add('active')
                    const TOCIndex = e.target.dataset.toc
                    window.scrollTo({ top: contentHeadings[TOCIndex].offsetTop - topTOCOffsetHeight, left: 0, behavior: "smooth" })
                }
            })
    
            // 监听页面滚动
            document.addEventListener('scroll', () => {
                const scrollTop = document.documentElement.scrollTop
                const activeToc = contentTOCTree.querySelector('.active')
                if (scrollTop < entryContent.offsetTop || scrollTop > (entryContent.offsetHeight + entryContent.offsetTop)) {
                    if (activeToc) activeToc.classList.remove('active')
                } else {
                    const contentTOCLis = contentTOCTree.querySelectorAll('li')
                    contentHeadings.forEach((e, k) => {
                        if (scrollTop >= (e.offsetTop - topTOCOffsetHeight) && scrollTop < (contentHeadings[k + 1] ? contentHeadings[k + 1].offsetTop - topTOCOffsetHeight : entryContent.offsetHeight + entryContent.offsetTop)) {
                            if (activeToc) activeToc.classList.remove('active')
                            contentTOCLis[k].classList.add('active')
                        }
                    })
                }
            })
        }
    });

    [/details]

    使用方法

    将CSS代码及JS代码复制后添加到主题模板相应的css、js文件内即可。某些程序或主题可通过后台添加css、js代码,直接在后台添加也可以。

    本文著作权归作者 [ 王叔书 ] 享有,未经作者书面授权,禁止转载,封面图片来源于 [ 互联网 ] ,本文仅供个人学习、研究和欣赏使用。如有异议,请联系博主及时处理。
    瞎折腾/主题制作
    — END —
    Copyright©2026  All Rights Reserved.  Load:0.012 s
    Theme by OneBlog V3.6.4
    夜间模式

    开源不易,请尊重作者版权,保留基本的版权信息。