vue 中实现侧边导航栏跟随对应的模块滚动

子模块

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<div class="box">
<ul class="list">
<li
v-for="(item, index) in navList"
:key="index"
@click="scrollTo(index)"
:class="{ active: active === index }"
class="text-ellipsis"
:title="item.title"
>
{{ item.title }}
</li>
</ul>
</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
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
<script lang="ts">
import { defineComponent, onMounted, onUnmounted, reactive, toRefs } from "vue";
export default defineComponent({
props: {
navList: {
type: Array,
default: [],
},
anchorClass: {
type: String,
default: "",
},
},
setup(props) {
let DATA: any = reactive({
active: 0,
});
let METHODS = reactive({
// 滚动监听器
onScroll() {
// 获取所有 锚点元素
const navContents = document.querySelectorAll(props.anchorClass); // 所有锚点元素的 offsetTop
const offsetTopArr: any[] = [];
navContents.forEach((item) => {
let newitem = <HTMLElement>item;
offsetTopArr.push(newitem.offsetTop);
}); // 获取当前文档流的 scrollTop
const scrollTop =
document.documentElement.scrollTop || document.body.scrollTop; // 定义当前点亮的导航下标
let navIndex = 0;
for (let n = 0; n < offsetTopArr.length; n++) {
// 如果 scrollTop 大于等于第n个元素的 offsetTop 则说明 n-1 的内容已经完全不可见
// 那么此时导航索引就应该是n了
if (scrollTop >= offsetTopArr[n]) {
navIndex = n;
}
}
DATA.active = navIndex;
}, //跳转到指定索引的元素
scrollTo: (index: any) => {
// 获取目标的 offsetTop
// css选择器是从 1 开始计数,我们是从 0 开始,所以要 +1
// const targetOffsetTop = document.querySelector(`.content div:nth-child(${index + 1})`).offsetTop
let navContents = document.querySelectorAll(props.anchorClass);
const currentOffsetTop = <HTMLElement>navContents[index];
const targetOffsetTop = currentOffsetTop.offsetTop; // 获取当前 offsetTop
let scrollTop =
document.documentElement.scrollTop || document.body.scrollTop; // 定义一次跳 50 个像素,数字越大跳得越快,但是会有掉帧得感觉,步子迈大了会扯到蛋
const STEP = 50; // 判断是往下滑还是往上滑
if (scrollTop > targetOffsetTop) {
// 往上滑
smoothUp();
} else {
// 往下滑
smoothDown();
} // 定义往下滑函数
function smoothDown() {
// 如果当前 scrollTop 小于 targetOffsetTop 说明视口还没滑到指定位置
if (scrollTop < targetOffsetTop) {
// 如果和目标相差距离大于等于 STEP 就跳 STEP
// 否则直接跳到目标点,目标是为了防止跳过了。
if (targetOffsetTop - scrollTop >= STEP) {
scrollTop += STEP;
} else {
scrollTop = targetOffsetTop;
}
document.body.scrollTop = scrollTop;
document.documentElement.scrollTop = scrollTop; // 关于 requestAnimationFrame 可以自己查一下,在这种场景下,相比 setInterval 性价比更高
requestAnimationFrame(smoothDown);
}
} // 定义往上滑函数
function smoothUp() {
if (scrollTop > targetOffsetTop) {
if (scrollTop - targetOffsetTop >= STEP) {
scrollTop -= STEP;
} else {
scrollTop = targetOffsetTop;
}
document.body.scrollTop = scrollTop;
document.documentElement.scrollTop = scrollTop;
requestAnimationFrame(smoothUp);
}
}
},
});
onMounted(() => {
window.addEventListener("scroll", METHODS.onScroll);
});
onUnmounted(() => {
window.removeEventListener("scroll", METHODS.onScroll);
});
return {
...toRefs(DATA),
...toRefs(METHODS),
};
},
});
</script>
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
<style scoped lang="scss">
.box {
width: 100%;
z-index: 9999;
.list {
background: #fff;
border: 1px solid #fff;
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
// width: 250px;
width: calc(100vw / 7.68);
padding: 5px 10px;
position: fixed;
top: 110px;
left: 60px;
font-size: 14px;
li {
padding: 8px 0;
cursor: pointer;
}
.active {
color: #f5be1f;
}
}
}
</style>