Nuxt 3 Layout 與 Page 的高度職責分離:解決多餘 scrollbar 問題
在 Nuxt 3 的 SPA 專案中,Layout 與 Page 之間的高度管理是個容易踩坑的地方。本文記錄一個常見問題:各個 Page 各自用 h-screen 控制高度,導致出現多餘的外層 scrollbar,以及修正過程中學到的 CSS 定位觀念。
問題描述
專案的 default.vue layout 結構大致如下:
<div class="min-h-screen flex flex-col">
<!-- 公告列,固定高度 -->
<div class="h-18">...</div>
<!-- 主區塊:側邊欄 + 頁面內容 -->
<div class="flex">
<SideNav />
<div class="flex-1">
<slot />
</div>
</div>
</div>
各個 Page 的最外層則是:
<div class="h-screen overflow-y-auto ...">
<!-- 頁面內容 -->
</div>
結果:頁面出現兩層 scrollbar,外層 body 也會滾動。
根本原因分析
h-screen 與 h-full 的差異
h-screen 等同於 height: 100vh,高度固定參考視窗高度,與父層給多少空間無關。
h-full 等同於 height: 100%,繼承父層分配的高度。
在這個 layout 裡,<slot /> 的父層只有「視窗高度 - 公告列高度」的空間,但 Page 用 h-screen 自己撐成整個視窗高,內容溢出後 scrollbar 就跑到外層去了。
overflow-y: auto 需要明確的高度限制
overflow-y: auto 要能生效,元素必須有明確的高度上限1。
h-auto:高度由內容撐開,沒有上限,overflow-y: auto永遠不會觸發h-full:繼承父層高度,提供了明確的高度約束,scrollbar 才會出現在元素內部
修正方案
Layout 層:鎖死高度邊界
<div class="h-screen flex flex-col overflow-hidden">
<div v-if="showAnnouncement" class="h-18 shrink-0">
<!-- 公告列 -->
</div>
<div class="flex flex-1 overflow-hidden">
<SideNav />
<div class="flex-1 overflow-y-auto">
<slot />
</div>
</div>
</div>
關鍵改動:
- 最外層從
min-h-screen改為h-screen overflow-hidden,鎖死視窗高度 - 主區塊加
flex-1 overflow-hidden,填滿剩餘空間並防止溢出 <slot />的容器加overflow-y-auto,讓 Page 的滾動發生在這裡
Page 層:改用 h-full
<!-- 修改前 -->
<div class="h-screen overflow-y-auto ...">
<!-- 修改後 -->
<div class="h-full overflow-y-auto ...">
Detail page 的 max-h-screen 同樣改為 h-full:
<!-- 修改前 -->
<div class="max-h-screen overflow-auto">
<!-- 修改後 -->
<div class="h-full overflow-auto">
職責分工
修正後的架構,職責清楚分離:
- Layout:負責定義整體高度邊界、切割空間
- Page:只負責自己內部的排版和滾動,高度從 layout 繼承
好處是未來 layout 有變動(例如關掉公告列、加上 top bar),所有 Page 不需要跟著改,高度會自動重新分配。
延伸:position absolute 的對立方向拉伸
修正側邊欄時,學到一個實用的 CSS 行為:position: absolute 或 fixed 的元素,當同時設定對立方向的 offset,瀏覽器會用「拉伸」來滿足兩個約束2。
/* 垂直拉伸,高度填滿定位祖先 */
position: absolute;
top: 0;
bottom: 0;
/* 水平拉伸,寬度填滿定位祖先 */
position: absolute;
left: 0;
right: 0;
/* 四邊都設,完全覆蓋父層(常見於 overlay) */
position: absolute;
inset: 0;
前提是元素沒有明確設定 width 或 height。若同時設定了固定尺寸,瀏覽器會優先尊重尺寸,拉伸不會發生。
以側邊欄為例,用 inset-y-0(等同 top: 0; bottom: 0)讓 absolute 定位的側邊欄撐滿父層高度:
<!-- 外層:提供定位基準與佔位 -->
<div class="relative h-full w-24 shrink-0">
<!-- 中間層:absolute 定位,inset-y-0 撐滿高度 -->
<div class="absolute inset-y-0 left-0">
<!-- 內層:實際內容,hover 展開動畫 -->
<div class="h-full w-24 hover:w-50 transition-[width] duration-200">
...
</div>
</div>
</div>
環境資訊
- Nuxt: 3.x
- Vue: 3.x
- UnoCSS(Tailwind-compatible utility classes)
本文撰寫時間:2026 年 3 月,技術版本可能隨時間更新,請以官方文件為準。
Reference
- overflow-y - CSS | MDN ↩
- top - CSS | MDN — "If position is set to absolute or fixed and height is unspecified, both the top and bottom values are respected." ↩