本文 AI 產出,尚未審核

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-ifasync 載入時,直接返回 { el: to.hash } 可能找不到元素,使用 Promise 延遲捲動可以避免此問題。


常見陷阱與最佳實踐

陷阱 為什麼會發生 解決方式
返回時捲動位置失效 忽略 savedPosition,或在 scrollBehavior 中直接回傳 { top: 0 } 必須先檢查 savedPosition,若有則直接回傳
錨點滾動失靈 目標元素尚未渲染(如使用 v-if、懶載入) 使用 PromisenextTick 等方式等待元素出現
平滑滾動在舊版瀏覽器不支援 behavior: 'smooth' 只在較新瀏覽器有效 可自行加入 polyfill 或在不支援時 fallback 為普通滾動
返回 false 仍觸發捲動 某些瀏覽器在 false 時仍會滾動到頂部 改用 return { left: window.pageXOffset, top: window.pageYOffset } 取得當前位置
多個路由同時需要不同捲動策略 把所有邏輯寫在同一個 if...else 中,導致可讀性低 利用 route meta自訂函式 把策略抽離,保持 scrollBehavior 簡潔

最佳實踐

  1. 先處理 savedPosition:保持瀏覽器原生的前進/返回行為是最自然的體驗。
  2. 使用 meta 讓路由自行宣告捲動需求:將捲動邏輯與路由定義分離,維護成本更低。
  3. 對於非同步渲染的錨點,使用 Promise:避免「找不到元素」的錯誤。
  4. 在需要平滑滾動時加入 fallback:確保舊版瀏覽器仍能正常定位。
  5. 盡量避免在 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. 部落格文章內部導覽(目錄錨點)

  • 需求:文章內部目錄點擊後,平滑滾動至對應段落,且在切換至其他文章再回來時,仍保留原本的閱讀位置。
  • 實作:結合 savedPositionhash,並在文章載入完成後使用 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 讓路由自行聲明捲動行為,保持程式碼的可讀性與維護性。

在實際專案中,建議先從最簡單的「回到頂部」開始,逐步加入 savedPositionhashmeta 的支援,最後根據需求加入非同步處理與平滑滾動。只要遵循上述最佳實踐,你的 Vue3 應用將能提供更自然、更友善的瀏覽體驗。祝開發順利 🎉