JS實現(xiàn)頁面導航與內容相互錨定實例詳解
引文
在日常的學習和工作中,經(jīng)常會瀏覽的這樣一種網(wǎng)頁,它的結構為左側是側邊欄,右側是內容區(qū)域,當點擊左側的側邊欄上的目錄時,右側的內容區(qū)域會自動滾動到該目錄所對應的內容區(qū)域;當滾動內容區(qū)域時,側邊欄上對應的目錄也會高亮。
恰巧最近需要寫個類似的小玩意,簡單的做下筆記,為了避免有人只熟悉Vue或React框架中的一個框架,還是使用原生JS來進行實現(xiàn)。
思路
點擊側邊欄上的目錄時,通過獲取點擊的目錄的類名、或id、或index,用這些信息作為標記,然后在內容區(qū)域查找對應的內容。
滾動內容區(qū)域時,根據(jù)內容區(qū)域的內容的dom節(jié)點獲取標記,根據(jù)標記來查找目錄。
實現(xiàn)
頁面初始化
首先把html和css寫成左邊為目錄,右邊為內容的頁面結構,為測試提供ui界面。
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <title>目錄與內容相互錨定</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">內容 1</div> <div class="content-block">內容 2</div> <div class="content-block">內容 3</div> <div class="content-block">內容 4</div> <div class="content-block">內容 5</div> <div class="content-block">內容 6</div> </div> </div> </body> </html>
通過點擊實現(xiàn)內容的滾動
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, }); } }); })
通過滾動內容實現(xiàn)導航的高亮
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)點擊左側的導航來使右側內容滾動到指定區(qū)域,這樣完全可行,但是如果需要平滑滾動的話,該怎么來實現(xiàn)?
scrollTo
這個函數(shù)提供了滾動方式的選項設置,指定滾動方式為平滑滾動方式,就可以實現(xiàn)。
content.scrollTo({ top: contentBlocks[index].offsetTop, behavior: 'smooth });
來看下效果
發(fā)現(xiàn)頁面的滾動確實變得平滑了,但是在點擊左側的目錄后會發(fā)生抖動的情況,那么為什么會發(fā)生這樣的情況?
首先觀察下現(xiàn)象,在點擊目錄5后,目錄5會在短暫高亮后,然后目錄1開始高亮直到目錄5。能夠改變高亮目錄的出了我們點擊的時候會讓目錄高亮,另外一個會使目錄高亮的地方就是在滾動事件函數(shù)里會根據(jù)內容所在位置來讓目錄高亮。
// content.addEventListener("scroll", handleScroll);
那么我們把對滾動事件的監(jiān)聽給去掉后,我們可以看看測試結果。
那么現(xiàn)在問題確定了,就是在滾動過程中會影響目錄導航的高亮,所以在剛開始滾動的時候會首先高亮目錄1,那么怎么解決?
比較直接的想法就是我在點擊目錄后,內容區(qū)域在滾動到對應內容區(qū)域時這段時間不觸發(fā)滾動事件,自然也不會反過來錨定目錄了,但是scrollTo
引起內容區(qū)域的滾動是平滑滾動,需要一段時間滾動才能結束,但怎么判斷滾動已經(jīng)結束了呢?
這里我給出自己的思路,就是判斷內容區(qū)域的scrollTop是否還在變化,如果沒有變化了,那么就認為滾動過程已經(jīng)結束了。
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); // 滾動結束后,記得把滾動事件函數(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", }); } }); });
看看效果
總結
目前功能已經(jīng)實現(xiàn),下面把完整的代碼貼出來
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <title>目錄與內容相互錨定</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">內容 1</div> <div class="content-block">內容 2</div> <div class="content-block">內容 3</div> <div class="content-block">內容 4</div> <div class="content-block">內容 5</div> <div class="content-block">內容 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)頁面導航與內容相互錨定的詳細內容,更多關于JS實現(xiàn)頁面導航與內容相互錨定的資料請關注腳本之家其它相關文章!
相關文章
JavaScript開發(fā)Chrome瀏覽器擴展程序UI的教程
Chrome擴展開發(fā)API中提供了一些關于UI外觀的操作,如果是剛剛上手的話首先需要了解Browser Actions、Omnibox、選項頁等,在這篇JavaScript開發(fā)Chrome瀏覽器擴展程序UI的教程中,我們先來回顧一下基本知識:2016-05-05JS+JSP通過img標簽調用實現(xiàn)靜態(tài)頁面訪問次數(shù)統(tǒng)計的方法
這篇文章主要介紹了JS+JSP通過img標簽調用實現(xiàn)靜態(tài)頁面訪問次數(shù)統(tǒng)計的方法,基于JavaScript動態(tài)調用jsp頁面通過對TXT文本文件的讀寫實現(xiàn)統(tǒng)計訪問次數(shù)的功能,需要的朋友可以參考下2015-12-12Javascript中獲取瀏覽器類型和操作系統(tǒng)版本等客戶端信息常用代碼
跟蹤一些最基本的客戶端訪問信息,這里將一些公用的代碼總結下來,需要的朋友可以參考下2016-06-06