自訂 PWA's 標題列的視窗控制項

使用視窗控制項旁的標題列區域,讓 PWA 更像應用程式。

如果你還記得我的文章「讓 PWA 更像應用程式」,可能還記得我提到自訂應用程式標題列的做法,這是為了打造更像應用程式的體驗。以下是 macOS 的 Podcast 應用程式範例。

macOS 版 Podcast 應用程式的標題列,顯示媒體控制按鈕和目前播放的 Podcast 相關中繼資料。
自訂標題列可讓 PWA 更像是特定平台的應用程式。

您可能會想反對,並說 Podcast 是特定平台的 macOS 應用程式,不會在瀏覽器中執行,因此可以隨心所欲,不必遵守瀏覽器的規則。沒錯,但好消息是,您很快就能透過本篇文章的主題「視窗控制項疊加層」功能,為 PWA 建立類似的使用者介面。

視窗控制項重疊顯示組件

視窗控制項重疊功能包含四個子功能:

  1. 網頁應用程式資訊清單中 "display_override" 欄位的 "window-controls-overlay" 值。
  2. CSS 環境變數 titlebar-area-xtitlebar-area-ytitlebar-area-widthtitlebar-area-height
  3. 將先前專屬的 CSS 屬性 -webkit-app-region 標準化為 app-region 屬性,以便定義網頁內容中的可拖曳區域。
  4. 透過 window.navigatorwindowControlsOverlay 成員,查詢及處理視窗控制項區域的機制。

什麼是視窗控制項重疊顯示

標題列區域是指視窗控制項 (即最小化、最大化、關閉等按鈕) 左側或右側的空間,通常會包含應用程式的標題。視窗控制項疊加層可讓漸進式網頁應用程式 (PWA) 將現有的全寬標題列換成包含視窗控制項的小型疊加層,提供更像應用程式的體驗。這可讓開發人員將自訂內容放置在先前由瀏覽器控制的標題列區域。

目前狀態

步驟 狀態
1. 建立說明 完成
2. 建立規格初稿 完成
3. 收集意見回饋並重複設計 進行中
4. 來源試用 完成
5. 發布 已完成 (在 Chromium 104 中)

如何使用視窗控制項重疊顯示

window-controls-overlay 新增至網頁應用程式資訊清單

漸進式網頁應用程式可在網頁應用程式資訊清單中,將 "window-controls-overlay" 新增為主要 "display_override" 成員,選擇啟用視窗控制項疊加層:

{
  "display_override": ["window-controls-overlay"]
}

只有在符合下列所有條件時,系統才會顯示視窗控制項疊加層:

  1. 應用程式不會在瀏覽器中開啟,而是在另一個 PWA 視窗中開啟。
  2. 資訊清單包含 "display_override": ["window-controls-overlay"]。(之後允許其他值)。
  3. PWA 在電腦作業系統上執行。
  4. 目前的網站來源與安裝 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 內容組成,而實際上就是如此。

應用程式視窗,標題列中含有搜尋列。
新的標題列處於活動狀態,且可回應使用者操作。

判斷標題列的哪些部分可拖曳

雖然上述螢幕截圖顯示您已完成設定,但您還沒完成。由於視窗控制項按鈕不是拖曳區域,標題列的其餘部分則由搜尋小工具組成,因此 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,使用者就可以拖曳 divimglabel,照常拖曳應用程式視窗。只有 input 元素是互動式元素,因此可以輸入搜尋查詢。

特徵偵測

如要偵測是否支援視窗控制項重疊顯示功能,請測試 windowControlsOverlay 是否存在:

if ('windowControlsOverlay' in navigator) {
  // Window Controls Overlay is supported.
}

使用 windowControlsOverlay 查詢視窗控制項區域

目前的程式碼有一個問題:在某些平台上,視窗控制項位於右側,在其他平台上則位於左側。更糟的是,「三點」Chrome 選單也會根據平台變更位置。也就是說,線性漸層背景圖片需要動態調整,以便從 #131313maroonmaroon#131313maroon 執行,這樣才能與標題列的 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 在分頁中執行時,也不會出現。如要偵測這種情況,您可以查詢 windowControlsOverlayvisible 屬性:

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.visibletrue 時。

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 內容。

在瀏覽器分頁中執行的 PWA,主體中顯示重疊的視窗控制項。
在舊版瀏覽器中,標題列的控制項可輕鬆顯示在內文中。

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

在獨立模式下執行的 PWA,主體中顯示重疊的視窗控制項。
在舊版瀏覽器中,標題列的控制項可輕鬆顯示在內文中。

使用者介面考量事項

雖然這麼做可能很誘人,但我們不建議您在「Window Controls Overlay」區域中建立傳統版下拉式選單。這麼做會違反 macOS 的設計指南,因為使用者會預期在畫面頂端看到選單列 (包括系統提供和自訂的選單列)。

