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

uzyskiwać informacje o podłączonych wyświetlaczach i pozycjonować okna względem nich;

Window Management API

Interfejs Window Management API umożliwia wyliczanie wyświetlaczy podłączonych do urządzenia i umieszczanie okien na określonych ekranach.

Sugerowane przypadki użycia

Przykłady witryn, które mogą korzystać z tego interfejsu API:

  • Edytory graficzne z wieloma oknami, takie jak GIMP, mogą umieszczać różne narzędzia do edycji w dokładnie określonych oknach.
  • Wirtualne platformy handlowe mogą wyświetlać trendy rynkowe w wielu oknach, z których każde można wyświetlić w trybie pełnoekranowym.
  • Aplikacje do prezentacji mogą wyświetlać notatki prelegenta na wewnętrznym ekranie głównym, a prezentację na zewnętrznym projektorze.

Jak korzystać z interfejsu Window Management API

Problem

Sprawdzony sposób sterowania oknami,Window.open() niestety, nie uwzględnia dodatkowych ekranów. Chociaż niektóre aspekty tego interfejsu API wydają się nieco przestarzałe, np. parametr windowFeatures DOMString, przez lata dobrze nam służył. Aby określić położenie okna, możesz przekazać współrzędne jako lefttop (lub odpowiednio screenXscreenY), a żądany rozmiar jako widthheight (lub odpowiednio innerWidthinnerHeight). Jeśli na przykład chcesz otworzyć okno o wymiarach 400 × 300 pikseli w odległości 50 pikseli od lewej i 50 pikseli od górnej krawędzi, 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
*/

Jak większość osób pracujących w branży technologicznej musiałem dostosować się do nowej rzeczywistości i urządzić sobie domowe biuro. Mój wygląda tak jak na zdjęciu poniżej (jeśli chcesz, możesz przeczytać szczegółowe informacje o mojej konfiguracji). iPad obok MacBooka jest połączony z laptopem za pomocą funkcji Sidecar, więc w razie potrzeby mogę szybko przekształcić iPada w drugi ekran.

Ławka szkolna na 2 krzesłach. Na ławce szkolnej stoją pudełka po butach, na których opiera się laptop. Wokół niego leżą 2 iPady.
Konfiguracja wielu ekranów.

Jeśli chcę skorzystać z większego ekranu, mogę przenieść wyskakujące okienko z przykładowego kodu powyżej na drugi ekran. Robię to tak:

popup.moveTo(2500, 50);

To tylko przybliżone oszacowanie, ponieważ nie ma możliwości poznania wymiarów drugiego ekranu. Informacje z window.screen obejmują tylko wbudowany ekran, a nie ekran iPada. Zgłoszona width wartość wbudowanego ekranu wynosiła 1680 pikseli, więc przejście na 2500 pikseli może przesunąć okno na iPada, ponieważ wiem, że znajduje się on po prawej stronie mojego MacBooka. Jak mogę to zrobić w przypadku ogólnym? Okazuje się, że istnieje lepszy sposób niż zgadywanie. Jest to interfejs Window Management API.

Wykrywanie cech

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

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

Uprawnienie window-management

Zanim będę mógł używać interfejsu Window Management API, muszę poprosić użytkownika o zgodę na to. O uprawnienie window-management można zapytać za pomocą interfejsu Permissions API w ten sposób:

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

Dopóki przeglądarki z nową i starą nazwą uprawnień są w użyciu, podczas przesyłania prośby o uprawnienia używaj kodu defensywnego, jak w poniższym przykładzie.

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 dynamicznie wyświetlać prośbę o przyznanie uprawnień przy pierwszej próbie użycia dowolnej metody nowego interfejsu API. Czytaj dalej, aby dowiedzieć się więcej.

Usługa window.screen.isExtended

Aby sprawdzić, czy do mojego urządzenia jest podłączony więcej niż 1 ekran, otwieram właściwość window.screen.isExtended. Zwraca wartość true lub false. W moim przypadku zwraca wartość true.

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

