帖子目录

功能介绍

将帖子内部的标题提取出来, 按照层级顺序将他们呈现在目录上, 方便用户浏览大致内容

随着用户滑动界面, 目录会显示用户浏览的位置(具体表现为对应的标题演示变化, 加上active)

当用户直接点击目录上的某一个标题时, 界面也会跳转到相应的位置

学习: 对于任何浏览器, 怎么在不影响滚动功能的情况下, 隐藏滚动条

详解

样式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
  <div class="toc-panel">
<div class="toc-container">
<div class="toc-title">目录</div>
<div class="hiddenMouse">
<div class="toc-list">
<template v-if="tocArray.length === 0">
<div class="no-toc">未解析到目录</div>
</template>
<template v-else>
<div v-for="toc in tocArray">
<span
class="toc-item"
:class="{'active': toc.id === activeChorId}"
:style="{'padding-left': toc.level * 10 + 'px'}"
@click="gotoAnchor(toc.id)"
>{{ toc.title }}</span>
</div>
</template>
</div>
</div>
</div>
</div>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
.toc-panel {
position: absolute;
top: 60px;
right: 0px;
width: 285px;
.toc-container {
position: fixed;
margin: 5px 5px;
width: 285px;
background-color: #fff;
.toc-title {
border-bottom: 1px solid #ddd;
padding: 10px;
}
.hiddenMouse { /*隐藏滚动条但不影响滚动效果*/
max-height: calc(100% - 30px);
overflow: hidden;
width: 285px;
.toc-list {
width: 290px;
padding: 8px 0px 5px 10px;
max-height: 500px;
overflow: auto;
.no-toc {
text-align: center;
color: #757474;
line-height: 13px;
font-size: 13px;
}
.toc-item {
margin: 3px 0;
cursor: pointer;
display: block;
line-height: 35px;
overflow: hidden;
text-overflow: ellipsis;
color: #8d8c8c;
font-size: 14px;
border-radius: 3px;
border-left: 2px solid #fff;
}
.toc-item:hover {
color: #3f3f3f;
border-radius: 0px;
font-size: 15px;
background-color: #f0eeee;
border-left: 2px solid #8fc7d3;
}
.active {
color: #3f3f3f;
border-radius: 0px;
font-size: 15px;
background-color: #f0eeee;
border-left: 2px solid #8fc7d3;
}
}
}
}
}

代码实现

隐藏滚动条

类似于上面的这段代码

1
2
3
4
5
6
7
8
9
10
.hiddenMouse { /*隐藏滚动条但不影响滚动效果*/
max-height: calc(100% - 30px);
overflow: hidden;
width: 285px;
.toc-list {
width: 290px;
padding: 8px 0px 5px 10px;
max-height: 500px;
overflow: auto;
}

给这个盒子再套一层, 设置最大高度和overflow:hidden属性
重点: 里面的这个盒子宽度必须略小于外面这个盒子的高度, 使滚动条能够超出父盒子的宽度限制, 达到隐藏效果

获取dom节点

遍历文章内容区域的所有子元素,找出标题标签(H1 到 H6),然后给这些标题添加唯一的 ID,并将标题信息(标题文本、层级、位置)存储在一个响应式数组中

  • contentDom: 通过 ID #detail 获取到文章内容的容器 DOM 元素。这是假定你的文章内容 HTML 结构被包裹在这个 ID 为 detail 的元素内。

  • childNodes: 获取 contentDom 下所有直接子节点(包括元素节点、文本节点等)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
//更新目录列表 
const tocArray = ref([])
const makeToc = () => {
nextTick(() => {
const tocTags = ["H1", "H2", "H3", "H4", "H5", "H6"]

//获取所有标签
const contentDom = document.querySelector('#detail')
const childNodes = contentDom.childNodes
let index = 0
//过滤非标题节点
childNodes.forEach((item) => {
let tagName = item.tagName
// console.log(tagName)
if (tagName === undefined || !tocTags.includes(tagName.toUpperCase())) {
return true //相当于continue
}
index++
let id = "toc" + index //生成唯一ID, 如"toc1"
item.setAttribute("id", id) //修改属性
tocArray.value.push({
id: id,
title: item.innerText,
level: Number.parseInt(tagName.substring(1)),
offsetTop: item.offsetTop,
})
})
})
}

跳转到相应的锚点

1
2
3
4
5
6
7
8
9
10
//跳转到相应的锚点
const activeChorId = ref(null)
const gotoAnchor = (domId) => {
activeChorId.value = domId
const dom = document.querySelector('#'+domId)
dom.scrollIntoView({
behavior: "smooth", //平滑滚动
block: "start", //把目标标题滚动到浏览器窗口的最上方
})
}

获取滚动条位置

1
2
3
4
5
6
7
const getScrollTop = () => {
let scrollTop =
document.documentElement.scrollTop ||
window.pageYOffset ||
document.body.scrollTop
return scrollTop
}

同步当前滚动条的位置与目录中活跃的标题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
const listenerScroll = () => {

let currentScrollTop = getScrollTop() //获取当前的位置
//.some() 方法会遍历数组中的每一个元素,并对每个元素执行传入的回调函数
//不会修改原数组
tocArray.value.some((item, index) => {
if (index < tocArray.value.length - 1 &&
currentScrollTop >= tocArray.value[index].offsetTop &&
currentScrollTop < tocArray.value[index + 1].offsetTop ||
index == tocArray.value.length - 1 &&
currentScrollTop < tocArray.value[index].offsetTop
) {
activeChorId.value = item.id //同步样式状态
return true
}
})
}

.some

  • 它接收一个回调函数作为参数。

  • 这个回调函数会对数组中的每个元素执行一次测试。

  • 一旦找到第一个让回调函数返回 true(或一个真值)的元素,.some() 就会立即停止遍历,并返回 true

  • 如果遍历完整个数组,所有的元素都未能通过测试(即回调函数对所有元素都返回了 false),则 .some() 返回 false

挂载与清空

1
2
3
4
5
6
7
onMounted(() => {
  window.addEventListener("scroll", listenerScroll, false)
})

onUnmounted(() => {
  window.removeEventListener("scroll", listenerScroll, false)
})