原理为监听页面滚动事件,计算每个菜单容器clientHeight的高度和scollHeight高度,判断scollHeight是否已经大于容器位置的高度,若已达到了容器位置的高度则, 对应菜单则高亮显示。
直接上代码:
<div>
<div class="container" ref="container">
<!--菜单栏位置-->
<ul class="nav">
<li v-for="i in 6" :key="i">
<p
:class="{'active-font': i === activeNo}"
ref="nav"
@click="navClick(i)"
>
菜单{{i}}
</p>
</li>
</ul>
<!--菜单对应正文位置-->
<article>
<div
:style="{
backgroundColor: getBackColor(i),
height: 100 * i + 'px'
}"
v-for="i in 6"
:key="i"
ref="article"
>
菜单{{i}}
</div>
</article>
</div>
</div>
js
data() {
return {
activeNo: 1 //菜单标题高线显示判断, 默认菜单标题第一个高亮
flag: null //判断点击了左边菜单栏的那个菜单
}
},
methods: {
// 防抖, 防止方法多次判断, 性能优化
debounce(func, wait, immediate) {
let timeout, args, context, timestamp, result;
const later = function () {
// 据上一次触发时间间隔
const last = new Date() - timestamp;
// 上次被包装函数被调用时间间隔last小于设定时间间隔wait
if (last < wait && last > 0) {
timeout = setTimeout(later, wait - last);
} else {
timeout = null;
// 如果设定为immediate===true,因为开始边界已经调用过了此处无需调用
if (!immediate) {
result = func.apply(context, args);
if (!timeout) context = args = null;
}
}
};
return function () {
context = this;
args = arguments;
timestamp = new Date();
const callNow = immediate && !timeout;
// 如果延时不存在,重新设定延时
if (!timeout) timeout = setTimeout(later, wait);
if (callNow) {
result = func.apply(context, args);
context = args = null;
}
return result;
};
},
//菜单标题点击事件
navClick(i) {
this.flag = i;
//获取菜单内容显示位置
const scrollBox1 = this.$refs.article[i - 1];
//滚动到菜单内容位置
scrollBox1.scrollIntoView();
//对应菜单标题高亮显示
this.activeNo = i
},
//内容背景色
getBackColor(index) {
const arr = ['', '#e6e6e6', '#fd5331', '#c00ab9', '#149ce1', '#f99831', '#0aff3c', '#dbe6f7'];
return arr[index]
}
},
mounted() {
const self = this;
const scrollCallback = self.debounce(() => {
//菜单内容容器集合
const articleList = self.$refs.article;
//每个article容器高度
const articleHeight = articleList.map(item => {
return item.clientHeight;
});
let scrollTopList = []; //每个article距离顶部的高度
for (let i = 0; i < articleHeight.length; i++) {
if (i === 0) scrollTopList.push(articleHeight[i]);
else{
let num = 0;
for (let j = 0; j <= i; j++) {
num += articleHeight[j]
}
scrollTopList.push(num)
}
}
const top = self.$refs.container.scrollTop; //设置变量top,表示当前滚动条到顶部的值
/*
* 特殊情况, 当滚动到底部且页面存在多个菜单, 导致菜单栏无法高亮显示
* 设置flag, 判断是通过点击具体哪个菜单的跳转 flag为null表示滚动事件
* */
if (self.flag !== null){
//当前点击的菜单距离顶部的偏移高度
const curMeunOffTopHeight = scrollTopList[self.flag - 1];
//若当前点击的菜单距离顶部的偏移高度大于滚动高度则不继续往下执行
if (curMeunOffTopHeight >= top) {
self.flag = null;
return
}
}
for (let i = 0; i < scrollTopList.length; i++) {
//判断滚动条滚动的高度对应的菜单, 使菜单标题高亮显示
if (i === 0 && top < scrollTopList[i]) self.activeNo = i + 1;
else {
if (top >= scrollTopList[i - 1] && top < scrollTopList[i]) self.activeNo = i + 1;
}
}
}, 100);
//页面容器
const container = self.$refs.container;
//监听滚动事件
container.addEventListener('scroll', scrollCallback);
//页面销毁时,移除滚动事件监听,提高性能
this.$on("hook:beforeDestroy", () => {
container.removeEventListener('scroll', scrollCallback);
})
}
css
* {
padding: 0;
}
li {
list-style-type: none
}
.container {
position: absolute;
top: 0;
right: 0;
left: 0;
display: flex;
overflow: auto;
width: 100%;
height: 500px;
margin: auto
}
.nav {
position: fixed;
z-index: 5;
display: inline-flex;
flex-wrap: wrap;
width: 100px;
height: inherit;
background: #fff
}
.nav li {
display: flex;
align-items: center;
justify-content: center;
width: 100%
}
article {
display: inline-block;
width: 80%;
margin-left: 100px
}
article > div {
width: 100%;
display: flex;
font-size: 25px;
padding-left: 100px;
align-items: center;
}
.active-font {
border-bottom: 2px solid dodgerblue;
font-weight: bold
}
页面效果
本文参考与文章:https://www.jianshu.com/p/9a7ed6ff2928