使用 Window Management API 管理多個螢幕

取得已連接螢幕和定位視窗相對於螢幕的資訊。

Window Management API

Window Management API 可讓您列舉連線至機器的螢幕,並在特定畫面中放置視窗。

建議用途

可能使用此 API 的網站範例包括:

  • 多視窗圖形編輯器 (Gimp) 可將各種編輯工具放入正確定位的視窗中。
  • 虛擬交易桌面可顯示多個視窗中的市場趨勢,而且可在全螢幕模式下查看。
  • 投影播放應用程式可在內部主螢幕顯示演講者備忘稿,以及在外部投影機上顯示簡報。

如何使用 Window Management API

問題所在

過去經過時間測試來控制視窗的方法 Window.open(),可惜不知道還有哪些螢幕。雖然這個 API 的某些面向似乎很有意義,例如其 windowFeatures DOMString 參數,但我們多年來一直提供這項服務。若要指定視窗的位置,您可以將座標以 lefttop (或 screenXscreenY) 的形式傳遞,並將所需大小做為 widthheight (或 innerWidthinnerHeight) 傳遞。舉例來說,如要開啟距離左側 50 像素、距離頂端 50 像素的 400×300 視窗,您可以使用以下程式碼:

const popup = window.open(
  'https://example.com/',
  'My Popup',
  'left=50,top=50,width=400,height=300',
);

您可以查看傳回 Screen 物件的 window.screen 屬性,取得目前畫面的相關資訊。這是 MacBook Pro 13 吋的輸出內容:

window.screen;
/* Output from my MacBook Pro 13″:
  availHeight: 969
  availLeft: 0
  availTop: 25
  availWidth: 1680
  colorDepth: 30
  height: 1050
  isExtended: true
  onchange: null
  orientation: ScreenOrientation {angle: 0, type: "landscape-primary", onchange: null}
  pixelDepth: 30
  width: 1680
*/

我和大多數科技業人員一樣,必須適應新的工作方式,並且設立個人家庭辦公室。我的相片看起來像下面這樣的相片 (如有需要,請參閱「設定的完整詳細資料」)。 我 MacBook 旁的 iPad 會透過補充資訊連接至筆記型電腦,因此每當我需要,我可以快速將 iPad 轉到第二個螢幕。

兩張椅子上的學校長椅。學校長椅上方有支撐的鞋盒,周圍有一台筆電和兩支 iPad。
多螢幕設定。

如果想要利用更大的螢幕,我可以把上述程式碼範例中的彈出式視窗放在第二個畫面。方法如下:

popup.moveTo(2500, 50);

由於無法得知第二個畫面的尺寸,因此這項估計值。window.screen 的資訊僅涵蓋內建畫面,不包括 iPad 螢幕。回報的內建螢幕 width1680 像素,因此改用 2500 像素後,可能會將視窗轉移至 iPad,因為「我」得知它位於 MacBook 的右側。一般情況下,我該怎麼做?反之,有更好的方法可以猜測。這樣就是 Window Management API。

功能偵測

如要確認 Window Management API 是否受支援,請使用:

if ('getScreenDetails' in window) {
  // The Window Management API is supported.
}

window-management 權限

我必須先要求使用者授予相關權限,才能使用 Window Management API。您可以透過 Permissions API 查詢 window-management 權限,如下所示:

let granted = false;
try {
  const { state } = await navigator.permissions.query({ name: 'window-management' });
  granted = state === 'granted';
} catch {
  // Nothing.
}

如果使用舊版和新權限名稱的瀏覽器,在要求權限時請務必使用防禦程式碼,如以下範例所示。

async function getWindowManagementPermissionState() {
  let state;
  // The new permission name.
  try {
    ({ state } = await navigator.permissions.query({
      name: "window-management",
    }));
  } catch (err) {
    return `${err.name}: ${err.message}`;
  }
  return state;
}

document.querySelector("button").addEventListener("click", async () => {
  const state = await getWindowManagementPermissionState();
  document.querySelector("pre").textContent = state;
});

瀏覽器可以選擇在第一次嘗試使用任何新 API 方法時,動態顯示權限提示。請繼續閱讀以瞭解詳情。

window.screen.isExtended 屬性

如果想確認我的裝置是否連結了多個螢幕,我可以存取 window.screen.isExtended 屬性。其會傳回 truefalse。根據我的設定,它會傳回 true

window.screen.isExtended;
// Returns `true` or `false`.

getScreenDetails() 方法

