前端实现文章目录功能,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代码,直接在后台添加也可以。

guest

此站点使用Akismet来减少垃圾评论。了解我们如何处理您的评论数据

13 评论
最新
最旧
内联反馈
查看所有评论
yuxiyuqi

挺好的,一直想加个目录

来自上海
安迪

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

来自广东
sagrre

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

来自云南
网友小宋

有些主题都是集成了吧

来自美国
S̆̈

折腾更健康

来自江苏
青山

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

来自江西
威言威语

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

来自上海