JS實現(xiàn)頁面導(dǎo)航與內(nèi)容相互錨定實例詳解
引文
在日常的學(xué)習(xí)和工作中,經(jīng)常會瀏覽的這樣一種網(wǎng)頁,它的結(jié)構(gòu)為左側(cè)是側(cè)邊欄,右側(cè)是內(nèi)容區(qū)域,當點擊左側(cè)的側(cè)邊欄上的目錄時,右側(cè)的內(nèi)容區(qū)域會自動滾動到該目錄所對應(yīng)的內(nèi)容區(qū)域;當滾動內(nèi)容區(qū)域時,側(cè)邊欄上對應(yīng)的目錄也會高亮。
恰巧最近需要寫個類似的小玩意,簡單的做下筆記,為了避免有人只熟悉Vue或React框架中的一個框架,還是使用原生JS來進行實現(xiàn)。
思路
點擊側(cè)邊欄上的目錄時,通過獲取點擊的目錄的類名、或id、或index,用這些信息作為標記,然后在內(nèi)容區(qū)域查找對應(yīng)的內(nèi)容。
滾動內(nèi)容區(qū)域時,根據(jù)內(nèi)容區(qū)域的內(nèi)容的dom節(jié)點獲取標記,根據(jù)標記來查找目錄。
實現(xiàn)
頁面初始化
首先把html和css寫成左邊為目錄,右邊為內(nèi)容的頁面結(jié)構(gòu),為測試提供ui界面。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>目錄與內(nèi)容相互錨定</title>
<style>
.container {
display: flex;
flex-direction: row;
}
#nav {
width: 150px;
height: 400px;
background-color: #eee;
}
#nav .nav-item {
cursor: pointer;
}
#nav .nav-item.active {
font-weight: bold;
background-color: #f60;
}
#content {
flex: 1;
margin-left: 10px;
position: relative;
width: 300px;
height: 400px;
overflow-y: scroll;
}
.content-block {
margin-top: 25px;
height: 200px;
background-color: #eee;
}
.content-block:first-child {
margin-top: 0;
}
</style>
</head>
<body>
<div class="container">
<div id="nav">
<div class="nav-item">目錄 1</div>
<div class="nav-item">目錄 2</div>
<div class="nav-item">目錄 3</div>
<div class="nav-item">目錄 4</div>
<div class="nav-item">目錄 5</div>
<div class="nav-item">目錄 6</div>
</div>
<div id="content">
<div class="content-block">內(nèi)容 1</div>
<div class="content-block">內(nèi)容 2</div>
<div class="content-block">內(nèi)容 3</div>
<div class="content-block">內(nèi)容 4</div>
<div class="content-block">內(nèi)容 5</div>
<div class="content-block">內(nèi)容 6</div>
</div>
</div>
</body>
</html>通過點擊實現(xiàn)內(nèi)容的滾動
const nav = document.querySelector("#nav");
const navItems = document.querySelectorAll(".nav-item");
navItems[0].classList.add("active");
nav.addEventListener('click', e => {
navItems.forEach((item, index) => {
navItems[index].classList.remove("active");
if (e.target === item) {
navItems[index].classList.add("active");
content.scrollTo({
top: contentBlocks[index].offsetTop,
});
}
});
})通過滾動內(nèi)容實現(xiàn)導(dǎo)航的高亮
const content = document.querySelector("#content");
const contentBlocks = document.querySelectorAll(".content-block");
let currentBlockIndex = 0;
const handleScroll = function () {
for (let i = 0; i < contentBlocks.length; i++) {
const block = contentBlocks[i];
if (
block.offsetTop <= content.scrollTop &&
block.offsetTop + block.offsetHeight > content.scrollTop
) {
currentBlockIndex = i;
break;
}
}
for (let i = 0; i < navItems.length; i++) {
const item = navItems[i];
item.classList.remove("active");
}
navItems[currentBlockIndex].classList.add("active");
};
content.addEventListener("scroll", handleScroll);最后實際效果如下