現在,我知道目前設定為多螢幕設定,現在可以使用 Window.getScreenDetails() 取得第二個畫面的詳細資訊。呼叫此函式會顯示權限提示,詢問網站是否可開啟並在螢幕上放置視窗。這個函式會傳回承諾,該承諾會透過 ScreenDetailed 物件解析。在已連接 iPad 的 MacBook Pro 13 中,這會包含具有兩個 ScreenDetailed 物件的 screens 欄位:

await window.getScreenDetails();
/* Output from my MacBook Pro 13″ with the iPad attached:
{
  currentScreen: ScreenDetailed {left: 0, top: 0, isPrimary: true, isInternal: true, devicePixelRatio: 2, …}
  oncurrentscreenchange: null
  onscreenschange: null
  screens: [{
    // The MacBook Pro
    availHeight: 969
    availLeft: 0
    availTop: 25
    availWidth: 1680
    colorDepth: 30
    devicePixelRatio: 2
    height: 1050
    isExtended: true
    isInternal: true
    isPrimary: true
    label: "Built-in Retina Display"
    left: 0
    onchange: null
    orientation: ScreenOrientation {angle: 0, type: "landscape-primary", onchange: null}
    pixelDepth: 30
    top: 0
    width: 1680
  },
  {
    // The iPad
    availHeight: 999
    availLeft: 1680
    availTop: 25
    availWidth: 1366
    colorDepth: 24
    devicePixelRatio: 2
    height: 1024
    isExtended: true
    isInternal: false
    isPrimary: false
    label: "Sidecar Display (AirPlay)"
    left: 1680
    onchange: null
    orientation: ScreenOrientation {angle: 0, type: "landscape-primary", onchange: null}
    pixelDepth: 24
    top: 0
    width: 1366
  }]
}
*/

您可以在 screens 陣列中找到已連結畫面的相關資訊。請注意,iPad 的 left 值會從 1680 開始,這與內建螢幕的 width 完全相同。這可讓我準確判斷畫面在邏輯上排列的方式 (彼此相鄰、彼此重疊等等)。現在每個畫面也都有資料,可指出它是否為 isInternal 類型,以及是否為 isPrimary。請注意,內建畫面不一定是主畫面

currentScreen 欄位是與目前 window.screen 對應的使用中物件。跨螢幕視窗刊登位置或裝置變更時,物件會隨之更新。

screenschange 事件

目前唯一缺少的功能是偵測螢幕設定何時變更的方法。新事件 screenschange 正好能夠達成這個目標:每當螢幕全景圖修改時,就會觸發這個事件。(請注意,事件名稱中的「Screen」是複數)。這表示每當新畫面或現有畫面 (在「邊車」) 接上或未接上電源時,就會觸發事件。

請注意,您需要以非同步方式查詢新畫面的詳細資料,screenschange 事件本身不會提供這項資料。如要查詢螢幕詳細資料,請使用快取 Screens 介面中的使用中物件。

const screenDetails = await window.getScreenDetails();
let cachedScreensLength = screenDetails.screens.length;
screenDetails.addEventListener('screenschange', (event) => {
  if (screenDetails.screens.length !== cachedScreensLength) {
    console.log(
      `The screen count changed from ${cachedScreensLength} to ${screenDetails.screens.length}`,
    );
    cachedScreensLength = screenDetails.screens.length;
  }
});

currentscreenchange 事件

如果我只想變更目前畫面 (也就是使用中物件 currentScreen 的值),則可以監聽 currentscreenchange 事件。

const screenDetails = await window.getScreenDetails();
screenDetails.addEventListener('currentscreenchange', async (event) => {
  const details = screenDetails.currentScreen;
  console.log('The current screen has changed.', event, details);
});

change 事件

最後,如果我只想變更具體螢幕,可以監聽該畫面的 change 事件。

const firstScreen = (await window.getScreenDetails())[0];
firstScreen.addEventListener('change', async (event) => {
  console.log('The first screen has changed.', event, firstScreen);
});

新的全螢幕選項

目前,您可以使用重新命名的 requestFullScreen() 方法,要求元素以全螢幕模式顯示。此方法使用 options 參數,您可以在其中傳遞 FullscreenOptions。目前為止,其唯一屬性是 navigationUI。Window Management API 新增了 screen 屬性,可讓您決定要在哪個畫面啟動全螢幕檢視畫面。舉例來說,如果想將主畫面設為全螢幕:

