大家好,我是南宮,今天寫一篇博客來整理下最近剛解決的一個問題,那就是導航欄跟內容聯動的問題,
簡單說一下我想要的效果:寫了一個寬度為螢屏100%的div,居中的部分是一個導航欄,水平排列,默認位于banner下,如果頁面滾到了banner下面,要讓導航欄固定頂部,如果頁面滾到了下方對應的內容,那就高亮對應的tab標記,如果點擊了tab,那就要讓頁面滾到對應的內容,并且讓該tab高亮,
(效果是動態的,我隨便截取一個場景來展示吧,比如我點擊“應用場景”的時候,頁面滾動到了對應的內容區域,并且對應的tab高亮了,也能看到導航欄固定頂部)

拆解一下,可以分為這么幾個部分:①吸頂、②選擇tab,可以讓頁面滾動到對應內容的位置、③頁面滾動到了對應的內容的位置,可以設定tab的選擇,
吸頂的實作:
這個是最容易的,我先簡單說一下原理:默認導航欄是位于banner下方的,也就是普通的標準流元素,沒有浮動沒有定位,是正常占位的;而固定頂部的狀態下,導航欄被固定定位(position: fixed)到了頂部,這個時候可以設定一下top,我們可以判斷當前滾動的位置是否需要固定定位,如果是,那就做一下樣式的切換,
顯然導航欄有兩種狀態,所以我們可以寫兩個class來分別控制,(我這里使用SCSS,可以參考一下我的代碼,nav-bar是默認狀態,再加上fixed的是固定頂部的狀態)
// 導航條
.nav-bar {
height: 61px;
background: url("/img/download/rect_bg.png") repeat-x;
background-size: 5px 61px;
&.fixed {
position: fixed;
top: 56px;
left: 0;
right: 0;
z-index: 10;
}
.nav-bar-item {
position: relative;
margin: 0 47px;
line-height: 55px;
cursor: pointer;
&:hover::after,
&.active::after {
content: "";
position: absolute;
bottom: 0;
left: 0;
right: 0;
height: 4px;
background: $dark-blue;
}
.nav-text {
font-size: 16px;
color: #333;
}
}
}
那么怎么控制導航欄是否固定頂部呢?可以在data里放一個變數(如isFixed)來控制,默認為false,監聽一下頁面的滾動,如果滾動超過了某個值,就讓isFixed改為true,否則改為false,然后在導航欄這邊,動態系結class,“fixed”在isFixed為true才設定上去,
data() {
return {
// 是否固定
isFixed: false,
//...其他data
}
}
// 處理滾動
handleScroll() {
var scrollTop =
window.pageYOffset ||
document.documentElement.scrollTop ||
document.body.scrollTop
// console.log(scrollTop) // 滑動的長度
var offsetTop = document.querySelector('.banner').offsetHeight
// 吸頂效果
if (scrollTop > offsetTop) {
this.isFixed = true
} else {
this.isFixed = false
}
},
<div class="nav-bar" :class="{ fixed: isFixed }">...</div>
Vue專案中如何監聽頁面的滾動呢?把剛才的handleScroll定義到組件的methods里面,在mounted的時候系結window的滾動時間,注意是在mounted的時候才可以使用DOM,退出頁面的時候,也要記得銷毀監聽,
mounted() {
window.addEventListener('scroll', this.handleScroll) // 監聽滑動事件
},
destroyed() {
window.removeEventListener('scroll', this.handleScroll) // 銷毀監聽滑動事件
}
點擊選擇tab,讓頁面滾動到對應內容的實作:
首先,tab會有普通狀態和高亮狀態,所以我們也需要用一個變數來控制當前選中的、處于高亮狀態的tab是第幾個,默認為0,讓第一個tab高亮,
data() {
return {
// ...其他data
// 當前選擇的tab
currentNav: 0,
}
}
<!-- 這是導航條里的每一個tab,給它動態系結active類,高亮才顯示,并且系結了點擊事件 -->
<div
v-for="(item, index) in obj.contentList"
:key="index"
class="nav-bar-item"
:class="{ active: currentNav == index }"
@click="toBox(index)"
>
<span class="nav-text">{{ item }}</span>
</div>
然后,頁面上有好幾塊內容,每一塊對應一個tab,我們給這幾塊內容的最外層加上一個class,作為標記,便于選擇這些錨點元素,比如我給它們加上“j-content”這個類名,這樣就實作了導航欄和每塊內容的系結,
<!-- 這是其中的一塊內容 -->
<div class="scene j-content">
<h3 class="production-title">應用場景</h3>
<!-- 其他內容 -->
</div>
點擊tab的時候,我們可以獲取到當前點擊的tab的索引(比如第1個的索引是0),在帶有j-content的div里,我們找到下標相同的div(如索引為0的時候找到第一個這樣的div),獲取它頂部的坐標,讓頁面滾動到這里,
(看下面的代碼,從所有的帶有j-content的class里找到對應當前索引的div,獲取它的offsetTop,然后用window.scrollTo平滑滾動到指定地方,這里用behavior設定了平滑滾動)
// 滾動到哪一塊
toBox(index) {
this.currentNav = index
const DOM = document.querySelectorAll('.j-content')[index]
const offsetTop = DOM.offsetTop - 25
// console.log('滾動到哪里', DOM)
window.scrollTo({
top: offsetTop,
behavior: 'smooth'
})
},
頁面滾動到對應內容,設定tab高亮的實作:
首頁,要判斷頁面滾到到了哪一塊內容,就得監聽頁面的滾動事件,所以判斷的代碼需要寫到handleScroll方法里,
然后滾動的時候,滾動的位置跟哪些值比較呢?這就需要我們記錄每一塊內容的offsetTop的位置,根據之前定好的class,找到每一塊內容的div,獲取一下它們各自的offsetTop,并保存,
// 獲取所有錨點元素
const divs = [...document.querySelectorAll('.j-content')]
// 將所有錨點元素offsetTop push到陣列內
divs.forEach((item, index) => {
this.contentTopList[index] = item.offsetTop - 25
})
接著,在滾動的程序中,怎么確定當前要高亮的tab是哪個呢?我們可以把當前的scrollTop值與每一個div的offsetTop比較,找到“小于但又最接近”的那個值,把這個下標作為要高亮的tab的下標,
(我補充了一個判斷,假如剛開始滾,還沒有滾到第一個內容區域的話,navIndex會算出來undefined,為了讓這個時候也有tab被高亮,我認為當前高亮的是0.)
let navIndex
// 滾動定位tab高亮的狀態
for (let i = 0; i < this.contentTopList.length; i++) {
// 如果當前滾動的top坐標大于第i個的top坐標,就記錄下i,
// 記錄到最后,i就會是最后一個滿足條件的i,也就是剛剛好的那個值
if (scrollTop >= this.contentTopList[i]) {
navIndex = i
}
}
// 把下標賦值給 vue 的 data
this.currentNav = navIndex
if (typeof navIndex !== 'number') {
this.currentNav = 0
}
到這一步,就已經做到了滾動吸頂、點擊tab滾動到相應的錨點、滾動高亮對應的tab,但是在除錯的時候我又發現了一個問題——點擊tab,頁面滾動的程序中,經過其他區域的時候,也會點亮對應的tab,這就顯得效果有些拖泥帶水,不像是被直接定位過去的,
于是我覺得,需要區分這兩種情況:“因為點擊而直接滾動到這里” 和 “頁面自己滾動的時候路過這里”,我在data里加了一個新的變數,叫isClick,默認為false,表示不是點擊定位的,在點擊tab后,瞬間把isClick賦值為true,然后在“根據滾動位置,判斷高亮tab”之前,先判斷isClick,確定不是點擊定位的才判斷,在點擊動作完成、滾動完畢后,過一小段時間,把isClick還原成true,以便恢復后續的滾動高亮效果,
完整的handleScroll和toBox代碼在這里:
// 處理滾動
handleScroll() {
var scrollTop =
window.pageYOffset ||
document.documentElement.scrollTop ||
document.body.scrollTop
// console.log(scrollTop) // 滑動的長度
var offsetTop = document.querySelector('.banner').offsetHeight
// 判斷是否已經記錄了每個內容的top,如果不是,就記錄一下,如果是,就直接使用
// 獲取所有錨點元素
const divs = [...document.querySelectorAll('.j-content')]
// 將所有錨點元素offsetTop push到陣列內
divs.forEach((item, index) => {
this.contentTopList[index] = item.offsetTop - 25
})
// if (this.contentTopList.length === 0) {
// }
// 判斷當前是否是點擊定位的,如果不是,才有滾動定位的效果
if (!this.isClick) {
let navIndex
// 滾動定位tab高亮的狀態
for (let i = 0; i < this.contentTopList.length; i++) {
// 如果當前滾動的top坐標大于第i個的top坐標,就記錄下i,記錄到最后,i就會是最后一個滿足條件的i,也就是剛剛好的那個值
if (scrollTop >= this.contentTopList[i]) {
navIndex = i
}
}
// 把下標賦值給 vue 的 data
this.currentNav = navIndex
if (typeof navIndex !== 'number') {
this.currentNav = 0
}
}
// 吸頂效果
if (scrollTop > offsetTop) {
this.isFixed = true
} else {
this.isFixed = false
}
},
// 滾動到哪一塊
toBox(index) {
// 點擊滾動到指定的位置,要去掉滾動的程序中因為位置變化帶來的效果
this.isClick = true
this.currentNav = index
const DOM = document.querySelectorAll('.j-content')[index]
const offsetTop = DOM.offsetTop - 25
// console.log('滾動到哪里', DOM)
window.scrollTo({
top: offsetTop,
behavior: 'smooth'
})
// 過一段時間,把isClick還原
setTimeout(() => {
this.isClick = false
}, 800)
},
還有20分鐘就是新年了,我總算趕在2021年寫完了這一篇博客,歡迎有類似需要的小伙伴來探討哦,謝謝大家!
轉載請註明出處,本文鏈接:https://www.uj5u.com/qianduan/400581.html
標籤:其他
上一篇:2022跨年代碼(有煙花)
