Zarządzanie kilkoma wyświetlaczami za pomocą interfejsu Window Management API

Uzyskuj informacje o połączonych wyświetlaczach i pozycjonuj okna względem tych wyświetlaczy.

Interfejs API zarządzania oknami

Interfejs Window Management API umożliwia wyliczenie wyświetlaczy podłączonych do komputera i rozmieszczanie okien na określonych ekranach.

Sugerowane przypadki użycia

Przykłady witryn, w których można używać tego interfejsu API:

  • Edytory grafiki obsługujące wiele okien Gimp mogą umieszczać różne narzędzia do edytowania w precyzyjnie rozmieszczonych oknach.
  • Wirtualne działy obrotu reklamami mogą pokazywać trendy rynkowe w wielu oknach, z których każdy może być wyświetlany w trybie pełnoekranowym.
  • Aplikacje do pokazów slajdów mogą wyświetlać notatki prelegenta na wewnętrznym ekranie głównym i prezentację na projektorze zewnętrznym.

Jak korzystać z interfejsu Window Management API

Problem

Działające od lat podejście do sterowania oknami, Window.open(), nie uwzględnia niestety dodatkowych ekranów. Niektóre aspekty tego interfejsu API, takie jak parametr windowFeatures DOMString, wydają się nieco przestarzałe, jednak na przestrzeni lat służą nam niesamowicie. Aby określić pozycję okna, możesz przekazać współrzędne jako left i top (lub odpowiednio screenX i screenY), a potem przekazać odpowiedni rozmiar jako width i height (lub odpowiednio innerWidth i innerHeight). Aby na przykład otworzyć okno 400 × 300 w odległości 50 pikseli od lewej i 50 pikseli od góry, możesz użyć tego kodu:

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

Informacje o bieżącym ekranie możesz uzyskać, sprawdzając właściwość window.screen, która zwraca obiekt Screen. Oto dane wyjściowe na moim MacBooku 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
*/

Tak jak większość ludzi z branży technologicznej musiałam dostosować się do nowej sytuacji zawodowej i skonfigurować prywatne biuro domowe. Mój wygląd jest taki na zdjęciu poniżej (jeśli chcesz, możesz przeczytać pełne informacje o mojej konfiguracji). iPad obok MacBooka jest podłączony do laptopa przez Sidecar, więc w razie potrzeby mogę szybko zmienić go w drugi ekran.

Ławka szkolna na 2 krzesłach. Na ławce szkolnej znajdują się pudełka na buty podtrzymujące laptopa i 2 iPady wokół niego.
Konfiguracja na wiele urządzeń.

Jeśli chcę wykorzystać większy ekran, mogę umieścić na drugim ekranie wyskakujące okienko z przykładowego kodu powyżej. Zrobię to w ten sposób:

popup.moveTo(2500, 50);

To może być przybliżone, ponieważ nie da się poznać wymiarów drugiego ekranu. Informacje z aplikacji window.screen obejmują tylko wbudowany ekran, a nie ekran iPada. Zgłoszona wartość width wbudowanego ekranu to 1680 piks., dlatego przejście do 2500 piks. może spowodować przesunięcie okna na iPada, bo wiem, że znajduje się on po prawej stronie mojego MacBooka. Jak to zrobić w praktyce? Okazuje się, że jest lepszy sposób niż zgadywanie. Jest to interfejs Window Management API.

Wykrywanie funkcji

Aby sprawdzić, czy interfejs Window Management API jest obsługiwany, użyj polecenia:

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

Uprawnienie window-management

Zanim będę mógł użyć interfejsu Window Management API, muszę poprosić użytkownika o pozwolenie. Zapytanie o uprawnienie window-management można wysłać za pomocą Permissions API, na przykład:

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

Gdy korzystasz z przeglądarek ze starą i nową nazwą uprawnienia, pamiętaj o użyciu kodu zabezpieczającego, gdy prosisz o uprawnienia, tak jak w przykładzie poniżej.

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;
});

Przeglądarka może wybrać dynamiczne wyświetlanie prośby o przyznanie uprawnień przy pierwszej próbie użycia dowolnej metody nowego interfejsu API. Czytaj dalej, aby dowiedzieć się więcej.

Właściwość window.screen.isExtended

Aby sprawdzić, czy z moim urządzeniem jest połączony więcej niż 1 ekran, otwieram właściwość window.screen.isExtended. Zwraca true lub false. W mojej konfiguracji zwraca wartość true.

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

Metoda getScreenDetails()

