JS實現(xiàn)頁面導(dǎo)航與內(nèi)容相互錨定實例詳解
引文
在日常的學(xué)習(xí)和工作中,經(jīng)常會瀏覽的這樣一種網(wǎng)頁,它的結(jié)構(gòu)為左側(cè)是側(cè)邊欄,右側(cè)是內(nèi)容區(qū)域,當(dāng)點擊左側(cè)的側(cè)邊欄上的目錄時,右側(cè)的內(nèi)容區(qū)域會自動滾動到該目錄所對應(yīng)的內(nèi)容區(qū)域;當(dāng)滾動內(nèi)容區(qū)域時,側(cè)邊欄上對應(yīng)的目錄也會高亮。
恰巧最近需要寫個類似的小玩意,簡單的做下筆記,為了避免有人只熟悉Vue或React框架中的一個框架,還是使用原生JS來進(jìn)行實現(xiàn)。
思路
點擊側(cè)邊欄上的目錄時,通過獲取點擊的目錄的類名、或id、或index,用這些信息作為標(biāo)記,然后在內(nèi)容區(qū)域查找對應(yīng)的內(nèi)容。
滾動內(nèi)容區(qū)域時,根據(jù)內(nèi)容區(qū)域的內(nèi)容的dom節(jié)點獲取標(biāo)記,根據(jù)標(biāo)記來查找目錄。
實現(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是否還在變化,如果沒有變化了,那么就認(rèn)為滾動過程已經(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)容相互錨定的詳細(xì)內(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-05js學(xué)習(xí)總結(jié)_選項卡封裝(實例講解)
下面小編就為大家?guī)硪黄猨s學(xué)習(xí)總結(jié)_選項卡封裝(實例講解)。小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2017-07-07JS+JSP通過img標(biāo)簽調(diào)用實現(xiàn)靜態(tài)頁面訪問次數(shù)統(tǒng)計的方法
這篇文章主要介紹了JS+JSP通過img標(biāo)簽調(diào)用實現(xiàn)靜態(tài)頁面訪問次數(shù)統(tǒng)計的方法,基于JavaScript動態(tài)調(diào)用jsp頁面通過對TXT文本文件的讀寫實現(xiàn)統(tǒng)計訪問次數(shù)的功能,需要的朋友可以參考下2015-12-12Javascript中獲取瀏覽器類型和操作系統(tǒng)版本等客戶端信息常用代碼
跟蹤一些最基本的客戶端訪問信息,這里將一些公用的代碼總結(jié)下來,需要的朋友可以參考下2016-06-06