這是我參與更文挑戰的第 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 的滾動事件,經過滾動高度來判斷那個內容區在當前視口, 從而操做對應的導航菜單裏的狀態的轉換。 點擊導航菜單觸發滾動, 與此相對應。
固然我解決平滑滾動過程當中重複監聽的辦法其實並不完美,若有更好的思路和建議,歡迎留言~~~
感激~~~