行動裝置上 100vh 的奇怪行爲
最近遇到一個手機瀏覽器上,在某個特定排版會發生高度有誤的問題
進而發現手機瀏覽器的特殊行爲
查了一下資料,發現這個問題已經存在許久,也有不少的解法
大致分為用 JS 及 CSS 去解決
CSS 的解決方式比較簡單明瞭
想要直接看結果,可以走傳送門:JS 解決方案 、CSS 解決方案
這個頁面是滿版的,所以 container 的部分佔滿整個 document.body
container 裡面,由上而下包含三個區域:header、body、footer
header 與 footer 分別固定在 container 上緣與下緣,body 撐滿剩下的空間,如果内容超過就會有捲軸滾動
layout 的示意圖如下:

意料之外的 100vh
Section titled “意料之外的 100vh”外層的 container 寬高分別是 100vw、100vh,在電腦上看起來沒什麼問題,然而在手機上就不是那麼回事:

可以看到 container 高度是 100vh,卻仍然可以 scroll 的現象,照理來說應該要剛好撐滿才是
- 頁面連結:100vh-issue-1-page
- 原始碼:100vh-issue-1-code
備註:可以在手機上點開連結,看一下真實情況
因此,若將 container 裡面的東西加進來,就會造成內外都有 scroll 的情況:

- 頁面連結:100vh-issue-2-page
- 原始碼:100vh-issue-2-code
看來 CSS 所抓到的 100vh,跟實際上的 window 高度不一樣,window 比較小,所以造成了 scroll
於是,試著把 container 元素的高度與 window 的高度印出來
var containerElement = document.getElementById("container");var containerHeight = containerElement.clientHeight;var heightElement = document.getElementById("container-height");heightElement.innerText = "container clientHeight: " + containerHeight + "px";
var windowHeightEl = document.getElementById("window-height");windowHeightEl.innerHTML = "window innerHeight: " + window.innerHeight + "px";在電腦瀏覽器上,這兩個數字會是一樣的,在手機上卻不同:

查了一下網路上的資料,大概有兩種方式可以解
一個是用 JS,一個是用 CSS
JS 的解決方案
Section titled “JS 的解決方案”監聽 window 的 resize 事件
Section titled “監聽 window 的 resize 事件”於是我加上一個 resize 事件,監聽 window 的高度變化,並顯示在畫面上:
var containerElement = document.getElementById("container");var containerHeight = containerElement.clientHeight;var heightElement = document.getElementById("container-height");heightElement.innerText = "container clientHeight: " + containerHeight + "px";
function updateWindowHeight() { var windowHeightEl = document.getElementById("window-height"); windowHeightEl.innerHTML = "window innerHeight: " + window.innerHeight + "px";}
updateWindowHeight();
window.addEventListener("resize", updateWindowHeight);在 iOS Chrome 上,可以看出兩者的高度並不一致:

iOS Safari 與 Android Chrome 的彩蛋
Section titled “iOS Safari 與 Android Chrome 的彩蛋”iOS 的 Safari,除了 container 與 window 高度不同之外,還發現了另一個現象

在最頂的時候往下滑動,window 的高度是動態的!
在 Android Chrome 上,也出現 window 變化的狀況,但還是有點差異

原來是彩蛋啊!!🥚
-
頁面連結:100vh-issue-3-page
複寫 container 的高度
Section titled “複寫 container 的高度”既然 100vh 與 window 的高度會不一致,而且在 iOS Safari 與 Android Chrome 上還會不斷改變,
那麼就在監聽 window 的 resize 事件時,
將 container 元素的高度複寫成當下的 window 高度就行了:
(function () { function updateWindowHeight() { var currentWindowHeight = window.innerHeight;
var windowHeightEl = document.getElementById("window-height"); windowHeightEl.innerHTML = "window innerHeight: " + currentWindowHeight + "px";
var containerElement = document.getElementById("container"); containerElement.style.height = currentWindowHeight + "px"; var containerHeight = containerElement.clientHeight;
var heightElement = document.getElementById("container-height"); heightElement.innerText = "container clientHeight: " + containerHeight + "px"; }
updateWindowHeight();
window.addEventListener("resize", updateWindowHeight);})();
我們可以看到,container 高度與 window 高度,始終保持同步,也就避免出現外層 scroll 的情況發生了