如果您的應用程式提供全螢幕體驗,請仔細考慮是否要讓視窗控制項重疊層成為全螢幕檢視畫面的一部分。您可能會在 onfullscreenchange 事件觸發時,重新排列版面配置。

示範

我已建立示範,您可以在不同支援和不支援的瀏覽器,以及已安裝和未安裝的狀態下進行操作。如要體驗實際的視窗控制項重疊功能,您必須安裝應用程式。下方有兩張螢幕截圖,說明您可以期待的內容。應用程式的原始碼可在 Glitch 上取得。

使用視窗控制項重疊功能的 Wikimedia 精選內容示範應用程式。
您可以使用試用版應用程式進行實驗。

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

使用 Wikimedia 精選內容示範應用程式,並在其中啟用視窗控制項重疊層,以及主動搜尋「cleopa…」這個字詞,並以紅色標示其中一個含有相符字詞「Cleopatra」的文章。
使用視窗控制項重疊的搜尋功能。

安全性考量

Chromium 團隊根據「控管強大網路平台功能的存取權」一文中定義的核心原則設計並實作 Window Controls Overlay API,包括使用者控制、透明度和人因工程。

假冒

讓網站部分控制標題列,可讓開發人員在先前信任的瀏覽器控制區域中偽造內容。目前在 Chromium 瀏覽器中,獨立模式包含標題列,在首次啟動時,左側會顯示網頁的標題,右側則會顯示網頁的來源,接著是「設定和更多」按鈕和視窗控制項。幾秒後,原始文字就會消失。如果瀏覽器已設為從右到左 (RTL) 語言,則此版面配置會翻轉,使原始文字位於左側。如果來源與重疊圖層的右邊緣之間的邊框間距不足,這會開啟視窗控制項重疊圖層來偽造來源。舉例來說,來源「evil.ltd」可能會附加可信的網站「google.com」,讓使用者誤以為來源可信。我們打算保留這個來源文字,讓使用者瞭解應用程式的來源,並確保應用程式符合使用者預期。對於已設定 RTL 的瀏覽器,來源文字右側必須有足夠的邊距,以防惡意網站將不安全的來源與信任的來源連結。

數位指紋採集

啟用視窗控制項重疊顯示和可拖曳區域,除了功能偵測之外,不會造成重大隱私權疑慮。不過,由於不同作業系統的視窗控制按鈕大小和位置不同,navigator.windowControlsOverlay.getTitlebarAreaRect() 方法會傳回 DOMRect,其位置和尺寸會揭露瀏覽器執行的作業系統相關資訊。目前,開發人員已可透過使用者代理程式字串發現作業系統,但基於指紋辨識的疑慮,我們正在討論是否要凍結 UA 字串,並統一作業系統版本。瀏覽器社群目前正致力於瞭解不同平台上重疊視窗控制項大小的變化頻率,因為目前的假設是這些大小在不同作業系統版本之間相當穩定,因此無法用於觀察次要作業系統版本。雖然這是潛在的指紋辨識問題,但只會影響使用自訂標題列功能的已安裝 PWA,不會影響一般瀏覽器的使用情形。此外,navigator.windowControlsOverlay API 不適用於 PWA 內嵌的 iframe。

在 PWA 中前往其他來源時,即使符合上述條件並以視窗控制項疊加層啟動,也會導致 PWA 改用一般獨立標題列。這是為了配合導覽至其他來源時顯示的黑色列。返回原始來源後,系統會再次使用視窗控制項疊加層。

用於來源外導覽的黑色網址列。
當使用者前往其他來源時,系統會顯示黑色列。

意見回饋

Chromium 團隊希望瞭解您使用 Window Controls Overlay API 的體驗。

請說明 API 設計

API 是否有任何部分無法正常運作?或者,您是否缺少實作想法所需的方法或屬性?您對安全性模型有任何問題或意見嗎?在對應的 GitHub 存放區中提出規格問題,或在現有問題中加入您的想法。

回報導入問題

你是否發現 Chromium 實作項目有錯誤?或者實作方式與規格不同?請前往 new.crbug.com 回報錯誤。請務必盡可能提供詳細資訊,並在「Components」方塊中輸入 UI>Browser>WebAppInstalls,以便重現問題。Glitch 可讓您輕鬆快速地分享重現內容。

顯示對 API 的支援

您打算使用 Window Controls Overlay API 嗎?您公開表示的支持,有助於 Chromium 團隊將功能列為優先,並向其他瀏覽器供應商顯示,支援這些功能的重要性。

請使用 #WindowControlsOverlay 主題標記,向 @ChromiumDev 發送推文,告訴我們你在何處使用這個功能,以及使用方式。

實用連結

特別銘謝

視窗控制項疊加層是由 Microsoft Edge 團隊的 Amanda Baker 實作及指定。本文由 Joe MedleyKenneth Rohde Christiansen 審查。主頁橫幅圖片由 SigmundUnsplash 上提供。