Window Management API で複数のディスプレイを管理する

接続されているディスプレイに関する情報を取得し、それらのディスプレイを基準にウィンドウを配置します。

公開日: 2020 年 9 月 14 日

ウィンドウ管理 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
*/

テクノロジー業界で働く多くの人と同じように、私も 2020 年の仕事の現実に対応し、自宅に個人的なホームオフィスを設置する必要がありました。私の場合は写真のようになっています(興味のある方は、私の設定の詳細をご覧ください)。MacBook の横にある iPad は Sidecar でノートパソコンに接続されているため、必要に応じて iPad をすぐにセカンド スクリーンに切り替えることができます。

2 つの椅子の上に置かれた学校のベンチ。学校のベンチの上に、ノートパソコンを支える靴箱と、その周りに 2 台の iPad が置かれている。
マルチスクリーン設定。

大きな画面を活用したい場合は、コードサンプルのポップアップを 2 つ目の画面に表示できます。私は次のようにします。

popup.moveTo(2500, 50);

これは、2 つ目の画面のサイズを把握する方法がないため、おおよその推測です。window.screen の情報は、内蔵ディスプレイのみを対象としており、iPad のディスプレイは対象としていません。内蔵ディスプレイの報告された width1680 ピクセルだったので、2500 ピクセルに移動すると、iPad にウィンドウを移動できる可能性があります。なぜなら、iPad は MacBook の右側にあることがわかっているからです。一般的なケースでこれを行うにはどうすればよいですか?推測するよりも良い方法があります。それが Window Management API です。

特徴検出

Window Management API がサポートされているかどうかを確認するには、次のコードを使用します。

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

window-management 権限

Window Management API を使用する前に、ユーザーにその権限をリクエストする必要があります。window-management 権限は、次のように Permissions API でクエリできます。

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 プロパティにアクセスします。true または false を返します。私の設定では、true が返されます。

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

getScreenDetails() メソッド

現在の設定がマルチスクリーンであることがわかったので、Window.getScreenDetails() を使用して 2 つ目の画面に関する詳細情報を取得できます。この関数を呼び出すと、サイトが画面上にウィンドウを開いて配置することを許可するかどうかを尋ねる権限プロンプトが表示されます。この関数は、ScreenDetailed オブジェクトで解決される Promise を返します。iPad が接続された MacBook Pro 13 の場合、これには 2 つの 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 は、まさにその処理を行います。画面のコンステレーションが変更されるたびに発生します。(イベント名では「screens」が複数形になっていることに注意してください)。つまり、新しい画面または既存の画面が(Sidecar の場合は物理的または仮想的に)接続または切断されるたびに、イベントが発火します。

新しい画面の詳細を非同期で検索する必要があります。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() メソッドを使用して、要素を全画面表示で表示するようリクエストできました。このメソッドは、FullscreenOptions を渡すことができる options パラメータを取ります。これまでのところ、このプロパティは 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 = as>ync () = [window.screen];
  // Set to `false`, noting that this might be a lie.
  window.screen.isExtended = false;
}

API のその他の側面、つまりさまざまな画面変更イベントと FullscreenOptionsscreen プロパティは、対応していないブラウザではそれぞれ発火しないか、無視されます。

デモ

さまざまな暗号通貨の開発を注視している場合は、アプリケーションのシングル スクリーン設定で、ベッドに寝転びながら市場をモニタリングできます。(私はこの地球を愛しているので、そんなことは絶対にしませんが、この記事では、私がそうしたと仮定します)。

ベッドの端に巨大なテレビ画面があり、著者の脚が一部見えている。画面には、偽の暗号通貨取引デスクが表示されています。
市場を眺めながらリラックス。

暗号通貨は市場が常に変動するため、このような場合は、マルチスクリーン設定のデスクにすぐに移動できます。通貨のウィンドウをクリックすると、反対側の画面に全画面表示で詳細がすぐに表示されます。これは、前回の YCY の大暴落のときに撮影された私の最近の写真です。完全に不意を突かれ、顔に手を当ててしまいました。

YCY の大暴落を目の当たりにしてパニックに陥った私。

デモを試すか、GitHub でソースコードをご覧ください。

セキュリティと権限

Chrome チームは、強力なウェブ プラットフォーム機能へのアクセスを制御するで定義されているユーザー制御、透明性、人間工学などの基本原則を使用して、Window Management API を設計、実装しました。Window Management API は、デバイスに接続された画面に関する新しい情報を公開し、ユーザー、特にデバイスに複数の画面を常に接続しているユーザーのフィンガープリント サーフェスを拡大します。このプライバシーに関する懸念を軽減するため、公開される画面プロパティは、一般的なプレースメントのユースケースに必要な最小限の範囲に限定されています。

サイトがマルチスクリーン情報を取得して他の画面にウィンドウを配置するには、ユーザーの権限が必要です。Chromium は詳細な画面ラベルを返しますが、ブラウザは説明の少ないラベル(または空のラベル)を自由に返すことができます。

ユーザー コントロール

ユーザーはセットアップの公開範囲を完全に制御できます。ユーザーは権限プロンプトを承認または拒否できます。また、ブラウザのサイト情報機能を使用して、以前に付与された権限を取り消すこともできます。

エンタープライズ管理

Chrome Enterprise ユーザーは、アトミック ポリシー グループの設定の関連セクションで説明されているように、Window Management API のいくつかの側面を制御できます。

透明性

Window Management API の使用権限が付与されているかどうかは、ブラウザのサイト情報で確認できます。また、Permissions API でクエリすることもできます。

権限の永続性

ブラウザは権限付与を保持します。この権限は、ブラウザのサイト情報から取り消すことができます。

フィードバック

API について、想定どおりに動作しない点はありますか?アイデアを実装するために必要なメソッドやプロパティが不足している場合はどうすればよいですか?セキュリティ モデルについて質問やコメントがある場合

API のサポートを表示する

Window Management API を使用する予定はありますか?公開サポートは、Chrome チームが機能の優先順位を決定するのに役立ち、他のブラウザ ベンダーにサポートの重要性を示すことができます。

  • WICG Discourse スレッドで、どのように使用する予定かを共有してください。
  • ハッシュタグ #WindowManagement を使用して @ChromiumDev にツイートし、どこでどのように使用しているかをお知らせください。
  • 他のブラウザ ベンダーに API の実装を依頼します。

リソース

謝辞

Window Management API 仕様は、Victor CostanJoshua BellMike Wasserman によって編集されました。この API は Mike WassermanAdrienne Walker によって実装されました。このドキュメントは、Joe MedleyFrançois BeaufortKayce Basques によってレビューされました。写真提供: Laura Torrent Puig