前端实现文章目录功能,CSS、JS代码分享

又折腾博客了,之前使用WPJAM插件文实现章目录功能,发现大部分WordPress博客都在使用WPJAM的文章目录,使用起来简单方便。

适用于WPJAM文章目录的js、css代码

https://feng.pub/1020235787.html

WPJAM的文章目录可以单独设置是否关闭文章目录功能,也可以单独设置文章目录显示到的级别,这一点很灵活。但WPJAM采用后端PHP构建文章目录的HTML结构,我在想是否可以让前端来实现,于是着手做这件事情,现把代码分享出来。

效果图

CSS代码

根据需要可以修改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;
    }
}

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')
					}
				})
			}
		})
	}
});

使用方法

将CSS代码及JS代码复制后添加到主题模板相应的css、js文件内即可。

某些程序或主题可通过后台添加css、js代码,直接在后台添加也可以。

阿锋
阿锋

沧海一粟,微亮而渺小。

用户头像

这个站点使用 Akismet 来减少垃圾评论。了解你的评论数据如何被处理

13 评论
最新
最旧
内联反馈
查看所有评论
用户头像

挺好的,一直想加个目录

来自上海
用户头像

建议默认折起来,不是很习惯目录打开状态

来自广东
用户头像

昨天有博友说让我搞个返回顶部,就想到目录一起搞了,今天来你这就发现了,现成的用起来

来自云南
用户头像

有些主题都是集成了吧

来自美国
用户头像

折腾更健康

来自江苏
用户头像

我的博客目录也是用的wpjam的相关代码,但是没有你这么美观,我想让它和你博客一样,悬浮在侧边,不知是否有空帮忙看看?

来自江西
用户头像

目录图标点击展开的时候滚动条看着有点碍事。

来自上海