实现简单的页面导航功能

本文最后更新于:2024年6月20日 晚上

实现一个简单的目录功能,当点击目录按钮时能够跳转到对应的标题,并高亮显示当前选中的目录,同时对页面滚动进行监听,在页面滚动到标题时同样高亮对应的目录按钮。

布局

首先将页面分为左右两个部分,因为左侧为目录且固定在页面中,所以设置为fixed,右侧则为内容部分,为不与目录重叠,添加margin值来空出位置。

跳转标题

由于在布局中,给不同的段落设置了对应的id,在按钮的点击事件中传入id参数,然后在函数中通过ref获取到整个内容部分的根元素,在根元素中通过传入的id进行querySelector,得到要跳转的段落。通过scrollIntoView方法实现跳转。

1
2
3
4
5
6
7
8
const content_container = ref(null);
function changeMenu(item:any) {
const el = content_container.value.querySelector(`#${item.value}`);
if (el) {
el.scrollIntoView({ behavior: 'smooth' });
console.log(el.offsetTop);
}
}

监听滚动事件

首先遍历所有的段落,根据每个段落的offsetTop为自定义的属性scrollTop设置初始值,该属性用于判断页面滚动到了哪个部分。(即每个段落距离根元素顶部的距离,也就是他们在根元素中的起始位置)

之后对根元素添加滚动的事件监听器,并通过根元素的scrollTop属性得到根元素滚动的距离(可视窗口向下移动了多少),然后将该scrollTop的值与各段落的scrollTop属性进行比较,当两者差值的绝对值小于50时即视为页面已经滚动到了该段落,则更新目录的高亮按钮。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
onMounted(()=>{
sections.value.forEach((item)=>{
const el = content_container.value.querySelector(`#${item.value}`);
if (el) {
item.scrollTop = el.offsetTop;
}
})
content_container.value.addEventListener('scroll', (event) => {
console.log(content_container.value.scrollTop);
sections.value.forEach((item)=>{
if(Math.abs(item.scrollTop-content_container.value.scrollTop)<50){
currentValue.value = item.value;
}
})
});
})

高亮的判定

为了实现目录按钮高亮的变更,设置一个变量currentValue来表示当前显示的段落id,当当前显示的段落id与目录按钮对应的段落id相同时,则该目录按钮应该高亮,为其添加active类来修改样式。

1
2
3
4
5
6
7
8
9
10
<div class="nav-item"
v-for="(item, index) in sections"
:key="item.name"
:index="index"
@click="changeMenu(item)"
:class="currentValue === item.value ? 'active':''"
>
<span class="nav-item-mask" v-if="currentValue !== item.value"></span>
{{ item.name }}
</div>

完整代码

其中html部分中的每个模块可以通过v-for来遍历生成。

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
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
<template>
<div class="main">
<div class="nav-container">
<div class="nav-item"
v-for="(item, index) in sections"
:key="item.name"
:index="index"
@click="changeMenu(item)"
:class="currentValue === item.value ? 'active':''"
>
<span class="nav-item-mask" v-if="currentValue !== item.value"></span>
{{ item.name }}
</div>
</div>
<div class="content" ref="content_container">
<div class="container">
<div id="Start" class="content-item">
<h2 class="name">快速开始</h2>
<p>Lorem...</p>
</div>
<div id="UpDate" class="content-item">
<h2 class="name">检查更新</h2>
<p>Lorem...</p>
</div>
<div id="DownLoad" class="content-item">
<h2 class="name">下载应用</h2>
<p>Lorem...</p>
</div>
<div id="History" class="content-item">
<h2 class="name">更新历史</h2>
<p>Lorem...</p>
</div>
</div>
</div>
</div>
</template>

<script lang="ts" setup>
import { onMounted, ref } from 'vue'

const sections = ref([
{
name: '快速开始',
value: 'Start',
scrollTop:0
},
{
name: '检查更新',
value: 'UpDate',
scrollTop:0
},
{
name: '下载应用',
value: 'DownLoad',
scrollTop:0
},
{
name: '更新历史',
value: 'History',
scrollTop:0
}
])

const content_container = ref(null);
const currentValue = ref('Start');

function changeMenu(item:any) {
const el = content_container.value.querySelector(`#${item.value}`);
if (el) {
el.scrollIntoView({ behavior: 'smooth' });
console.log(el.offsetTop);
}
}

onMounted(()=>{
sections.value.forEach((item)=>{
const el = content_container.value.querySelector(`#${item.value}`);
if (el) {
item.scrollTop = el.offsetTop;
}
})
content_container.value.addEventListener('scroll', (event) => {
console.log(content_container.value.scrollTop);
sections.value.forEach((item)=>{
if(Math.abs(item.scrollTop-content_container.value.scrollTop)<50){
currentValue.value = item.value;
}
})
});
})



</script>

<style scoped>
.nav-container{
position: fixed;
margin-left: 35px;
margin-top: 50px;
}

.nav-item{
position: relative;
display: flex;
background: #60439a;
width: 80px;
height: 80px;
margin-bottom: 10px;
justify-content: center;
align-items: center;
border-radius: 50%;
color: white;
}

.nav-item-mask{
content: '';
position: absolute;
background: linear-gradient(
rgba(0, 0, 0, 0.2) 5%,
rgba(0, 0, 0,0.5)
);
width: 80px;
height: 80px;
top: 0px;
left: 0px;
}

.active{
background-color: #725bdc;
color: white;
}

.main {
display: flex;
background-color: #121212;
color: #d5d5d5;

.content {
flex: 1;
height: 78vh;
overflow-y: auto;
padding: 30px 150px 30px 150px;
line-height:1.5em;
}

.container{
background-color: #1f2020;
padding: 25px;
border-radius: 15px;
border: 1px rgba(255,255,255,0.05) solid;
}

.content-item {
margin-bottom: 30px;

&-p {
padding: 2px 0;
}
}
.content-item-name {
font-weight: bold;
padding: 30px 0 20px 0;
}
.name {
margin-bottom: 10px;
}
}
</style>


Reference

如何使用 Vue3 实现文章目录功能


实现简单的页面导航功能
http://starnight.top/2024/06/20/实现简单的页面导航功能/
作者
Cardy Xie
发布于
2024年6月20日
许可协议