Skoro wiemy już, że bieżąca konfiguracja obejmuje wiele urządzeń, mogę więc uzyskać więcej informacji o drugim ekranie, korzystając z usługi Window.getScreenDetails(). Wywołanie tej funkcji spowoduje wyświetlenie prośby o przyznanie uprawnień z pytaniem, czy witryna może otwierać i umieszczać okna na moim ekranie. Funkcja zwraca obietnicę, która kończy się obiektem ScreenDetailed. Na moim MacBooku Pro 13 z połączonym iPadem zawiera pole screens z 2 obiektami ScreenDetailed:

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
  }]
}
*/

Informacje o połączonych ekranach są dostępne w tablicy screens. Zwróć uwagę, że wartość left w przypadku iPada zaczyna się od 1680, co odpowiada dokładnie width wbudowanemu wyświetlaczowi. Dzięki temu mogę określić, jak są logicznie rozmieszczone ekrany (obok siebie, jeden nad drugim itd.). Dostępne są też teraz dane dla każdego ekranu, które pokazują, czy jest to ekran isInternal, a czy isPrimary. Pamiętaj, że wbudowany ekran nie musi być ekranem głównym.

Pole currentScreen to aktywny obiekt odpowiadający bieżącej wartości window.screen. Obiekt jest aktualizowany w przypadku zmiany położenia okien na różnych ekranach lub zmian na urządzeniu.

Zdarzenie screenschange

Obecnie brakuje jedynie sposobu na wykrycie zmian w konfiguracji ekranu. Nowe zdarzenie – screenschange – działa dokładnie tak samo: uruchamia się przy każdej zmianie konstelacji ekranu. (Zwróć uwagę, że „ekrany” w nazwie zdarzenia mają liczbę mnogą). Oznacza to, że zdarzenie jest wywoływane za każdym razem, gdy nowy ekran lub istniejący ekran jest (fizycznie lub wirtualnie w przypadku Sidecar) podłączony lub odłączony.

Pamiętaj, że nowe szczegóły ekranu musisz wyszukiwać asynchronicznie – samo zdarzenie screenschange ich nie dostarcza. Aby wyszukać szczegóły ekranu, użyj aktywnego obiektu z interfejsu Screens zapisanego w pamięci podręcznej.

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;
  }
});

Zdarzenie currentscreenchange

Jeśli interesują mnie tylko zmiany na bieżącym ekranie (tj. wartość aktywnego obiektu currentScreen), mogę nasłuchiwać zdarzenia currentscreenchange.

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

Zdarzenie change

Jeśli interesują mnie tylko zmiany na konkretnym ekranie, mogę odsłuchać zdarzenie change z tego ekranu.

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

Nowe opcje pełnego ekranu

Do tej pory można było wymagać wyświetlania elementów w trybie pełnoekranowym za pomocą metody requestFullScreen() o odpowiedniej nazwie. Metoda wykorzystuje parametr options, za pomocą którego można przekazywać parametr FullscreenOptions. Na razie jego jedyną usługą jest navigationUI. Interfejs Window Management API dodaje nową właściwość screen, która pozwala określić, na którym ekranie rozpocząć widok pełnoekranowy. Jeśli na przykład chcesz ustawić pełny ekran głównego ekranu:

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);
}

Włókno poliestrowe

Interfejsu Window Management API nie można wypełnić polyfill, ale możesz podłożyć jego kształt, aby kodować wyłącznie na podstawie nowego interfejsu 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;
}

Inne aspekty interfejsu API, czyli różne zdarzenia zmiany ekranu i właściwość screen obiektu FullscreenOptions, nie są po prostu uruchamiane lub są pomijane automatycznie przez nieobsługiwane przeglądarki.

Pokaz

