在視窗控制項旁的標題列區域,讓 PWA 更像應用程式。
如果您還記得我撰寫的「讓 PWA 更像應用程式」一文,應該會記得我提到自訂應用程式的標題列,是打造更像應用程式體驗的策略。以下是 macOS 播客應用程式的顯示範例。

你可能會想反駁,認為「Podcast」是 macOS 專用的應用程式,不會在瀏覽器中執行,因此可以隨意運作,不必遵守瀏覽器的規則。沒錯,但好消息是,本文要介紹的「視窗控制項重疊」功能很快就會推出,讓您為 PWA 建立類似的使用者介面。
視窗控制項重疊元件
視窗控制項重疊包含四項子功能:
- 網頁應用程式資訊清單中
"display_override"
欄位的"window-controls-overlay"
值。 - CSS 環境變數
titlebar-area-x
、titlebar-area-y
、titlebar-area-width
和titlebar-area-height
。 - 先前專屬的 CSS 屬性
-webkit-app-region
已標準化為app-region
屬性,用於定義網頁內容中的可拖曳區域。 - 透過
window.navigator
的windowControlsOverlay
成員查詢及規避視窗控制項區域的機制。
什麼是視窗控制項重疊?
標題列區域是指視窗控制項 (即最小化、最大化、關閉等按鈕) 左側或右側的空間,通常會包含應用程式的標題。漸進式網頁應用程式 (PWA) 可透過視窗控制項疊加層,將現有的全寬標題列換成含有視窗控制項的小型疊加層,提供更接近應用程式的體驗。開發人員可藉此在先前由瀏覽器控管的標題列區域中,放置自訂內容。
目前狀態
如何使用視窗控制項重疊顯示
將 window-controls-overlay
新增至網頁應用程式資訊清單
漸進式網頁應用程式可以將 "window-controls-overlay"
新增為網頁應用程式資訊清單中的主要 "display_override"
成員,選擇加入視窗控制項疊加層:
{
"display_override": ["window-controls-overlay"]
}
只有在符合下列所有條件時,才會顯示視窗控制項疊加層:
- 應用程式不會在瀏覽器中開啟,而是在獨立的 PWA 視窗中開啟。
- 資訊清單包含
"display_override": ["window-controls-overlay"]
。(之後可使用其他值)。 - PWA 正在桌機作業系統上執行。
- 目前的來源與 PWA 安裝來源相符。
結果會是空白的標題列區域,左側或右側則會顯示一般視窗控制項,視作業系統而定。

