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

uzyskiwać informacje o podłączonych wyświetlaczach i oknach w stosunku do tych wyświetlaczy;

Interfejs API zarządzania oknami

Interfejs Window Management API umożliwia zliczanie wyświetlaczy połączonych z urządzeniem oraz umieszczanie okien na określonych ekranach.

Sugerowane zastosowania

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

  • Edytory grafiki z wieloma oknami w stylu Gimp mogą umieszczać różne narzędzia do edycji w precyzyjnie rozmieszczonych oknach.
  • Wirtualne biura transakcyjne mogą wyświetlać trendy rynkowe w wielu oknach, z których każde można wyświetlić w trybie pełnoekranowym.
  • Aplikacje do pokazów slajdów mogą wyświetlać notatki na wewnętrznym ekranie głównym, a prezentację na zewnętrznym projektorze.

Jak korzystać z interfejsu Window Management API

Problem

Działające od lat podejście do 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, to przez lata dobrze nam służył. Aby określić położenie okna, możesz przekazać współrzędne jako lefttop (odpowiednio screenXscreenY) oraz przekazać żądany rozmiar jako widthheight (odpowiednio innerWidthinnerHeight). Jeśli na przykład chcesz otworzyć okno o wymiarach 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ć, korzystając z właściwości 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
*/

Podobnie jak większość osób pracujących w branży technologicznej musiałam dostosować się do nowej rzeczywistości i urządzić sobie prywatny gabinet w domu. Mój wygląda tak jak na zdjęciu poniżej (jeśli chcesz, możesz przeczytać szczegóły dotyczące mojego urządzenia). iPad obok MacBooka jest połączony z laptopem przez Sidecar, więc w razie potrzeby mogę szybko przekształcić iPada w drugi ekran.

Ławka szkolna na 2 krzesłach. Na szkolnej ławce znajdują się pudełka po butach, na których stoi laptop, a wokół niego 2 iPady.
Konfiguracja wielu ekranów.

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

popup.moveTo(2500, 50);

To przybliżone przypuszczenie, bo nie można poznać wymiarów drugiego ekranu. Informacje z window.screen dotyczą tylko wbudowanego ekranu, a nie ekranu iPada. Raportowany width ekranu wbudowanego urządzenia to 1680 pikseli, więc przeniesienie do 2500 pikseli może pomóc w przesunięciu okna na iPada, ponieważ wiem, że znajduje się on po prawej stronie mojego MacBooka. Jak mogę to zrobić w ogóle? 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:

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

Uprawnienie window-management

Aby móc używać interfejsu Window Management API, muszę poprosić użytkownika o zgodę. Zapytanie window-management można wysłać za pomocą Permissions API w następujący sposób:

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

Chociaż używane są przeglądarki ze starą i nową nazwą uprawnienia, przy prośbie o uprawnienia należy używać kodu obronnego, tak 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 wyświetlić prośbę o uprawnienia dynamicznie przy pierwszej próbie użycia dowolnej metody nowego interfejsu API. Aby dowiedzieć się więcej, czytaj dalej.

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

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

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

Metoda getScreenDetails()

Teraz, gdy wiem, że bieżąca konfiguracja to konfiguracja wieloekranowa, mogę uzyskać więcej informacji o drugim ekranie, używając Window.getScreenDetails(). Po wywołaniu tej funkcji wyświetli się prośba o uprawnienia, w której pytam, czy witryna może otwierać i umieszczać okna na moim ekranie. Funkcja zwraca obietnicę, która zwraca obiekt 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 na iPadzie zaczyna się od 1680, co odpowiada dokładnie width wbudowanego wyświetlacza. Pozwala mi to określić, jak dokładnie ekrany są logicznie rozmieszczone (obok siebie, jeden na drugim itp.). Dla każdego ekranu dostępne są teraz dane, które pokazują, czy ma on typ isInternal, a także czy ma stan isPrimary. Pamiętaj, że wbudowany ekran niekoniecznie jest ekranem głównym.

Pole currentScreen jest aktywnym obiektem odpowiadającym bieżącemu obiektowi window.screen. Obiekt jest aktualizowany w przypadku umieszczenia okna na różnych ekranach lub zmiany urządzenia.

Zdarzenie screenschange

Jedyną rzeczą, której brakuje, jest sposób wykrywania zmian w układzie ekranu. Nowe zdarzenie screenschange działa właśnie w ten sposób: jest wywoływane, gdy nastąpi zmiana w konfiguracji ekranu. (Zwróć uwagę, że w nazwie zdarzenia „screens” występuje w formie liczby mnogiej.) Oznacza to, że zdarzenie jest wywoływane za każdym razem, gdy podłączysz lub odłączysz nowy lub istniejący ekran (fizycznie lub wirtualnie w przypadku aplikacji Sidecar).