Metoda getScreenDetails()

Teraz, gdy wiem, że bieżąca konfiguracja obejmuje wiele ekranów, mogę uzyskać więcej informacji o drugim ekranie za pomocą Window.getScreenDetails(). Wywołanie tej funkcji spowoduje wyświetlenie prośby o uprawnienia, w której użytkownik zostanie zapytany, czy witryna może otwierać i umieszczać okna na jego ekranie. Funkcja zwraca obietnicę, która jest spełniana przez obiekt ScreenDetailed. Na moim MacBooku Pro 13 z podłączonym iPadem obejmuje to 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 na iPadzie zaczyna się od 1680, czyli dokładnie od width wbudowanego wyświetlacza. Dzięki temu mogę dokładnie określić, jak ekrany są rozmieszczone logicznie (obok siebie, jeden na drugim itp.). Dostępne są też dane dotyczące każdego ekranu, które pokazują, czy jest on isInternal, czy isPrimary. Pamiętaj, że wbudowany ekran nie musi być ekranem głównym.

Pole currentScreen to aktywny obiekt odpowiadający bieżącemu window.screen. Obiekt jest aktualizowany w przypadku umieszczeń w oknach na różnych ekranach lub zmian urządzeń.

Zdarzenie screenschange

Brakuje tylko sposobu wykrywania zmian w konfiguracji ekranu. Nowe zdarzenie, screenschange, robi dokładnie to: uruchamia się za każdym razem, gdy zmienia się konfiguracja ekranu. (Zwróć uwagę, że w nazwie zdarzenia „screens” jest w liczbie mnogiej). Oznacza to, że zdarzenie jest wywoływane za każdym razem, gdy nowy ekran lub istniejący ekran (fizycznie lub wirtualnie w przypadku Sidecar) jest podłączany lub odłączany.

Pamiętaj, że szczegóły nowego ekranu musisz wyszukać asynchronicznie. Samo zdarzenie screenschange nie zawiera tych danych. Aby sprawdzić szczegóły ekranu, użyj obiektu na żywo z interfejsu Screensw 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 (czyli wartość obiektu na żywo 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ę nasłuchiwać zdarzenia change 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 poprosić o wyświetlenie elementów w trybie pełnoekranowym za pomocą metody o odpowiedniej nazwie requestFullScreen(). Metoda przyjmuje parametr options, w którym możesz przekazać FullscreenOptions. Do tej pory jedyną właściwością tej usługi była navigationUI. Interfejs Window Management API dodaje nową właściwość screen, która pozwala określić, na którym ekranie ma się rozpocząć widok pełnoekranowy. Jeśli na przykład chcesz wyświetlić ekran główny na pełnym ekranie:

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

Watolina poliestrowa

Nie można zastosować polyfillu do interfejsu Window Management API, ale możesz zastosować shim do jego kształtu, aby pisać kod wyłącznie pod kątem 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;
}

Pozostałe aspekty interfejsu API, czyli różne zdarzenia zmiany ekranu i właściwość screen elementu FullscreenOptions, nigdy nie będą wywoływane lub będą cicho ignorowane przez przeglądarki, które nie obsługują tej funkcji.

Prezentacja

Jeśli jesteś podobny do mnie, uważnie śledzisz rozwój różnych kryptowalut. (W rzeczywistości bardzo mi na niej zależy, ale na potrzeby tego artykułu załóżmy, że tak nie jest). Aby śledzić posiadane kryptowaluty, stworzyłem aplikację internetową, która umożliwia mi obserwowanie rynków w każdej sytuacji, np. w wygodnym łóżku, gdzie mam przyzwoity zestaw z jednym ekranem.

Ogromny ekran telewizora na końcu łóżka, z którego wystają nogi autora. Na ekranie znajduje się fałszywe stanowisko handlowe kryptowalut.
Relaks i obserwowanie rynków.

