Skip to main content

裝置上的 100vh 問題與現代視窗單位

明明設定了 100vh,底部的按鈕或選單卻被瀏覽器的網址列(Address Bar)擋住。

本文觀念與範例整理自 css-100vh GitHub 專案

為什麼 100vh 在手機上會壞掉?

在桌機瀏覽器裡,100vh 通常代表「現在看到的整個視窗高度」,所以拿來做 Hero 首屏或全螢幕版面幾乎沒問題。

但在 行動裝置瀏覽器(iOS Safari、Chrome 等) 裡,畫面上方與下方會有:

  • 網址列 / 工具列:會隨著使用者捲動而「伸縮、隱藏或出現」。
  • 實際可視區域:會跟著網址列高度變化,而不是固定不變。

問題在於:

  • 100vh 通常等於「網址列完全縮起來時的最大高度」。
  • 當網址列再次顯示時,瀏覽器 不會重新計算 vh

因此就會看到這些情況:

  • Hero 區塊底部的 CTA 按鈕被網址列擋住。
  • 版面多出一點點捲軸,畫面看起來「快滿版但又不是」。
  • 鍵盤彈出/收起時,整個畫面高度劇烈跳動。

這些就是「100vh 在手機上壞掉」的常見症狀。

現代視窗單位:svhdvhlvh

為了解決行動裝置上的 vh 問題,現代瀏覽器新增了三個 Viewport Height 單位

單位全名含意適合情境
svhSmall Viewport Height最小 可視高度(假設網址列始終存在)Hero、Landing 首屏:穩定、不被遮擋
dvhDynamic Viewport Height動態 可視高度(網址列伸縮時即時更新)聊天室、全螢幕 Modal、Web App 版面
lvhLarge Viewport Height最大 可視高度(網址列完全隱藏的情境)特殊背景、全螢幕特效,較少單獨使用

重點可以這樣記:

  • svh:最保守但最穩定,不求剛好填滿整個實際螢幕,但不會被網址列吃掉。
  • dvh:最接近「實際看到的高度」,適合真的要「填滿剩餘畫面」的互動式 UI。
  • lvh:頂多拿來做背景或特效,平常很少直接拿來當內容高度。

實戰情境 A:穩定的 Hero / Landing 首屏(用 svh

Landing Page 最怕:

  • 首屏底部 CTA 被網址列遮住。
  • 捲一下畫面高度改變,整塊 Hero 跳來跳去。

這種情況可以這樣寫:

.hero {
/* 舊瀏覽器 fallback:至少有滿版高度 */
min-height: 100vh;

/* 現代行動瀏覽器:用不會被網址列吃掉的可視高度 */
min-height: 100svh;

display: grid;
place-items: center;
}

說明:

  • 先寫 100vh,再覆蓋 100svh:不支援新單位的瀏覽器會停在 vh,支援的會套用 svh
  • min-height 而不是 height:內容比一頁還長時,仍可正常往下捲。

搭配 HTML 大致會長這樣:

<section class="hero">
<div class="hero-content">
<h1>行動裝置上的 100vh 問題</h1>
<p>了解為什麼會壞掉,並用 svh / dvh 解決。</p>
<button class="hero-cta">開始閱讀</button>
</div>
</section>

實戰情境 B:真正「全螢幕」的 App / Modal(用 dvh

有些情境(例如聊天室、全螢幕側邊欄、行動版 Dashboard)真的需要「畫面剩多少就佔多少」,可以用 dvh

.app-shell {
position: fixed;
inset: 0;

/* fallback:至少有滿版高度 */
height: 100vh;

/* 真正隨網址列、工具列變化的高度 */
height: 100dvh;

overflow: auto;
}

或是全螢幕 Modal:

.modal {
position: fixed;
inset: 0;
z-index: 1000;

height: 100vh;
height: 100dvh;

overflow: auto;
background: rgba(0, 0, 0, 0.6);
}

.modal__panel {
max-width: 640px;
margin: 0 auto;
background: #fff;
}

在支援 dvh 的行動瀏覽器中:

  • 網址列縮起來 → 可視高度變高 → 100dvh 也跟著增加。
  • 網址列再出現 → 100dvh 會變小 → 畫面不會被切掉或多出奇怪空白。

使用 lvh 的時機

lvh 表示「網址列完全隱藏時的最大高度」,實務上較少直接用在內容區塊,可以考慮用在:

  • 全螢幕背景圖或漸層特效。
  • 不太在意被切掉一點點的裝飾性元素。

例如:

.landing-background {
position: fixed;
inset: 0;
height: 100vh;
height: 100lvh;
background: radial-gradient(circle at top, #4dc9b0, #1e293b);
z-index: -1;
}

視覺上可以盡量鋪滿整個裝置,但文字內容區塊本身仍建議用 svh / dvh

Fallback 心法:永遠先寫 vh,再寫 svh / dvh

現代瀏覽器在解析 CSS 時,遇到「看不懂的單位」會直接忽略那一行。

因此建議的寫法是:

.page {
height: 100vh; /* 舊瀏覽器或不支援新單位時使用 */
height: 100dvh; /* 支援新單位時,覆蓋前一行 */
}

同樣概念也適用在 min-height

.hero {
min-height: 100vh;
min-height: 100svh;
}

這樣寫可以同時符合:

  • 相容性:舊瀏覽器至少有 100vh 的效果。
  • 行動體驗:新瀏覽器用更正確的 svh / dvh,避免被網址列吃掉或跳動。

本地練習與 Debug 建議

如果想實際感受差異,可以比照 css-100vh 專案 的做法:

  • 做兩個頁面:
    • 一個只用 100vh(例如 index-100vh.html)。
    • 一個用 svh / dvh + vh fallback(例如 index.html)。
  • 用 Chrome DevTools 的裝置模擬或實機手機打開,反覆:
    • 捲動頁面,讓網址列縮起來 / 再出現。
    • 注意底部按鈕是否被擋住或畫面是否跳動。

實際看過一次之後,對「vh 在手機上為什麼不可靠」會有更直覺的體感。

小結

  • 不要盲信 100vh:在行動裝置上,它通常是「網址列縮起來時的最大高度」,而不是你當下看到的螢幕高度。
  • 穩定的首屏 / Hero 區塊:用 min-height: 100vh; min-height: 100svh;,避免底部 CTA 被遮住。
  • 需要真正填滿可視區域的 UI:用 height: 100vh; height: 100dvh;,讓高度隨網址列即時變化。
  • 記得 Fallback 心法:先寫 vh,再寫 svh / dvh,就能兼顧舊瀏覽器與行動端體驗。

參考 / 延伸閱讀

  • CSS Viewport 新單位 vh, vw & dvh, lvh, svh…:整理傳統 vh 的歷史問題,以及 svhdvhlvh 等新單位的設計背景與使用場合。
    連結:CSS Viewport 新單位 vh, vw & dvh, lvh, svh…
  • Why 100vh breaks on mobile and what to use instead — Tushar Kanjariya:實際解釋為什麼 100vh 在行動瀏覽器上會超出可視區域,以及如何用現代 viewport 單位與 CSS 寫法來修正版面。
    連結:Why 100vh breaks on mobile and what to use instead