現(xiàn)在能基本實現(xiàn)點擊左側(cè)的導(dǎo)航來使右側(cè)內(nèi)容滾動到指定區(qū)域,這樣完全可行,但是如果需要平滑滾動的話,該怎么來實現(xiàn)?
scrollTo這個函數(shù)提供了滾動方式的選項設(shè)置,指定滾動方式為平滑滾動方式,就可以實現(xiàn)。
content.scrollTo({
top: contentBlocks[index].offsetTop,
behavior: 'smooth
});來看下效果

發(fā)現(xiàn)頁面的滾動確實變得平滑了,但是在點擊左側(cè)的目錄后會發(fā)生抖動的情況,那么為什么會發(fā)生這樣的情況?
首先觀察下現(xiàn)象,在點擊目錄5后,目錄5會在短暫高亮后,然后目錄1開始高亮直到目錄5。能夠改變高亮目錄的出了我們點擊的時候會讓目錄高亮,另外一個會使目錄高亮的地方就是在滾動事件函數(shù)里會根據(jù)內(nèi)容所在位置來讓目錄高亮。
// content.addEventListener("scroll", handleScroll);那么我們把對滾動事件的監(jiān)聽給去掉后,我們可以看看測試結(jié)果。

那么現(xiàn)在問題確定了,就是在滾動過程中會影響目錄導(dǎo)航的高亮,所以在剛開始滾動的時候會首先高亮目錄1,那么怎么解決?
比較直接的想法就是我在點擊目錄后,內(nèi)容區(qū)域在滾動到對應(yīng)內(nèi)容區(qū)域時這段時間不觸發(fā)滾動事件,自然也不會反過來錨定目錄了,但是scrollTo引起內(nèi)容區(qū)域的滾動是平滑滾動,需要一段時間滾動才能結(jié)束,但怎么判斷滾動已經(jīng)結(jié)束了呢?
這里我給出自己的思路,就是判斷內(nèi)容區(qū)域的scrollTop是否還在變化,如果沒有變化了,那么就認為滾動過程已經(jīng)結(jié)束了。
let timerId = null;
nav.addEventListener("click", (e) => {
if (timerId) {
window.clearInterval(timerId);
}
content.removeEventListener("scroll", handleScroll);
let lastScrollPosition = content.scrollTop;
timerId = window.setInterval(() => {
const currentScrollPosition = content.scrollTop;
console.log(currentScrollPosition, lastScrollPosition);
if (lastScrollPosition === currentScrollPosition) {
content.addEventListener("scroll", handleScroll); // 滾動結(jié)束后,記得把滾動事件函數(shù)重新綁定到scroll事件上去
window.clearInterval(timerId);
}
lastScrollPosition = currentScrollPosition;
}, 150);
navItems.forEach((item, index) => {
navItems[index].classList.remove("active");
if (e.target === item) {
navItems[index].classList.add("active");
content.scrollTo({
top: contentBlocks[index].offsetTop,
behavior: "smooth",
});
}
});
});看看效果