Ponieważ chodzi o kryptowaluty, rynki mogą w każdej chwili stać się niestabilne. W takiej sytuacji mogę szybko przejść do biurka, gdzie mam konfigurację z wieloma ekranami. Mogę kliknąć okno dowolnej waluty i szybko wyświetlić wszystkie szczegóły w widoku pełnoekranowym na przeciwnym ekranie. Poniżej znajduje się moje ostatnie zdjęcie zrobione podczas ostatniej rzezi YCY. Zaskoczyło mnie to całkowicie i zostałem z rękami na twarzy.

Autor z rękami na twarzy, która wyraża panikę, wpatruje się w fałszywe stanowisko do handlu kryptowalutami.
Panikuje, widząc masakrę YCY.

Możesz wypróbować wersję demonstracyjną poniżej lub zobaczyć jej kod źródłowy na GitHubie.

Zabezpieczenia i uprawnienia

Zespół Chrome zaprojektował i wdrożył interfejs Window Management API zgodnie z głównymi zasadami określonymi w dokumencie Controlling Access to Powerful Web Platform Features, w tym z zasadami dotyczącymi kontroli użytkownika, przejrzystości i ergonomii. Interfejs Window Management API udostępnia nowe informacje o ekranach podłączonych do urządzenia, co zwiększa obszar odcisków cyfrowych użytkowników, zwłaszcza tych, którzy mają wiele ekranów stale podłączonych do swoich urządzeń. Aby ograniczyć to zagrożenie dla prywatności, udostępniane właściwości ekranu są ograniczone do minimum niezbędnego w przypadku typowych miejsc docelowych. Aby witryny mogły uzyskiwać informacje o wielu ekranach i umieszczać okna na innych ekranach, wymagana jest zgoda użytkownika. Chromium zwraca szczegółowe etykiety ekranu, ale inne przeglądarki mogą zwracać mniej opisowe etykiety (lub nawet puste).

Kontrola sprawowana przez użytkowników

Użytkownik ma pełną kontrolę nad tym, jak jego konfiguracja jest widoczna. Mogą zaakceptować lub odrzucić prośbę o uprawnienia oraz cofnąć wcześniej przyznane uprawnienia za pomocą funkcji informacji o witrynie w przeglądarce.

Kontrola w firmie

Użytkownicy Chrome Enterprise mogą kontrolować różne aspekty interfejsu Window Management API, jak opisano w odpowiedniej sekcji ustawień atomowych grup zasad.

Przejrzystość

Informacja o tym, czy przyznano uprawnienia do korzystania z interfejsu Window Management API, jest widoczna w informacjach o witrynie w przeglądarce i można ją też uzyskać za pomocą interfejsu Permissions API.

Trwałość uprawnień

Przeglądarka zachowuje przyznane uprawnienia. Zezwolenie można cofnąć w informacjach o witrynie w przeglądarce.

Prześlij opinię

Zespół Chrome chce poznać Twoje wrażenia związane z korzystaniem z interfejsu Window Management API.

Opisz projekt interfejsu API

Czy coś w API nie działa tak, jak oczekujesz? Czy brakuje metod lub właściwości, które są potrzebne do realizacji Twojego pomysłu? Masz pytania lub uwagi dotyczące modelu zabezpieczeń?

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

Zgłaszanie problemu z implementacją

Czy w implementacji Chrome występuje błąd? A może implementacja różni się od specyfikacji?

Wyrażanie poparcia dla interfejsu API

Czy planujesz używać interfejsu Window Management API? Twoje publiczne wsparcie pomaga zespołowi Chrome ustalać priorytety funkcji i pokazuje innym dostawcom przeglądarek, jak ważne jest ich obsługiwanie.

Przydatne linki

Podziękowania

Specyfikację interfejsu Window Management API edytowali Victor Costan, Joshua BellMike Wasserman. Interfejs API został wdrożony przez Mike’a Wassermana i Adrienne Walker. Ten artykuł został sprawdzony przez Joego Medleya, François Beauforta i Kayce Basques. Dziękujemy Laurze Torrent Puig za zdjęcia.