旋轉螢幕也不是問題~
- 頁面連結:100vh-issue-4-page
- 原始碼:100vh-issue-4-code
既然 container 的高度解決了,再將 container 裡面的 header、body、footer 加進來:

一切看來都非常美好 😎
至於 resize 事件是否要加入 debounce 增進效能,那就看個人選擇了
CSS 的解決方案
Section titled “CSS 的解決方案”CSS 的方法有分幾種:
max-height: -webkit-fill-available
Section titled “max-height: -webkit-fill-available”.container { display: flex; flex-direction: column; width: 100vw; height: 100vh; max-height: -webkit-fill-available; // 加上 fill-available background: lavender;}但如同前綴 -webkit- 的字面意義,在 Android Chrome 上仍然有問題
但 Windows 及 macOS 上的 Chrome 卻沒事
| Chrome | Safari | |
|---|---|---|
| macOS | O | O |
| Windows | O | NA |
| iOS | O | O |
| Android | X | NA |
html, body height 100%
Section titled “html, body height 100%”將 html 與 body 的高度設為 100%,底下的 container 就放心地用 100%
完全將在 mobile 上有爭議的 vh 單位移除了,是一個簡潔的方式
html,body { margin: 0; padding: 0; height: 100%; // 用 100% width: 100%;}
.container { display: flex; flex-direction: column; width: 100%; height: 100%; // 這裡也用 100% background: lavender;}所有平台及裝置的瀏覽器都通過了考驗 💯
| Chrome | Safari | |
|---|---|---|
| macOS | O | O |
| Windows | O | NA |
| iOS | O | O |
| Android | O | NA |
新的 CSS 單位:dvh
Section titled “新的 CSS 單位:dvh”有一個人分享了利用新 CSS 單位的解法,也是一個不錯的選擇:
.container { display: flex; flex-direction: column; width: 100%; height: 100dvh; // 用單位 dvh background: lavender;}關於 dvh 的示意圖如下:

PS. 本圖來自 Google IO
但這個似乎還很新,看來支援度沒有太好
簡單測試了一下,在 Safari 可以正常運作,但是 Chrome 卻不認識這個單位
| Chrome | Safari | |
|---|---|---|
| macOS | X | O |
| Windows | X | NA |
| iOS | O | O |
| Android | X | NA |
支援度可以參考:“dvh” | Can I use… Support tables for HTML5, CSS3, etc
之後想要來研究一下關於 dvh 這一類給 mobile 專用的單位
各平台裝置比較表整理
Section titled “各平台裝置比較表整理”100vh 與 window 高度比較表
Section titled “100vh 與 window 高度比較表”| Chrome | Safari | |
|---|---|---|
| MacOS | O | O |
| Windows | O | NA |
| iOS | X | X |
| Android | X | NA |
- O:代表 100vh 與 window 高度一致
- X:代表 100vh 與 window 高度不同
- NA:此平台無此瀏覽器
window 動態高度比較表
Section titled “window 動態高度比較表”| Chrome | Safari | |
|---|---|---|
| iOS | X | O |
| Android | O | NA |
- O:代表 window 高度會隨 scroll 變化
- X:代表 window 高度固定
- NA:此平台無此瀏覽器
測試瀏覽器、作業系統與版本
Section titled “測試瀏覽器、作業系統與版本”| OS | Chrome | Safari | |
|---|---|---|---|
| iOS | 15.15 | 103.0.5060.63 | 15.15 |
| Android | 11 | 103.0.5060.71 | NA |
這次因為使用了滿版 layout 搭配 100vh,而發現了這個水很深的問題
iOS Safari 在往下滑動時,window 會不斷改變的現象,確實是大開眼界
總結下來,JS 的解法需要去監聽 resize 事件,
此外大概不可避免要搭配 debounce,會需要寫很多的程式碼,
還需要在適當的時機取消訂閱 resize 事件,避免記憶體洩漏問題,
簡單來說為了效能及資源,都要額外再做其他事
CSS 的三種解法都相對簡單,沒有太多的程式碼
但唯有第二個解法(html、body 設為 100%)通過了所有瀏覽器的考驗
第三個解法(新單位 dvh)雖然現在有相容性問題,但未來的可用性令人期待







