Flex overflow: auto 不捲動?從巢狀祖先檢查 min-height: 0
側邊工具或聊天室做成直向 Flex,標頭固定、下方內容設 overflow: auto,卻變成整頁往下長。先檢查外層是否真的限制高度,再沿著 Flex 主軸找出不能縮小的 item;巢狀結構裡,常要調整的是捲動區的祖先。
不能只在任何一層貼上 min-height: 0 就宣布修好。你需要知道誰分配高度、誰允許縮小,以及誰負責捲動。
最裡面的 overflow: auto,管不到上層的最小高度
Flexbox 規格的自動最小尺寸與主軸、overflow 條件有關。對非捲動的 Flex item,內容可能形成自動最小尺寸;捲動容器的自動最小尺寸則有不同處理。因此「每個 Flex 子元素都一定要 min-height: 0」並不準確。
本文刻意使用巢狀結構:
shell:直向 Flex,高度 320px├─ header:48px└─ workspace:Flex item,也是一個直向 Flex 容器 └─ pane:Flex item,overflow: auto └─ content:900px 高的內容pane 雖然負責捲動,但未限制的 workspace 仍可能按內容最小高度長大,讓下層拿到足以裝下內容的空間。內容根本沒有超過 pane 自己的高度,就不會出現你期待的局部捲動。
三組對照,保留壞掉的情境才看得出差別
將下列完整 HTML 存成 flex-scroll-demo.html,在瀏覽器開啟。檔案沒有外部資源或腳本,提供三組相同內容:
<!doctype html><html lang="zh-Hant"><meta charset="utf-8"><title>巢狀 Flex 捲動對照</title><style> * { box-sizing: border-box; } body { margin: 16px; font-family: sans-serif; } .cases { display: flex; flex-wrap: wrap; gap: 24px; } .shell { width: 280px; height: 320px; display: flex; flex-direction: column; outline: 2px solid #555; } header { flex: 0 0 48px; background: #cde9df; } .workspace { flex: 1; display: flex; flex-direction: column; } .pane { flex: 1; overflow: auto; } .content { height: 900px; padding: 12px; background: linear-gradient(#f7c4b6, #b4d6f3); } .fixed .workspace { min-height: 0; } .unbounded { height: auto; }</style><div class="cases"> <section> <h2>祖先未縮小</h2> <div class="shell broken"><header>固定標頭</header> <div class="workspace"><div class="pane" tabindex="0" role="region" aria-label="原始內容區"> <div class="content">內容開頭<br>向下捲動檢查</div> </div></div> </div> </section> <section> <h2>祖先 min-height: 0</h2> <div class="shell fixed"><header>固定標頭</header> <div class="workspace"><div class="pane" tabindex="0" role="region" aria-label="修正內容區"> <div class="content">內容開頭<br>向下捲動檢查</div> </div></div> </div> </section> <section> <h2>外層沒有高度上限</h2> <div class="shell fixed unbounded"><header>固定標頭</header> <div class="workspace"><div class="pane" tabindex="0" role="region" aria-label="無上限內容區"> <div class="content">內容開頭<br>向下捲動檢查</div> </div></div> </div> </section></div></html>第一組不加修正;第二組只在 workspace 加 min-height: 0;第三組再把 shell 改成自動高度。這樣能分辨「祖先不肯縮」與「根本沒有高度限制」,避免兩個問題用同一個補丁處理。
正式版面不必照抄 320px。你可以從 viewport 或上層容器分配高度,但必須確認最終有可計算的限制。height: 100% 如果沒有對應的包含塊高度,也不會憑空建立預算。
量測真正的 pane,不用整頁捲軸判定
在 DevTools Console 執行下列唯讀量測。先開啟範例,不是在任意網站貼上:
console.table([...document.querySelectorAll('.shell')].map(shell => { const workspace = shell.querySelector('.workspace'); const pane = shell.querySelector('.pane'); return { case: shell.className, shell: shell.clientHeight, workspace: workspace.clientHeight, pane: pane.clientHeight, content: pane.scrollHeight, minHeight: getComputedStyle(workspace).minHeight, };}));第二組應讓 workspace 與 pane 使用標頭之外的 272px,而 pane.scrollHeight 仍是 900px。第一組可觀察中間祖先被內容撐高;第三組即使 min-height: 0 生效,外層仍會跟著內容長大。
接著把焦點移到第二組內容區,實際向下捲動,確認 pane.scrollTop 改變、標頭保持原位。範例保留第一與第三組的溢位,因此整張測試頁出現垂直捲軸是預期現象,不能拿它判定第二組修正失敗。
本機 Chrome 量測結果為:第一組 pane 900px、第二組 272px、第三組 900px;第二組以 End 鍵捲至 scrollTop 628,內容高度仍為 900px。本文的本機驗收只涵蓋這個對照頁,沒有測試其他瀏覽器、螢幕閱讀器、行動裝置鍵盤或正式專案。範例加上可聚焦的具名 region,方便單獨操作內容區;正式介面仍要檢查鍵盤焦點順序與內容可達性。
還是捲不動,再沿高度鏈往外找
若第二組模式放回專案仍失敗,逐層檢查從 pane 到有高度限制容器之間的 Flex item。某一層有固定 min-height、內容尺寸限制,或另一個不肯縮小的祖先,都可能改變可用空間。只在最裡面再加一次零,無法解除外層的限制。
overflow: hidden 會改變溢位處理,可能讓表面尺寸變小,也可能裁掉內容;若需求是可讀取全部訊息,請以內容能否捲到尾端驗收。橫向長文字撐開欄位則看 Grid 的 minmax 與 min-width 邊界;sticky 行為受哪層捲動容器影響,可接著看 單軸 overflow。
參考資料: