
當手機版網頁的上半部與下半部使用不同底色時,iOS 瀏覽器可能會把同一個顏色延伸到畫面上下。網頁內容本身看起來沒有問題,網址列周圍卻和貼近它的頁面底色不同,中間因此出現一道很明顯的分界
一開始以為是 safe area 沒處理好,後來才發現真正的問題不是內容有沒有避開瀏海或 Home Indicator,而是瀏覽器如何決定上下介面周圍要延伸什麼顏色
這篇記錄幾個試過但沒有解決的方向,以及如何讓 iOS 瀏覽器在畫面上方與下方延伸不同顏色
Safe area 和瀏覽器取色是兩件事
viewport-fit=cover 和 env(safe-area-inset-*) 解決的是版面能否延伸至整個螢幕,以及重要內容是否會被圓角、感測器區域或 Home Indicator 擋住
<meta
name="viewport"
content="width=device-width, initial-scale=1, viewport-fit=cover"
/>.page {
padding-top: env(safe-area-inset-top);
padding-bottom: env(safe-area-inset-bottom);
}WebKit 對 iPhone X 網頁設計的說明也提到,safe area 以外的 inset 區域會使用 html 或 body 的 background-color 填滿。但 safe-area-inset-top 和 safe-area-inset-bottom 只提供安全距離,不會告訴網頁網址列目前在上方還是下方,也不能指定瀏覽器上、下各自使用哪個顏色
所以即使頁面已經使用 100dvh,也完整加上 safe area padding,仍可能看到瀏覽器介面和主視覺之間的色差
為什麼 theme-color 解不了上下不同色
最直覺的作法是設定:
<meta name="theme-color" content="#000000" />theme-color 是建議瀏覽器用來呈現頁面周圍介面的主題色,但它只有一個顏色,而且瀏覽器支援並不一致
如果整頁只需要一種延伸色,這可能已經足夠;但上下需要不同顏色時,指定其中一個顏色只會讓另一端出現接縫。把它改成另一個顏色,也只是把問題從下方移到上方
因此這個案例最後沒有設定 theme-color,讓頁面邊緣的實際結構負責提供顏色
漸層背景也不一定有用
另一個腦洞大開的想法是替 html 設定從上方色過渡到下方色的漸層:
html {
background-color: var(--bottom-color);
background-image: linear-gradient(
var(--top-color) 0 50%,
var(--bottom-color) 50% 100%
);
}這能畫出網頁內的漸層,卻不能保證瀏覽器介面也照著漸層的上下位置取色。WebKit 描述的是純色背景的延伸行為,而瀏覽器介面如何取色仍是實作細節
實際測試時,頁面內雖然有漸層,工具列周圍仍可能延伸成單一顏色。因此不能只看桌面版 DevTools 的畫面,必須回到 iPhone 實機確認
關鍵是貼齊邊緣的固定純色元素
WebKit 工程師在 Bug 301756 的說明提到:當 fixed 或 sticky 元素貼近 viewport 邊緣時,Safari 會在被瀏覽器介面遮住的相鄰區域延伸純色,避免捲動時在元素旁留下突兀空隙
因此最後採用的結構是:
html、body使用頁面下方要延伸的顏色- 頂部放一個貼齊 viewport、具有實際高度的 fixed 純色元素
- 不設定
theme-color - 主內容避開這個頂部元素的高度
HTML 可以簡化成:
<body>
<div class="browser-edge-top" aria-hidden="true"></div>
<main class="app-main">
<section class="page">
<!-- 頁面內容 -->
</section>
</main>
</body>對應 CSS:
:root {
--browser-edge-top: var(--page-top-color);
--browser-edge-bottom: var(--page-bottom-color);
--edge-header-height: 0.75rem;
}
html,
body {
margin: 0;
background-color: var(--browser-edge-bottom);
}
.browser-edge-top {
position: fixed;
inset: 0 0 auto;
z-index: 40;
height: var(--edge-header-height);
background-color: var(--browser-edge-top);
}
.app-main {
padding-top: var(--edge-header-height);
}範例中的 --page-top-color 與 --page-bottom-color 應替換成專案原有的設計色彩變數
這裡有幾個細節不能省略:頂部元素要貼齊 viewport、保持可見,並使用實際的純色背景。先前把背景放在負 z-index、被其他內容蓋住的固定圖層,實機上沒有得到相同效果
改成這個結構後,iOS Safari 與 Chrome 的實機取色都能跟隨頁面上下各自的底色
多出一小段捲動怎麼處理
取色正確後又遇到另一個問題:頁面原本已有 min-height: 100dvh,現在 main 再加上 0.75rem 的頂部 padding,總高度就會超過 viewport,導致原本不該捲動的畫面多出一小段捲動距離
100dvh + 0.75rem = 超出 viewport解法不是直接設定 overflow: hidden。這樣可能讓長內容、表單或鍵盤彈出時無法正常捲動。比較安全的作法是從頁面的最小高度扣掉 header 高度:
.page {
min-height: calc(100dvh - var(--edge-header-height));
}main 的 padding 和 .page 的最小高度使用同一個 CSS 變數,之後調整 header 高度也不會再次算錯。內容比畫面長時,min-height 仍允許頁面自然撐開
這不是通用的瀏覽器介面控制 API
這個方法的本質不是直接控制 Safari 或 Chrome 的網址列,而是提供瀏覽器可以延伸的頁面邊緣顏色。因此還是有幾個限制:
- 網頁沒有標準 API 可以可靠判斷網址列目前在上方或下方
- 瀏覽器介面的取色與透明、模糊效果可能隨 iOS 版本改變
theme-color、safe area、根元素背景和貼邊元素處理的是不同問題,不能互相替代- 模擬器或桌面響應式模式不等於實機結果,至少要測 Safari 和實際會使用的其他 iOS 瀏覽器
如果頁面只需要單一延伸色,先設定一致的 html、body 背景通常比較簡單。如果上下需要不同顏色,再考慮使用「下方根背景+頂部固定純色元素」的結構
最後整理
這次真正有效的不是偵測網址列位置,也不是用 JavaScript 偷捲動 1px,而是讓頁面的 DOM 結構清楚表達上下邊緣的顏色:
- 移除會強制整體單一顏色的
theme-color - 讓
html、body使用下方應延伸的顏色 - 用貼齊頂端的 fixed 純色元素提供上方應延伸的顏色
- 主內容加上相同高度的 padding
- 從
100dvh扣掉這段高度,避免產生額外捲動 - 回到 iPhone 實機測試展開與縮小網址列後的效果
safe area 仍然重要,但它負責的是內容安全,不是瀏覽器工具列取色。把這兩件事拆開處理後,上下不同色的頁面就比較容易找到問題真正出在哪裡
參考資料: