这是我参与更文挑战的第 12 天,活动详情查看: 更文挑战css
Lynne,一个能哭爱笑永远少女心的前端开发工程师。身处互联网浪潮之中,热爱生活与技术。前端
首先描述下问题,应用场景以下:web
随着滚动条的滚动,tab会对应进行切换,切换tab时,又会定位到tab对应内容的高度变化。数组
切换 tab 定位到对应内容这个能够用简单的锚点定位来实现,但若是须要平滑地进行内容滚动切换最好借助scrollIntoView 来实现,而要实现tab随滚动条的滚动进行切换则须要监听当前页面的滚动。markdown
接下来以一段代码为例,分析实现思路。async
<template>
<div class="box">
<div class="tab" ref="tab">
<div v-for="(item, index) in tabs" :key="index">
<div :class="{ active: active === index }" @click="switchTab(index)">
{{ item }}
</div>
</div>
</div>
<div class="cont" ref="cont">
<div class="cont_1" ref="cont_1">内容一</div>
<div class="cont_2" ref="cont_2">内容二</div>
<div class="cont_3" ref="cont_3">内容三</div>
</div>
<div class="back-top" @click="backTop"></div>
</div>
</template>
<script>
export default {
data() {
return {
tabs: ["tab1", "tab2", "tab3"],
active: 0,
cont1: null,
cont2: null,
cont3: null,
isClickTab: false
};
},
methods: {
backTop() { // 回到顶部
this.cont1.scrollIntoView({
block: "start",
behavior: "smooth"
});
},
switchTab(index) { // 根据当前index切换到对应的页面内容
if (index === 0) {
this.cont1.scrollIntoView({
block: "start",
behavior: "smooth"
});
} else if (index === 1) {
this.cont2.scrollIntoView({
block: "start",
behavior: "smooth"
});
} else {
this.cont3.scrollIntoView({
block: "start",
behavior: "smooth"
});
}
}
},
mounted() {
this.cont1 = this.$refs["cont_1"];
this.cont2 = this.$refs["cont_2"];
this.cont3 = this.$refs["cont_3"];
const tabH = this.$refs["tab"].offsetHeight;
// 添加scroll事件监听
this.$refs["cont"].addEventListener("scroll", () => {
if (this.cont3.getBoundingClientRect().top <= tabH) {
this.active = 2;
return false;
}
if (this.cont2.getBoundingClientRect().top <= tabH) {
this.active = 1;
return false;
}
if (this.cont1.getBoundingClientRect().top <= tabH) {
this.active = 0;
}
});
}
};
</script>
<style lang="scss" scoped>
.box {
font-size: 28px;
overflow-x: auto;
height: 100vh;
display: -webkit-flex;
display: flex;
flex-direction: column;
overflow-y: hidden;
.tab {
height: 88px;
background: #fff;
line-height: 88px;
color: #666;
display: -webkit-flex;
display: flex;
justify-content: space-around;
.active {
font-size: 32px;
color: #333;
&::after {
display: block;
content: "";
width: 36px;
height: 6px;
margin: auto;
margin-top: -10px;
background: rgba(255, 51, 0, 1);
border-radius: 3px;
}
}
}
.cont {
height: 300px;
flex-grow: 1;
overflow: auto;
.cont_1 {
height: 400px;
background: pink;
}
.cont_2 {
height: 800px;
background: yellow;
}
.cont_3 {
height: 100%;
background: lightgreen;
}
}
.back-top {
width: 80px;
height: 80px;
background: url(../../assets/back-top.png) center / 100%
100% no-repeat;
border-radius: 50%;
position: fixed;
bottom: 120px;
right: 32px;
}
}
</style>
复制代码
这段代码是参考网上的,相对来讲简单一些,由于tab项的数量固定,咱们只要根据tab点击时index切换和监听高度切换至对应index的tab便可。ide
但若是数组 tab 的元素数量未知,咱们应该如何处理?这种数量不肯定的场景更为通用,接下来抽象出通用代码实现流程。post
举个栗子:flex
<div class="box">
<div class="tab" ref="tab">
<div v-for="(item, index) in tabs" :key="index">
<div :class="{ active: active === index }" @click="switchTab(index)">
{{ item }}
</div>
</div>
</div>
<div class="tab-content-list">
<tab-content :id="`to-${index}`" v-for="(data, index) in dataList" :key="index" :resData="data" ref="content" />
</div>
</div>
复制代码
tab-content 中接收父组件内容进行展现:this
props: {
resData: Object
}
复制代码
使用scrollIntoView方法实现平滑滚动:
methods: {
switchTab(index) {
this.clickedIndex = index
// 定位到当前tab对应的内容
document.getElementById(`to-${clickedIndex}`).scrollIntoView({
block: 'start',
behavior: 'smooth'
})
}
}
复制代码
在mounted中收集滚动元素的高度并初始化挂载滚动监听:
mounted() {
// 初始化滚动监听
this.scrollInit()
this.isSlide = false // 平滑滚动前
// 收集收集滚动元素的高度
this.slotsTopList = this.$refs.content.map((item, index) => {
return item.offsetTop
})
setTimeout(() => { // 点击时平滑滚动完成前不监听滚动
this.isSlide = false
}, 600)
}
复制代码
具体方法实现:
methods: {
// 监听滚动高度
scrollInit () {
this.throttleGetScrollTop = _.throttle(this.getScrollTop, 100)
this.throttleGetScrollTop()
window.addEventListener('scroll', this.throttleGetScrollTop, true)
},
// 监听滚动距离
async getScrollTop () {
const scrollTop = document.getElementById('privilage-list').scrollTop
// 获取当前滚动内容对应的tabIndex
const currentIndex = await this.slotsTopList.findIndex((top, index) => {
return scrollTop >= this.slotsTopList[index + 1] && scrollTop < this.slotsTopList[index + 2]
})
if (this.isSlide) return
this.clickedIndex = currentIndex + 1
}
}
复制代码
有一点小问题是,在点击时的滚动中也会发生滚动监听致使tab有一次来回切换,但目前又没办法确切知道点击平滑滚动完成的事件,所以我在点击切换tab时间中加了一个标记和延迟,以免重复监听:
this.isSlide = false // 平滑滚动前
setTimeout(() => { // 点击时平滑滚动完成前不监听滚动
this.isSlide = false
}, 600)
复制代码
而后在滚动监听高度时间中加入if (this.isSlide) return
。
以上是页面滚动锚点定位tab的实现原理和实现流程,最主要的是经过监听 window 的滚动事件,经过滚动高度来判断那个内容区在当前视口, 从而操做对应的导航菜单里的状态的转换。 点击导航菜单触发滚动, 与此相对应。
固然我解决平滑滚动过程当中重复监听的办法其实并不完美,若有更好的思路和建议,欢迎留言~~~
感激~~~