Jeśli podobnie jak ja, uważnie obserwuję rozwój różnych kryptowalut. (W rzeczywistości bardzo nie lubię tej planety, ale w kontekście tego artykułu zakładam, że tak. Aby śledzić posiadane kryptowaluty, opracowałem aplikację internetową, która pozwala mi obserwować rynki w każdej sytuacji, np. gdy nie śpię w łóżku, gdzie mam przydatną konfigurację na jednym ekranie.

Ogromny ekran telewizora na końcu łóżka z częściowo widocznymi nogami autora. Na ekranie przedstawia fałszywą platformę do handlu kryptowalutami.
Odprężenie i obserwacja rynków.

Jeśli chodzi o kryptowaluty, rynki mogą w każdej chwili stać się gorące. Jeśli tak się stanie, mogę szybko przejść do biurka i zainstalować urządzenie na wiele urządzeń. Mogę kliknąć okno dowolnej waluty, aby szybko zobaczyć szczegóły w widoku pełnoekranowym na drugim ekranie. Poniżej to moje ostatnie zdjęcie zrobione podczas ostatniej kąpieli YCY. Zupełnie odbiegłam od siebie i zostałam z rękami na twarzy.

Autor z dłońmi na przerażających twarzy wpatruje się w szafkę handlową z fałszywymi kryptowalutami.
Panika, świadkiem krwawej kąpieliska YCY.

Możesz pobawić się prezentacją umieszczoną poniżej lub zobaczyć jej kod źródłowy w razie usterek.

Zabezpieczenia i uprawnienia

Zespół Chrome zaprojektował i wdrożył interfejs Window Management API zgodnie z podstawowymi zasadami określonymi w artykule o kontrolowaniu dostępu do zaawansowanych funkcji platformy internetowej, takimi jak kontrola użytkownika, przejrzystość i ergonomia. Interfejs Window Management API udostępnia nowe informacje o ekranach połączonych z urządzeniem, co zwiększa powierzchnię odcisków palców użytkowników, zwłaszcza tych, którzy mają wiele ekranów stale połączonych z urządzeniami. Aby zminimalizować ryzyko naruszenia prywatności, ograniczyliśmy właściwości ujawnionych ekranów do minimum potrzebnego w typowych przypadkach użycia miejsc docelowych. Aby witryny mogły wyświetlać informacje o różnych ekranach i rozmieszczać okna na innych urządzeniach, uprawnienia użytkownika są wymagane. Chromium zwraca szczegółowe etykiety ekranu, ale przeglądarki mogą zwracać mniej opisowe (lub nawet puste etykiety).

Kontrola użytkowników

Użytkownik ma pełną kontrolę nad tym, jakie dane są widoczne w jego konfiguracji. Mogą zaakceptować lub odrzucić prośbę o uprawnienia, a także cofnąć wcześniej udzielone uprawnienia w sekcji informacji o witrynie w przeglądarce.

Kontrola firmy

Użytkownicy Chrome Enterprise mogą kontrolować kilka aspektów interfejsu Window Management API, jak opisano w odpowiedniej sekcji ustawień Atomic Policy Groups (Atomowe grupy zasad).

Przejrzystość

Informacja o tym, czy uprawnienie do korzystania z interfejsu Window Management API zostało przyznane, jest widoczne w informacjach o witrynie przeglądarki i można do niego wysyłać zapytania przy użyciu interfejsu Permissions API.

Trwałość uprawnień

Przeglądarka będzie nadal używała przyznanych uprawnień. Te uprawnienia można cofnąć, korzystając z informacji o witrynie w przeglądarce.

Prześlij opinię

Zespół Chrome chce poznać Twoją opinię na temat interfejsu Window Management API.

Opowiedz nam o projekcie interfejsu API

Czy interfejs API nie działa zgodnie z oczekiwaniami? A może brakuje metod lub właściwości, które potrzebujesz do realizacji swojego pomysłu? Masz pytanie lub komentarz na temat modelu bezpieczeństwa?

  • Zgłoś problem ze specyfikacją w odpowiednim repozytorium GitHub lub dodaj swoje uwagi do istniejącego problemu.

Zgłoś problem z implementacją

Czy wystąpił błąd związany z implementacją przeglądarki Chrome? A może implementacja różni się od specyfikacji?

  • Zgłoś błąd na stronie new.crbug.com. Podaj jak najwięcej szczegółów, proste instrukcje odtworzenia i wpisz Blink>Screen>MultiScreen w polu Komponenty. Usterki to świetny sposób na udostępnianie szybkich i łatwych replik.

Pokaż obsługę interfejsu API

Czy zamierzasz użyć interfejsu Window Management API? Twoja publiczna pomoc pomaga zespołowi Chrome priorytetowo traktować funkcje i pokazuje innym dostawcom przeglądarek, jak ważne jest ich wsparcie.

  • W wątku WICG Discourse napisz, jak zamierzasz korzystać z tego narzędzia.
  • Wyślij tweeta na adres @ChromiumDev, używając hashtagu #WindowManagement, i daj nam znać, gdzie i do czego go używasz.
  • Poproś innych dostawców przeglądarek o wdrożenie interfejsu API.

Przydatne linki

Podziękowania

Specyfikację interfejsu Window Management API edytowali Victor Costan, Joshua Bell i Mike Wasserman. Interfejs API wdrożyli Mike Wasserman i Adrienne Walker. Ten artykuł napisali Joe Medley, François Beaufort i Kayce Basques. Dziękujemy Laurze Torrent Puig za zdjęcia.