Vue3 單元:路由系統(Vue Router 4)— scrollBehavior 控制
簡介
在單頁應用(SPA)中,使用者在不同路由之間切換時,瀏覽器的捲動位置往往會影響使用體驗。若未加以控制,可能會出現「跳回頂部」或「保留上一次捲動位置」不符合需求的情況。Vue Router 4 為我們提供了 scrollBehavior 這個全局選項,讓開發者可以自訂每次導航時的捲動行為,從而提升 使用者體驗 與 可存取性。
本篇文章將從概念說明、實作範例、常見陷阱與最佳實踐,逐步帶你掌握 scrollBehavior 的使用方式,並以實務案例說明它在真實專案中的價值。
核心概念
1. scrollBehavior 的基本結構
在建立 Vue Router 實例時,我們可以傳入 scrollBehavior 函式:
import { createRouter, createWebHistory } from 'vue-router'
const router = createRouter({
history: createWebHistory(),
routes,
scrollBehavior(to, from, savedPosition) {
// 必須回傳一個位置物件或 Promise
return { left: 0, top: 0 }
}
})
to:目標路由的RouteLocationNormalized物件。from:離開的來源路由。savedPosition:當使用者點擊瀏覽器的「前進」或「返回」按鈕時,瀏覽器會自動保存的捲動位置(若有的話),此時savedPosition會是一個{ left, top }物件,否則為null。
scrollBehavior 必須回傳一個 位置物件({ left, top })或 Promise,Vue Router 會依此決定捲動到哪裡。
2. 常見的捲動需求
| 需求 | 實作方式 |
|---|---|
| 每次切換路由都回到頁面頂部 | 回傳 { left: 0, top: 0 } |
| 返回上一頁時保留原本捲動位置 | 若 savedPosition 不為 null,直接回傳它 |
| 對特定路由使用錨點(hash)滾動 | 判斷 to.hash,回傳對應的 selector |
| 平滑滾動 | 回傳 { el: selector, behavior: 'smooth' }(需瀏覽器支援) |
| 依據路由 meta 動態決定捲動 | 讀取 to.meta.scrollToTop 等自訂屬性 |
3. 程式碼範例
以下提供 五個實用範例,涵蓋最常見的需求,並在每段程式碼中加入說明註解。
範例 1:基本回到頂部
scrollBehavior(to, from, savedPosition) {
// 每次導航都回到頁面最上方
return { left: 0, top: 0 }
}
說明:最簡單的實作,適合內容較短或不需要保留捲動的列表頁面。
範例 2:保留前進/返回的捲動位置
scrollBehavior(to, from, savedPosition) {
// 若瀏覽器有保存的捲動位置(前進/返回),直接使用
if (savedPosition) {
return savedPosition
}
// 否則回到頂部
return { left: 0, top: 0 }
}
說明:利用
savedPosition讓使用者在點擊瀏覽器的「回上一頁」時,頁面會回到離開前的捲動位置。
範例 3:支援錨點(hash)滾動
scrollBehavior(to, from, savedPosition) {
if (savedPosition) {
return savedPosition
}
// 若 URL 帶有 hash,滾動到對應的元素
if (to.hash) {
return {
el: to.hash, // 例如 #section2
behavior: 'smooth' // 平滑滾動(可選)
}
}
return { left: 0, top: 0 }
}
說明:當路由 URL 為
/about#team時,頁面會平滑滾動至id="team"的元素。
範例 4:依據路由 meta 動態決定是否回頂部
// 路由設定中加入 meta
const routes = [
{ path: '/', component: Home },
{ path: '/list', component: List, meta: { scrollToTop: true } },
{ path: '/detail/:id', component: Detail }
]
scrollBehavior(to, from, savedPosition) {
if (savedPosition) {
return savedPosition
}
// 若路由 meta 設定 scrollToTop 為 true,回到頂部
if (to.meta.scrollToTop) {
return { left: 0, top: 0 }
}
// 否則保持當前位置(適用於 infinite scroll 等情況)
return false
}
說明:
return false代表 不改變捲動位置,讓列表頁面在切換到詳細頁再返回時仍保留原本的捲動點。
範例 5:非同步取得目標元素後再捲動(Promise)
scrollBehavior(to, from, savedPosition) {
if (to.hash) {
// 假設元素是動態載入,需要等它渲染完成
return new Promise(resolve => {
// 觀察 DOM,等到元素出現再滾動
const checkExist = setInterval(() => {
const el = document.querySelector(to.hash)
if (el) {
clearInterval(checkExist)
resolve({ el: to.hash, behavior: 'smooth' })
}
}, 100)
})
}
return { left: 0, top: 0 }
}
說明:當目標元素是透過
v-if或async載入時,直接返回{ el: to.hash }可能找不到元素,使用 Promise 延遲捲動可以避免此問題。
常見陷阱與最佳實踐
| 陷阱 | 為什麼會發生 | 解決方式 |
|---|---|---|
| 返回時捲動位置失效 | 忽略 savedPosition,或在 scrollBehavior 中直接回傳 { top: 0 } |
必須先檢查 savedPosition,若有則直接回傳 |
| 錨點滾動失靈 | 目標元素尚未渲染(如使用 v-if、懶載入) |
使用 Promise 或 nextTick 等方式等待元素出現 |
| 平滑滾動在舊版瀏覽器不支援 | behavior: 'smooth' 只在較新瀏覽器有效 |
可自行加入 polyfill 或在不支援時 fallback 為普通滾動 |
返回 false 仍觸發捲動 |
某些瀏覽器在 false 時仍會滾動到頂部 |
改用 return { left: window.pageXOffset, top: window.pageYOffset } 取得當前位置 |
| 多個路由同時需要不同捲動策略 | 把所有邏輯寫在同一個 if...else 中,導致可讀性低 |
利用 route meta 或 自訂函式 把策略抽離,保持 scrollBehavior 簡潔 |
最佳實踐
- 先處理
savedPosition:保持瀏覽器原生的前進/返回行為是最自然的體驗。 - 使用
meta讓路由自行宣告捲動需求:將捲動邏輯與路由定義分離,維護成本更低。 - 對於非同步渲染的錨點,使用 Promise:避免「找不到元素」的錯誤。
- 在需要平滑滾動時加入 fallback:確保舊版瀏覽器仍能正常定位。
- 盡量避免在
scrollBehavior中使用大量計算或 API 請求:此函式會在每次導航時呼叫,過重的運算會影響導航流暢度。
實際應用場景
1. 電商商品列表與細節頁
- 需求:使用者在商品列表(具備 infinite scroll)點擊商品進入細節頁,返回列表時希望保留當前的捲動位置。
- 實作:在列表路由的
meta加上{ keepScroll: true },scrollBehavior判斷此 flag,返回false或當前window.scrollY。
// router/index.js
{
path: '/products',
component: ProductList,
meta: { keepScroll: true }
}
// scrollBehavior
if (to.meta.keepScroll) {
return { left: 0, top: window.pageYOffset }
}
2. 部落格文章內部導覽(目錄錨點)
- 需求:文章內部目錄點擊後,平滑滾動至對應段落,且在切換至其他文章再回來時,仍保留原本的閱讀位置。
- 實作:結合
savedPosition與hash,並在文章載入完成後使用 Promise 確保元素已渲染。
scrollBehavior(to, from, savedPosition) {
if (savedPosition) return savedPosition
if (to.hash) {
return new Promise(resolve => {
const el = document.querySelector(to.hash)
if (el) {
resolve({ el: to.hash, behavior: 'smooth' })
} else {
// 等待 300ms 再嘗試
setTimeout(() => resolve({ left: 0, top: 0 }), 300)
}
})
}
return { left: 0, top: 0 }
}
3. 多語系網站的 SEO 友好路由
- 需求:切換語系時,URL 會改變(如
/en/about→/zh-TW/about),但使用者仍希望回到頁面頂部。 - 實作:在語系切換的路由上加上
meta: { scrollToTop: true },讓scrollBehavior統一處理。
{
path: '/en/about',
component: About,
meta: { scrollToTop: true }
},
{
path: '/zh-TW/about',
component: About,
meta: { scrollToTop: true }
}
總結
scrollBehavior 雖然只是一個小小的設定,但它直接影響使用者在 SPA 中的 瀏覽流暢度 與 感知品質。透過本文的概念說明、五個實用範例、常見陷阱與最佳實踐,你應該已經能夠:
- 依需求在每次路由切換時 自訂捲動位置。
- 正確處理瀏覽器的「前進/返回」保存位置 (
savedPosition)。 - 為 錨點、平滑滾動、非同步渲染 等情境提供可靠的解決方案。
- 利用 route meta 讓路由自行聲明捲動行為,保持程式碼的可讀性與維護性。
在實際專案中,建議先從最簡單的「回到頂部」開始,逐步加入 savedPosition、hash 與 meta 的支援,最後根據需求加入非同步處理與平滑滾動。只要遵循上述最佳實踐,你的 Vue3 應用將能提供更自然、更友善的瀏覽體驗。祝開發順利 🎉