超詳細Vue實現導航欄綁定內容錨點+滾動動畫+vue-router(hash模式可用)
轉載自:https://www.jianshu.com/p/2ad8c8b5bf75
親測有效~
<template>
<div>
<!-- 內容區域 -->
<div class="content">
<div>
content-0
</div>
<div>
content-1
</div>
<div>
content-2
</div>
<div>
content-3
</div>
<div>
content-4
</div>
</div>
<!-- 導航區域 -->
<ul class="navs">
<li :class="{active: active===0}" @click="scrollTo(0)">
content-0
</li>
<li :class="{active: active===1}" @click="scrollTo(1)">
content-1
</li>
<li :class="{active: active===2}" @click="scrollTo(2)">
content-2
</li>
<li :class="{active: active===3}" @click="scrollTo(3)">
content-3
</li>
<li :class="{active: active===4}" @click="scrollTo(4)">
content-4
</li>
</ul>
</div>
</template>
<script>
export default {
props: {},
data() {
return {
active: 0 // 當前激活的導航索引
}
},
mounted() {
// 監聽滾動事件
window.addEventListener('scroll', this.onScroll, false)
},
destroy() {
// 必須移除監聽器,不然當該vue組件被銷毀了,監聽器還在就會出錯
window.removeEventListener('scroll', this.onScroll)
},
methods: {
// 滾動監聽器
onScroll() {
// 獲取所有錨點元素
const navContents = document.querySelectorAll('.content div')
// 所有錨點元素的 offsetTop
const offsetTopArr = []
navContents.forEach(item => {
offsetTopArr.push(item.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
}
}
this.active = navIndex
},
// 跳轉到指定索引的元素
scrollTo(index) {
// 獲取目標的 offsetTop
// css選擇器是從 1 開始計數,我們是從 0 開始,所以要 +1
const targetOffsetTop = document.querySelector(`.content div:nth-child(${index + 1})`).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)
}
}
}
}
}
</script>
<style scoped>
/* 內容區的樣式 */
.content {
background-color: white;
width: 500px;
}
.content div {
width: 100%;
height: 600px;
font-size: 36px;
padding: 20px;
background-color: #7ec384;
}
.content div:nth-child(2n) {
background-color: #847ec3;
}
/* 導航欄的樣式 */
.navs {
position: fixed;
top: 80px;
left: 700px;
background-color: #efefef;
}
.navs li {
padding: 0 20px;
line-height: 1.6;
font-size: 24px;
}
/* 當導航被點亮后改變顏色 */
.navs .active{
color: #847ec3;
background-color: #e2e2e2;
}
</style>