藥不藥來當工程師一個不務正業的藥師,誤打誤撞來當了前端工程師。斜槓工程師的人生!

Nuxt 3 Layout 與 Page 的高度職責分離:解決多餘 scrollbar 問題

發佈於 最後更新於
5 min read

在 Nuxt 3 的 SPA 專案中,Layout 與 Page 之間的高度管理是個容易踩坑的地方。本文記錄一個常見問題:各個 Page 各自用 h-screen 控制高度,導致出現多餘的外層 scrollbar,以及修正過程中學到的 CSS 定位觀念。

問題描述

專案的 default.vue layout 結構大致如下:

html
<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 的最外層則是:

html
<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 層:鎖死高度邊界

html
<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

html
<!-- 修改前 -->
<div class="h-screen overflow-y-auto ...">

<!-- 修改後 -->
<div class="h-full overflow-y-auto ...">

Detail page 的 max-h-screen 同樣改為 h-full

html
<!-- 修改前 -->
<div class="max-h-screen overflow-auto">

<!-- 修改後 -->
<div class="h-full overflow-auto">

職責分工

修正後的架構,職責清楚分離:

  • Layout:負責定義整體高度邊界、切割空間
  • Page:只負責自己內部的排版和滾動,高度從 layout 繼承

好處是未來 layout 有變動(例如關掉公告列、加上 top bar),所有 Page 不需要跟著改,高度會自動重新分配。

延伸:position absolute 的對立方向拉伸

修正側邊欄時,學到一個實用的 CSS 行為:position: absolutefixed 的元素,當同時設定對立方向的 offset,瀏覽器會用「拉伸」來滿足兩個約束2

css
/* 垂直拉伸,高度填滿定位祖先 */
position: absolute;
top: 0;
bottom: 0;

/* 水平拉伸,寬度填滿定位祖先 */
position: absolute;
left: 0;
right: 0;

/* 四邊都設,完全覆蓋父層(常見於 overlay) */
position: absolute;
inset: 0;

前提是元素沒有明確設定 widthheight。若同時設定了固定尺寸,瀏覽器會優先尊重尺寸,拉伸不會發生。

以側邊欄為例,用 inset-y-0(等同 top: 0; bottom: 0)讓 absolute 定位的側邊欄撐滿父層高度:

html
<!-- 外層:提供定位基準與佔位 -->
<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

  1. overflow-y - CSS | MDN
  2. top - CSS | MDN — "If position is set to absolute or fixed and height is unspecified, both the top and bottom values are respected."
Copyright 2020-2026 - AzureBlue ALL RIGHTS RESERVED.