try {
  const primaryScreen = (await getScreenDetails()).screens.filter((screen) => screen.isPrimary)[0];
  await document.body.requestFullscreen({ screen: primaryScreen });
} catch (err) {
  console.error(err.name, err.message);
}

聚酯纖維

您無法為 Window Management API 建立折線,但可以調整其形狀,以便只針對新的 API 編寫程式碼:

if (!('getScreenDetails' in window)) {
  // Returning a one-element array with the current screen,
  // noting that there might be more.
  window.getScreenDetails = async () => [window.screen];
  // Set to `false`, noting that this might be a lie.
  window.screen.isExtended = false;
}

API 的其他層面 (即各種螢幕變更事件和 FullscreenOptionsscreen 屬性) 絕不會被不支援瀏覽器個別觸發,也不會自動忽略。

操作示範

如果你像我一樣,密切留意各種加密貨幣的開發過程。(事實上,我其實並非因為我很喜歡這個星球,但為了閱讀本文,就先假裝自己知道了)。為了持續追蹤我擁有的加密貨幣,我開發了一款網頁應用程式,能夠在各種情況下觀看市場趨勢,例如在床上舒適地進行單一螢幕配置。

躺在床末的大型電視螢幕,部分人物的腿部可見。畫面上顯示的是假的加密貨幣交易中心。
放鬆自己,看市場。

不只是加密貨幣,市場隨時都可能生硬。發生這種情況時 我可以快速移到我的辦公桌上,開始設置多螢幕設定我只要點一下任何貨幣視窗,用全螢幕畫面 就能快速查看完整詳細資料以下是我在過去 YCY 血巴期間拍攝的近期相片。它吸引我完全沒有警覺,然後將我的手放在我的臉上。

這位作者將手拿著驚嚇的臉,望向假的加密貨幣交易平台。
小尼會看 YCY 血巴。

你可以利用下方的嵌入式示範來試玩,也可以檢查是否有故障的原始碼

安全性和權限

Chrome 團隊根據「控管強大的網路平台功能存取權」一文中定義的核心原則 (包括使用者控制項、資訊公開和人體工學) 設計及實作 Window Management API。Window Management API 會公開與裝置連線畫面相關的新資訊,進而增加使用者的指紋辨識途徑,尤其是擁有多個螢幕持續與其裝置連線的使用者。為緩解這項隱私權疑慮,暴露畫面屬性僅限於常見刊登位置用途所需的最低限度。網站必須取得使用者權限,才能取得多螢幕資訊,並將視窗放在其他畫面上。雖然 Chromium 會傳回詳細的畫面標籤,但瀏覽器可自由傳回較不的說明內容 (甚至是空白標籤)。

使用者控制項

使用者可以完全掌控是否要啟用這項設定。他們可以接受或拒絕權限提示,也能透過瀏覽器的網站資訊功能,撤銷先前授予的權限。

企業控管

Chrome Enterprise 使用者可以控制 Window Management API 的多個方面,相關說明請參閱「原子政策群組」設定的相關章節。

資訊公開

瀏覽器的網站資訊中是否會顯示授予 Window Management API 使用權限,使用者也可以透過 Permissions API 進行查詢。

權限持續性

瀏覽器會維持授予的權限。您可以透過瀏覽器的網站資訊撤銷權限。

意見回饋:

Chrome 團隊想瞭解您的 Window Management API 使用體驗。

告訴我們 API 設計

有什麼 API 功能不如您預期嗎?或者您需要實作提案的方法或屬性嗎?對於安全性模型有任何疑問或意見嗎?

  • 在對應的 GitHub 存放區上提交規格問題,或是將您的想法新增至現有問題。

回報導入問題

您在執行 Chrome 時發現錯誤了嗎?或者實作與規格不同?

  • 請前往 new.crbug.com 回報錯誤。請務必盡可能提供詳細的細節、簡單的重現操作說明,並在「Components」(元件) 方塊中輸入 Blink>Screen>MultiScreenGlitch 適合用來快速分享簡單快速的提案,

展現對 API 的支援

您打算使用 Window Management API 嗎?您的公開支援可協助 Chrome 團隊排定功能的優先順序,讓其他瀏覽器廠商瞭解這些功能對他們的支援程度有多重要。

實用連結

特別銘謝

Window Management API 規格是由 Victor CostanJoshua BellMike Wasserman 編輯。這個 API 是由 Mike WassermanAdrienne Walker 實作。本文由 Joe MedleyFrançois BeaufortKayce Basques 審查。感謝 Laura Torrent Puig 提供相片。