總結(jié)
目前功能已經(jīng)實現(xiàn),下面把完整的代碼貼出來
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>目錄與內(nèi)容相互錨定</title>
<style>
.container {
display: flex;
flex-direction: row;
}
#nav {
width: 150px;
height: 400px;
background-color: #eee;
}
#nav .nav-item {
cursor: pointer;
}
#nav .nav-item.active {
font-weight: bold;
background-color: #f60;
}
#content {
flex: 1;
margin-left: 10px;
position: relative;
width: 300px;
height: 400px;
overflow-y: scroll;
}
.content-block {
margin-top: 25px;
height: 200px;
background-color: #eee;
}
.content-block:first-child {
margin-top: 0;
}
</style>
</head>
<body>
<div class="container">
<div id="nav">
<div class="nav-item">目錄 1</div>
<div class="nav-item">目錄 2</div>
<div class="nav-item">目錄 3</div>
<div class="nav-item">目錄 4</div>
<div class="nav-item">目錄 5</div>
<div class="nav-item">目錄 6</div>
</div>
<div id="content">
<div class="content-block">內(nèi)容 1</div>
<div class="content-block">內(nèi)容 2</div>
<div class="content-block">內(nèi)容 3</div>
<div class="content-block">內(nèi)容 4</div>
<div class="content-block">內(nèi)容 5</div>
<div class="content-block">內(nèi)容 6</div>
</div>
</div>
<script>
const content = document.querySelector("#content");
const contentBlocks = document.querySelectorAll(".content-block");
const navItems = document.querySelectorAll(".nav-item");
const nav = document.querySelector("#nav");
let timerId = null;
let currentBlockIndex = 0;
navItems[currentBlockIndex].classList.add("active");
const handleScroll = function () {
for (let i = 0; i < contentBlocks.length; i++) {
const block = contentBlocks[i];
if (
block.offsetTop <= content.scrollTop &&
block.offsetTop + block.offsetHeight > content.scrollTop
) {
currentBlockIndex = i;
break;
}
}
for (let i = 0; i < navItems.length; i++) {
const item = navItems[i];
item.classList.remove("active");
}
navItems[currentBlockIndex].classList.add("active");
};
nav.addEventListener("click", (e) => {
if (timerId) {
window.clearInterval(timerId);
}
content.removeEventListener("scroll", handleScroll);
let lastScrollPosition = content.scrollTop;
timerId = window.setInterval(() => {
const currentScrollPosition = content.scrollTop;
console.log(currentScrollPosition, lastScrollPosition);
if (lastScrollPosition === currentScrollPosition) {
content.addEventListener("scroll", handleScroll);
window.clearInterval(timerId);
}
lastScrollPosition = currentScrollPosition;
}, 150);
navItems.forEach((item, index) => {
navItems[index].classList.remove("active");
if (e.target === item) {
navItems[index].classList.add("active");
content.scrollTo({
top: contentBlocks[index].offsetTop,
behavior: "smooth",
});
}
});
});
content.addEventListener("scroll", handleScroll);
</script>
</body>
</html>以上就是JS實現(xiàn)頁面導(dǎo)航與內(nèi)容相互錨定的詳細內(nèi)容,更多關(guān)于JS實現(xiàn)頁面導(dǎo)航與內(nèi)容相互錨定的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
JavaScript開發(fā)Chrome瀏覽器擴展程序UI的教程
Chrome擴展開發(fā)API中提供了一些關(guān)于UI外觀的操作,如果是剛剛上手的話首先需要了解Browser Actions、Omnibox、選項頁等,在這篇JavaScript開發(fā)Chrome瀏覽器擴展程序UI的教程中,我們先來回顧一下基本知識:2016-05-05
js學(xué)習(xí)總結(jié)_選項卡封裝(實例講解)
下面小編就為大家?guī)硪黄猨s學(xué)習(xí)總結(jié)_選項卡封裝(實例講解)。小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2017-07-07
JS+JSP通過img標簽調(diào)用實現(xiàn)靜態(tài)頁面訪問次數(shù)統(tǒng)計的方法
這篇文章主要介紹了JS+JSP通過img標簽調(diào)用實現(xiàn)靜態(tài)頁面訪問次數(shù)統(tǒng)計的方法,基于JavaScript動態(tài)調(diào)用jsp頁面通過對TXT文本文件的讀寫實現(xiàn)統(tǒng)計訪問次數(shù)的功能,需要的朋友可以參考下2015-12-12
Javascript中獲取瀏覽器類型和操作系統(tǒng)版本等客戶端信息常用代碼
跟蹤一些最基本的客戶端訪問信息,這里將一些公用的代碼總結(jié)下來,需要的朋友可以參考下2016-06-06