Pamiętaj, że informacje o nowym ekranie musisz wyszukiwać asynchronicznie, ponieważ zdarzenie screenschange samo w sobie nie zawiera tych danych. Aby sprawdzić szczegóły ekranu, użyj obiektu na żywo z interfejsu z pamięci podręcznejScreens.

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 żywocurrentScreen), 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 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 wysyłać prośby o wyświetlanie elementów w trybie pełnoekranowym za pomocą metody requestFullScreen() o odpowiedniej nazwie. Metoda przyjmuje parametr options, do którego możesz przekazać wartość FullscreenOptions. Do tej pory jego jedyną usługą była usługa navigationUI. Interfejs Window Management API dodaje nową właściwość screen, która pozwala określić, na którym ekranie chcesz włączyć 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

Nie można polyfillować interfejsu Window Management API, ale możesz użyć szablonu, aby móc kodować wyłącznie na potrzeby 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 byłyby wywoływane w nieobsługiwanych przeglądarkach lub byłyby ignorowane.

Prezentacja

Tak jak ja, uważnie przyglądacie się rozwojowi różnych kryptowalut. (W rzeczywistości nie chcę tego robić, ponieważ kocham tę planetę, ale na potrzeby tego artykułu załóżmy, że tak). Aby śledzić swoje kryptowaluty, stworzyłem aplikację internetową, która pozwala mi obserwować rynki w każdej sytuacji życiowej, np. z wygody łóżka, gdzie mam przyzwoite ustawienie z jednym ekranem.

Duży telewizor na końcu łóżka. Widoczne są nogi autora. Na ekranie widać fałszywy dział handlu kryptowalutami.
Relaks i obserwowanie rynków.

Jeśli chodzi o kryptowaluty, na rynku panuje gwałtowny ruch w każdej chwili. W takiej sytuacji mogę szybko przeskoczyć do biurka, gdzie mam włączoną obsługę wielu ekranów. Mogę kliknąć okno dowolnej waluty i szybko wyświetlić pełne szczegóły w widoku pełnoekranowym na drugim ekranie. Poniżej znajduje się moje zdjęcie zrobione podczas ostatniego krwawawego pochodu YCY. Złapał mnie całkowicie nieprzygotowanego i umieścił moje ręce na twarzy.

Autor z rękami na twarzy w panice wpatrujący się w fałszywy pulpit do handlu kryptowalutami.
Panicky, świadek krwawej jatki w YCY.

Możesz wypróbować demo umieszczone poniżej lub sprawdzić kod źródłowy na stronie glitch.

Bezpieczeństwo i uprawnienia

Zespół Chrome zaprojektował i wdrożył interfejs Window Management API zgodnie z podstawowymi zasadami opisanymi w artykule Kontrola 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ę odcisku palca użytkowników, zwłaszcza tych, którzy mają wiele ekranów stale połączonych ze swoimi urządzeniami. Aby rozwiązać ten problem, ograniczyliśmy liczbę wyświetlanych właściwości ekranu do minimum potrzebnego w najczęstszych przypadkach użycia. Aby witryny mogły uzyskiwać informacje o wielu ekranach i rozmieszczać okna na innych ekranach, muszą mieć na to zgodę użytkownika. Podczas gdy Chromium zwraca szczegółowe etykiety ekranu, inne przeglądarki mogą zwracać mniej opisowe (lub nawet puste) etykiety.

Kontrola użytkowników

Użytkownik ma pełną kontrolę nad ekspozycją swojego urządzenia. Mogą zaakceptować lub odrzucić prośbę o zgodę i cofnąć wcześniej udzieloną zgodę za pomocą funkcji informacji o witrynie w przeglądarce.

Kontrola firmowa

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

Przejrzystość

Fakt przyznania uprawnień do korzystania z interfejsu Window Management API jest ujawniany w informacjach o witrynie w przeglądarce. Można też wysyłać do niego zapytania za pomocą interfejsu Permissions API.

Trwałość uprawnień

Przeglądarka zachowuje przyznane uprawnienia. Użytkownik może cofnąć zgodę w informacjach o witrynie przeglądarki.

Prześlij opinię

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

Prześlij informacje o projektowaniu interfejsu API

Czy jest coś, co nie działa w interfejsie API zgodnie z oczekiwaniami? A może brakuje metod lub właściwości, których potrzebujesz do wdrożenia swojego pomysłu? Masz pytania lub uwagi dotyczące modelu bezpieczeństwa?

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

Zgłaszanie problemów z implementacją

Czy znalazłeś/znalazłaś błąd w implementacji 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 odtwarzania błędu i wpisz Blink>Screen>MultiScreen w polu Components. Usługa Glitch świetnie nadaje się do szybkiego i łatwego udostępniania poprawek.

Pokaż wsparcie dla interfejsu API

Czy planujesz skorzystać z interfejsu Window Management API? Twoja publiczna pomoc pomaga zespołowi Chrome ustalać priorytety funkcji i pokazuje innym dostawcom przeglądarek, jak ważne jest wspieranie tych funkcji.

Przydatne linki

Podziękowania

Specyfikację interfejsu Window Management API opracowali Victor Costan, Joshua Bell i Mike Wasserman. Interfejs API został wdrożony przez Mike’a WassermanaAdrienne Walker. Ten artykuł został sprawdzony przez Joe Medley, François Beaufort i Kayce Basques. Dziękujemy Laurze Torrent Puig za zdjęcia.