
背景與概念說明
相信不少前端開發者都有過這樣的經驗:點擊頁面內的錨點連結(anchor link)後,頁面瞬間跳轉到目標區塊,但發現該區塊的標題被頂部的固定導覽列遮住了!這種情況令使用者無法看到段落開頭的內容,體驗很不友善。為什麼會這樣呢?主要原因在於固定導覽列(fixed navigation bar)在頁面頂部佔據空間,而瀏覽器預設在跳轉錨點時,會把目標元素頂到視窗最上方,結果就被導覽列蓋住了。這就是所謂的「錨點偏移」問題:錨點定位的內容相對於視窗的位置產生了偏差。
在現代網頁中,固定導覽列相當常見,因為它可以讓使用者在捲動頁面時始終看到導航選單,快速跳轉到各部分內容。然而,搭配錨點連結時,就必須解決Anchor Offset問題,確保錨點目標不會被導覽列遮蓋。此外,我們也常希望增加一些互動效果,例如平滑捲動(smooth scrolling)讓畫面過渡更順暢,以及捲動偵測高亮(scroll spy)在使用者瀏覽長頁面時自動高亮目前所在章節對應的導覽項目,提升使用者定位當前內容的體驗。
實作步驟
步驟 1:建立基本的 HTML 架構
首先,我們建立一個簡單的 HTML 網頁結構,其中包含固定導覽列和多個內容區塊。導覽列會有若干連結(<a>),對應到頁面內部的錨點。每個錨點區塊這裡用<section>來表示,並給予唯一的 id 供錨點連結定位。
下面是範例結構:
<!DOCTYPE html>
<html lang="zh-TW">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>錨點與固定導覽列範例</title>
</head>
<body>
<!-- 固定導覽列 -->
<nav>
<a href="#section1">Section 1</a>
<a href="#section2">Section 2</a>
<a href="#section3">Section 3</a>
</nav>
<!-- 內容區塊們(錨點目標) -->
<section id="section1">
<h2>第一部分</h2>
<p>這是第一部分的內容...</p>
</section>
<section id="section2">
<h2>第二部分</h2>
<p>這是第二部分的內容...</p>
</section>
<section id="section3">
<h2>第三部分</h2>
<p>這是第三部分的內容...</p>
</section>
</body>
</html>
上述 HTML 中,我們建立了一個簡單的導航列 <nav>,其中包含三個錨點連結,它們的 href 對應於頁面中三個 <section> 區塊的 id(section1、section2、section3)。這樣一來,點擊導覽列的連結時,瀏覽器會自動跳轉到相應的 <section>。接下來,我們將為導覽列加入 CSS,讓它固定在頁面頂部,同時確保內容不被遮擋。
步驟 2:設定固定導覽列的基本樣式 (CSS)
現在,我們使用 CSS 將導覽列固定在頁面頂端,並設計其外觀。固定導覽列通常使用 position: fixed 或 position: sticky 實現。這裡我們選擇 position: fixed,使導覽列在頁面上始終停留在視窗頂部。為了讓後續內容不被導覽列壓住,我們也會對 <body> 或內容容器加入上方內間距(padding)。
以下是基本的 CSS 設定:
/* 讓整體盒模型計算更直觀 */
* {
box-sizing: border-box;
}
/* 基本頁面樣式調整 */
body {
margin: 0;
padding-top: 60px; /* 預留空間,避免內容被高度60px的導覽列遮住 */
font-family: sans-serif;
}
/* 導覽列樣式 */
nav {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 60px;
background-color: #333;
color: #fff;
display: flex;
align-items: center;
padding: 0 20px;
z-index: 1000; /* 確保導覽列蓋在其他內容上方 */
}
/* 導覽列中的連結樣式 */
nav a {
color: #fff;
text-decoration: none;
margin-right: 15px;
font-size: 1rem;
}
/* 可選:導覽列連結的hover效果 */
nav a:hover {
text-decoration: underline;
}
在這段 CSS 中,我們做了以下幾件事:
對 body 設置了 padding-top: 60px,這個數值等於導覽列的高度,確保頁面內容從導覽列下方開始,而不會被導覽列覆蓋。這對於頁面載入初始位置非常重要。若不這樣做,第一個 <section> 會被固定在頂部的導覽列擋住一部分。
使用 nav { position: fixed; top: 0; width: 100%; } 將導覽列固定定位在視窗頂部,並撐滿寬度。
設定了一些外觀樣式,例如背景色 #333(深灰)、文字顏色白色,以及 display: flex 將連結橫向排列置中對齊。
z-index: 1000 非常重要,讓導覽列的堆疊位在最前面,避免在頁面上出現其他內容或元件遮蓋導航列的情況。
到此,固定導覽列已經可以正常工作了。你可以試著打開網頁,滾動看看:導覽列會一直停留在頂部不動。然而,如果此時點擊導覽列的錨點連結,你可能會遇到我們開頭提到的問題:跳轉後的區塊標題依然被導覽列遮住。下一步我們就來解決錨點偏移的問題。
步驟 3:解決錨點被固定導覽列遮擋(錨點偏移修正)
一般來說,當使用 <a href="#section1"> 這種錨點連結時,瀏覽器會讓 id="section1" 的元素頂格出現在視窗頂部。由於我們的導覽列固定在頂部,目標元素就會剛好跑到導覽列背後,導致內容遮擋。為了解決這個錨點偏移問題,我們可以利用 CSS 提供的解決方案。
最新的 CSS 中有兩個屬性可以用來調整滾動定位的偏移:
scroll-padding-top: 針對滾動容器(例如 html 或特定容器),指定在滾動定位時容器頂部預留的內間距。
scroll-margin-top: 針對目標元素,指定在滾動對齊時元素頂部預留的外間距。
這兩種方法效果相似,選一種用即可。我們這裡採用 scroll-margin-top,直接在錨點目標元素上設定向下偏移的距離。由於我們錨點目標是每個 <section>,而導覽列高度為 60px,所以我們讓每個 section 在滾動進視窗時預留 60px 的空間:
/* 避免錨點跳轉內容被固定導覽列遮住 */
section {
scroll-margin-top: 60px;
}
將上述 CSS 新增到樣式表中。這麼做之後,當使用者點擊導覽列的錨點連結,瀏覽器在滾動目標 <section> 進入視窗時,會自動在其頂端留出60px的距離,相當於把目標內容往下推了60px,正好避開高度為60px的導覽列。換句話說,scroll-margin-top 幫我們自動預留空白,確保錨點元素不會被固定導覽列擋住。
小提示: 你也可以使用 html { scroll-padding-top: 60px; } 達成類似效果,作用是在整個頁面的滾動環境預留頂部空間。兩者的差別在於實作層面略有不同,但對最終用戶效果幾乎一樣。scroll-margin-top 是設在目標元素上,每個錨點元素都可各自設定;scroll-padding-top 則是設在滾動容器上(通常是 html, body),統一為所有錨點提供偏移量。依據你的需求選擇其一即可。
現在再試著點擊錨點連結,應該可以看到,被導覽列蓋住標題的問題消失了——每個標題上方都有適當的空間,看起來很舒服。這解決了導覽列遮擋錨點的煩人問題。
步驟 4:加入平滑捲動效果
目前我們的錨點跳轉是瞬間跳到目標區域。雖然功能上沒問題,但體驗上稍嫌突兀。接下來,我們要實現平滑捲動效果:當點擊錨點連結時,頁面會以動畫方式順暢地捲動到指定位置,而不是瞬間跳轉。這種視覺上的平滑過渡能讓使用者更容易跟蹤視線,不會突然迷失在新位置。
實作平滑捲動有兩種途徑:
CSS 方法:使用 scroll-behavior: smooth。
JS 方法:攔截錨點點擊事件,用 window.scrollTo 或 element.scrollIntoView 等帶平滑選項的 API 來執行滾動。
最簡單的莫過於 CSS 一行設定。在我們的範例中,只需對 HTML 設定全域的平滑滾動行為:
/* 全域啟用平滑捲動 */
html {
scroll-behavior: smooth;
}
把這行加到你的 CSS 裡。現在,所有透過錨點連結觸發的滾動(以及調用瀏覽器內建滾動 API 的行為)都會變成平滑動畫效果了。不需要任何額外的 JavaScript!此時再點擊一次導覽列中的「Section 2」或「Section 3」,你會看到頁面內容柔和地滑動到新位置,而非突然跳轉,體驗提升不少。
注意: scroll-behavior: smooth; 已被大多數現代瀏覽器支援,但如果你需要支援不支援此屬性的環境(例如較舊版的 Safari 或 IE 瀏覽器),可以考慮加上一段 JavaScript 作為後備方案。在本文中我們以 CSS 方法為主;若有需要,稍後的問與答部分會提及 JavaScript 平滑滾動的替代方案。
步驟 5:實作滾動時導覽項目高亮 (Scroll Spy)
最後一個功能,是在使用者滾動瀏覽頁面時,自動高亮導覽列中對應當前所在區塊的連結。這個效果常被稱作 Scroll Spy(捲動侦測)——很多前端框架(如 Bootstrap)的元件都有類似功能,但我們可以用原生 JavaScript 自己實現。
實作的思路如下:偵聽頁面的捲動事件,每當使用者捲動時,程式計算目前哪一個 <section> 正位於視窗中(或接近頂部的位置)。然後,根據該 section 的 id,找到導覽列中對應的 <a>,將其標示為「active」(例如加上一個特殊的 class)。同時,要移除其他連結的高亮狀態。如此即可做到隨捲動自動切換導覽高亮。
讓我們來實現它。首先,在 CSS 中定義一個樣式供高亮顯示使用,例如 .active 狀態下改變文字顏色或其他效果:
/* 高亮當前區塊對應的導覽項目 */
nav a.active {
font-weight: bold;
text-decoration: underline;
}
以上設定當連結具有 active 類別時,加粗字體並加底線,以醒目顯示。目前 .active 還不會自動套用,我們接下來用 JS 讓它動起來。
加入以下 JavaScript 腳本(可以放在頁面底部,緊貼 </body> 前,或是用 <script> 引入外部檔案):
<script>
// 取得所有的 section 和 nav 中的連結
const sections = document.querySelectorAll('section');
const navLinks = document.querySelectorAll('nav a');
window.addEventListener('scroll', () => {
let currentSectionId = "";
// 找出目前視窗所在的區塊(以頁面頂端位置判斷)
sections.forEach(section => {
const sectionTop = section.offsetTop - document.querySelector('nav').offsetHeight;
const sectionHeight = section.offsetHeight;
if (window.scrollY >= sectionTop && window.scrollY < sectionTop + sectionHeight) {
currentSectionId = section.getAttribute('id');
}
});
// 根據 currentSectionId,切換導覽列中對應連結的 active 狀態
navLinks.forEach(link => {
const targetId = link.getAttribute('href').substring(1); // 去掉開頭的 '#'
if (targetId === currentSectionId) {
link.classList.add('active');
} else {
link.classList.remove('active');
}
});
});
</script>
這段程式碼做了以下事情:
用 querySelectorAll 抓取頁面中的所有 <section> 以及所有 <nav> 裡的 <a> 連結,分別存入 sections 和 navLinks 變數。
監聽 window 的 scroll 事件,每當頁面發生捲動時就執行回呼函式。
在每次捲動時,迴圈遍歷所有區塊 sections,利用 section.offsetTop 和 section.offsetHeight 計算區塊的位置範圍。如果目前的 scrollY(頁面頂部離開文件頂端的距離)落在某個 section 的範圍內,表示使用者視窗此刻正看到該區段,我們將記錄下這個區段的 ID。
我們從每個 section 的 offsetTop 減去導覽列的高度來得到實際出現在視窗頂部時的臨界位置,這樣可以更準確地對齊可視範圍。(這裡用 document.querySelector('nav').offsetHeight 動態取得導航列高度。若之前有直接使用固定值60px,也可以在此寫死 -60。動態取得則比較萬一導覽列高度調整也能自適應。)
找出當前所在的 section 之後,再遍歷每一個導覽連結 navLinks。把所有連結的 .active 類先移除,然後比對每個連結的 href 是否對應我們偵測到的 currentSectionId。如果匹配,則加上 .active 類別。如此即可更新高亮狀態。
現在,大功告成!當你瀏覽頁面、向下滾動時,對應所在區塊的導航連結會自動加上粗體底線標示。比如當你滾動進入 Section 2 區域時,「Section 2」這個連結會高亮顯示,讓使用者清楚知道目前閱讀的位置。在長篇幅的一頁式網站或文件頁面中,這種 Scroll Spy 功能非常實用。
進階提示: 上述用 scroll 事件監聽的方法在絕大多數情況下都表現良好。但如果你的頁面內容非常多、區塊非常多,頻繁計算可能帶來性能負擔。此時可以考慮使用 Intersection Observer API 來更有效率地監測元素的可見性,取代手動計算 scrollY。另外,別忘了節流(throttle)或防抖(debounce)技巧也能避免滾動事件觸發過於頻繁導致卡頓。
經過以上五個步驟,我們已經實現了一個功能完整的頁面範例:具有固定頂端導航列、內部錨點連結正確對齊(不會被遮擋)、點擊連結平滑捲動過渡、以及滾動時自動更新導覽高亮。接下來,我們會整理一些常見的問題和解決方案,幫助你在實作類似功能時避免陷入那些小陷阱。
問與答 (Q&A)
window.addEventListener('load', () => {
if (window.location.hash) {
window.scrollBy(0, -document.querySelector('nav').offsetHeight);
}
});
這段程式會在頁面載入時,若發現 URL 中有 hash,就將頁面再往上拉一段(導覽列高度)距離,確保對應內容露出。總之,CSS 解決方案較為優雅,而 JS 後補方案可作為輔助,用於特殊情況下調整。
經過以上講解,你現在應該對「內部錨點與固定導覽列的互動設計」有了全面的瞭解。我們從定義問題出發,探討了固定導覽列如何影響錨點定位,並一步步實作了對應的解決方案和擴展功能。希望這篇教學能讓你在實際專案中輕鬆應對類似需求,打造出體驗佳、易於導航的一頁式網站或長頁面。祝你在前端開發的道路上玩得開心,寫出更出色的互動效果!