將內容移至標題列
標題列現在有空間,因此您可以將項目移到該處。在本文中,我建構了 Wikimedia 精選內容 PWA。這類應用程式的實用功能可能是搜尋文章標題中的字詞。搜尋功能的 HTML 如下所示:
<div class="search">
<img src="logo.svg" alt="Wikimedia logo." width="32" height="32" />
<label>
<input type="search" />
Search for words in articles
</label>
</div>
如要將這個 div
移至標題列,需要使用一些 CSS:
.search {
/* Make sure the `div` stays there, even when scrolling. */
position: fixed;
/**
* Gradient, because why not. Endless opportunities.
* The gradient ends in `#36c`, which happens to be the app's
* `<meta name="theme-color" content="#36c">`.
*/
background-image: linear-gradient(90deg, #36c, #131313, 33%, #36c);
/* Use the environment variable for the left anchoring with a fallback. */
left: env(titlebar-area-x, 0);
/* Use the environment variable for the top anchoring with a fallback. */
top: env(titlebar-area-y, 0);
/* Use the environment variable for setting the width with a fallback. */
width: env(titlebar-area-width, 100%);
/* Use the environment variable for setting the height with a fallback. */
height: env(titlebar-area-height, 33px);
}
您可以在下方的螢幕截圖中查看這段程式碼的效果。標題列完全採用回應式設計,當您調整 PWA 視窗大小時,標題列會像一般 HTML 內容一樣做出反應,而事實上,標題列確實是由 HTML 內容組成。

判斷標題列的哪些部分可拖曳
雖然上述螢幕截圖顯示您已完成操作,但其實還沒。PWA 視窗無法再拖曳 (除了很小的區域),因為視窗控制項按鈕不是拖曳區域,而其餘的標題列則包含搜尋小工具。如要修正這個問題,請使用 app-region
CSS 屬性,並將值設為 drag
。在具體案例中,除了 input
元素以外,其他所有項目都可以拖曳。
/* The entire search `div` is draggable… */
.search {
-webkit-app-region: drag;
app-region: drag;
}
/* …except for the `input`. */
input {
-webkit-app-region: no-drag;
app-region: no-drag;
}
設定這個 CSS 後,使用者就能像平常一樣拖曳 div
、img
或 label
,拖曳應用程式視窗。只有 input
元素是互動式,因此可以輸入搜尋查詢。
特徵偵測
您可以測試是否存在 windowControlsOverlay
,偵測視窗控制項重疊顯示的支援情形:
if ('windowControlsOverlay' in navigator) {
// Window Controls Overlay is supported.
}
使用 windowControlsOverlay
查詢視窗控制項區域
目前為止,程式碼有一個問題:在某些平台上,視窗控制項位於右側,在其他平台上則位於左側。更糟的是,Chrome「三點」選單也會根據平台變更位置。也就是說,線性漸層背景圖片必須動態調整,才能從 #131313
→maroon
或 maroon
→#131313
→maroon
執行,與標題列的 maroon
背景顏色 (由 <meta name="theme-color" content="maroon">
決定) 融為一體。只要查詢 navigator.windowControlsOverlay
屬性上的 getTitlebarAreaRect()
API,即可達成這個目標。
if ('windowControlsOverlay' in navigator) {
const { x } = navigator.windowControlsOverlay.getTitlebarAreaRect();
// Window controls are on the right (like on Windows).
// Chrome menu is left of the window controls.
// [ windowControlsOverlay___________________ […] [_] [■] [X] ]
if (x === 0) {
div.classList.add('search-controls-right');
}
// Window controls are on the left (like on macOS).
// Chrome menu is right of the window controls overlay.
// [ [X] [_] [■] ___________________windowControlsOverlay [⋮] ]
else {
div.classList.add('search-controls-left');
}
} else {
// When running in a non-supporting browser tab.
div.classList.add('search-controls-right');
}
修改後的程式碼現在會使用上述程式碼動態設定的兩個類別,而不是直接在 .search
類別 CSS 規則中加入背景圖片 (如先前做法)。
/* For macOS: */
.search-controls-left {
background-image: linear-gradient(90deg, #36c, 45%, #131313, 90%, #36c);
}
/* For Windows: */
.search-controls-right {
background-image: linear-gradient(90deg, #36c, #131313, 33%, #36c);
}
判斷視窗控制項重疊顯示是否可見
在某些情況下,視窗控制項疊加層不會顯示在標題列區域。如果瀏覽器不支援「視窗控制項重疊」功能,自然不會顯示這個按鈕。此外,如果相關 PWA 在分頁中執行,也不會顯示這個按鈕。如要偵測這種情況,可以查詢 windowControlsOverlay
的 visible
屬性:
if (navigator.windowControlsOverlay.visible) {
// The window controls overlay is visible in the title bar area.
}
或者,您也可以在 JavaScript 和/或 CSS 中使用 display-mode
媒體查詢:
// Create the query list.
const mediaQueryList = window.matchMedia('(display-mode: window-controls-overlay)');
// Define a callback function for the event listener.
function handleDisplayModeChange(mql) {
// React on display mode changes.
}
// Run the display mode change handler once.
handleDisplayChange(mediaQueryList);
// Add the callback function as a listener to the query list.
mediaQueryList.addEventListener('change', handleDisplayModeChange);
@media (display-mode: window-controls-overlay) {
/* React on display mode changes. */
}
接收幾何圖形變更通知
使用 getTitlebarAreaRect()
查詢視窗控制項重疊區域,足以處理一次性事項,例如根據視窗控制項所在位置設定正確的背景圖片,但在其他情況下,則需要更精細的控制。舉例來說,您可以根據可用空間調整視窗控制項重疊顯示,並在空間足夠時,在視窗控制項重疊顯示中加入笑話。

如要接收幾何變化通知,請訂閱 navigator.windowControlsOverlay.ongeometrychange
或為 geometrychange
事件設定事件監聽器。只有在視窗控制項疊加層顯示時,才會觸發這個事件,也就是 navigator.windowControlsOverlay.visible
為 true
時。
const debounce = (func, wait) => {
let timeout;
return function executedFunction(...args) {
const later = () => {
clearTimeout(timeout);
func(...args);
};
clearTimeout(timeout);
timeout = setTimeout(later, wait);
};
};
if ('windowControlsOverlay' in navigator) {
navigator.windowControlsOverlay.ongeometrychange = debounce((e) => {
span.hidden = e.titlebarAreaRect.width < 800;
}, 250);
}
除了將函式指派給 ongeometrychange
,您也可以新增事件監聽器至 windowControlsOverlay
,如下所示。如要瞭解兩者的差異,請參閱 MDN。
navigator.windowControlsOverlay.addEventListener(
'geometrychange',
debounce((e) => {
span.hidden = e.titlebarAreaRect.width < 800;
}, 250),
);
在分頁中執行時的相容性,以及不支援的瀏覽器
有兩種可能情況:
- 應用程式在支援 Window Controls Overlay 的瀏覽器中執行,但應用程式是在瀏覽器分頁中使用的情況。
- 應用程式在不支援視窗控制項重疊顯示的瀏覽器中執行。
在這兩種情況下,預設會顯示為視窗控制項疊加層建構的 HTML,就像一般 HTML 內容一樣內嵌顯示,而 env()
變數的回退值會啟動定位功能。在支援的瀏覽器上,您也可以檢查疊加層的 visible
屬性,決定是否顯示為視窗控制項疊加層指定的 HTML,如果該屬性回報 false
,則隱藏該 HTML 內容。

提醒您,不支援的瀏覽器會完全忽略 "display_override"
網路應用程式資訊清單屬性,或無法辨識 "window-controls-overlay"
,因此會根據備援鏈使用下一個可能的值,例如 "standalone"
。

使用者介面注意事項
雖然您可能會想在「視窗控制項重疊」區域中建立傳統下拉式選單,但我們不建議這麼做。這麼做會違反 macOS 的設計規範,因為使用者會期待在螢幕頂端看到選單列 (包括系統提供的選單列和自訂選單列)。
如果應用程式提供全螢幕體驗,請仔細考量視窗控制項重疊是否適合做為全螢幕檢視畫面的一部分。onfullscreenchange
事件觸發時,您可能想重新排列版面配置。
示範
我建立了一個示範,您可以在支援和不支援的瀏覽器中,以及安裝和未安裝的狀態下使用。如要體驗實際的視窗控制項重疊畫面,請安裝應用程式。下方有兩張螢幕截圖,可供您參考。您可以在 Glitch 上找到應用程式的原始碼。

視窗控制項重疊顯示中的搜尋功能可正常運作:

安全性考量
Chromium 團隊設計及實作 Window Controls Overlay API 時,採用了「控管強大的網頁平台功能存取權」中定義的核心原則,包括使用者控制、透明度和人體工學。
假冒
如果網站能部分控管標題列,開發人員就能在先前受信任的瀏覽器控制區域中,偽造內容。目前在 Chromium 瀏覽器中,獨立模式包含標題列,首次啟動時會在左側顯示網頁標題,右側顯示網頁來源 (後面接著「設定及其他」按鈕和視窗控制項)。幾秒後,原始文字就會消失。如果瀏覽器設為從右到左 (RTL) 的語言,這個版面配置會翻轉,原始文字會顯示在左側。如果來源與重疊視窗右側邊緣之間的邊框間距不足,系統會開啟視窗控制項重疊畫面,以模擬來源。舉例來說,來源「evil.ltd」可能會附加受信任的網站「google.com」,讓使用者以為來源值得信賴。我們打算保留原始文字,讓使用者瞭解應用程式的來源,並確保符合他們的期望。對於設定為 RTL 的瀏覽器,來源文字右側必須有足夠的邊框間距,防止惡意網站將不安全的來源附加至可信任的來源。
指紋
啟用視窗控制項重疊顯示和可拖曳區域,除了功能偵測外,不會造成重大隱私權問題。不過,由於各作業系統的視窗控制按鈕大小和位置不同,navigator.
方法會傳回 DOMRect
,其位置和尺寸會揭露瀏覽器執行的作業系統資訊。目前開發人員可以從使用者代理程式字串中找出作業系統,但由於指紋辨識疑慮,我們正在討論凍結 UA 字串並統一作業系統版本。瀏覽器社群目前正努力瞭解各平台上的視窗控制項疊加層大小變化頻率,因為目前的假設是這些項目在各個 OS 版本中相當穩定,因此不適合用來觀察次要 OS 版本。雖然這可能是指紋辨識問題,但只適用於使用自訂標題列功能的已安裝 PWA,不適用於一般瀏覽器使用情況。此外,在 PWA 內嵌的 iframe 無法使用 navigator.
API。
導覽
即使 PWA 符合上述條件,並以視窗控制項疊加層啟動,只要導覽至 PWA 內的其他來源,就會返回正常的獨立標題列。這是為了配合導覽至不同來源時顯示的黑條。返回原始來源後,系統會再次使用視窗控制項疊加層。

意見回饋
Chromium 團隊很想瞭解您使用 Window Controls Overlay API 的體驗。
介紹 API 設計
API 是否有任何功能無法如預期運作?或者,是否有缺少的屬性或方法需要實作,才能實現您的想法?對安全模型有任何問題或意見嗎?在對應的 GitHub 存放區中提出規格問題,或在現有問題中新增想法。
回報導入問題
您是否發現 Chromium 實作有錯誤?還是實作方式與規格不同?
在 new.crbug.com 回報錯誤。請務必盡可能提供詳細資料、重現問題的簡單操作說明,並在「Components」(元件) 方塊中輸入 UI>Browser>WebAppInstalls
。
支援 API
您打算使用 Window Controls Overlay API 嗎?您的公開支持有助於 Chromium 團隊優先處理功能,並向其他瀏覽器供應商展現支援這些功能的重要性。
在 Twitter 上發布推文並加上 @ChromiumDev #WindowControlsOverlay
主題標記,告訴我們您在何處及如何使用。
實用連結
特別銘謝
視窗控制項重疊顯示功能是由 Microsoft Edge 團隊的 Amanda Baker 實作及指定。本文由 Joe Medley 和 Kenneth Rohde Christiansen 審查。主頁橫幅圖片由 Unsplash 的 Sigmund 提供。