Получите информацию о подключенных дисплеях и расположите окна относительно этих дисплеев.
Опубликовано: 14 сентября 2020 г.
API управления окнами
API управления окнами позволяет перечислять дисплеи, подключенные к вашему компьютеру, и размещать окна на определенных экранах.
Предлагаемые варианты использования
Примеры сайтов, которые могут использовать этот API, включают:
- Многооконные графические редакторы, подобные Gimp, позволяют размещать различные инструменты редактирования в точно расположенных окнах.
- Виртуальные торговые площадки могут отображать рыночные тренды в нескольких окнах, любое из которых можно просматривать в полноэкранном режиме.
- Приложения для создания слайд-шоу могут отображать заметки докладчика на основном внутреннем экране, а презентацию — на внешнем проекторе.
Как использовать API управления окнами
Проверенный временем подход к управлению окнами, Window.open() , к сожалению, не учитывает наличие дополнительных экранов. Хотя некоторые аспекты этого API кажутся несколько архаичными, например, параметр DOMString windowFeatures , он, тем не менее, хорошо служил нам на протяжении многих лет. Чтобы указать положение окна, вы можете передать координаты как left и top (или screenX и screenY соответственно) и передать желаемый размер как width и height (или innerWidth и innerHeight соответственно). Например, чтобы открыть окно размером 400×300 пикселей на расстоянии 50 пикселей от левого края и 50 пикселей от верхнего края, вы можете использовать следующий код:
const popup = window.open(
'https://example.com/',
'My Popup',
'left=50,top=50,width=400,height=300',
);
Информацию о текущем экране можно получить, обратившись к свойству window.screen , которое возвращает объект 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 году и обустроить свой домашний офис. Мой выглядит так, как на фото (если вам интересно, вы можете прочитать подробную информацию о моей конфигурации ). iPad рядом с моим MacBook подключен к ноутбуку через Sidecar , поэтому при необходимости я могу быстро превратить iPad во второй экран.

Если я хочу воспользоваться преимуществами большего экрана, я могу разместить всплывающее окно из примера кода на втором экране. Я делаю это так:
popup.moveTo(2500, 50);
Это приблизительная оценка, поскольку невозможно узнать размеры второго экрана. Информация из window.screen охватывает только встроенный экран, но не экран iPad. Заявленная width встроенного экрана составляет 1680 пикселей, поэтому увеличение до 2500 пикселей может помочь переместить окно на iPad, поскольку я знаю, что он расположен справа от моего MacBook. Как это сделать в общем случае? Оказывается, есть способ получше, чем гадать. Этот способ — API управления окнами.
Обнаружение признаков
Чтобы проверить, поддерживается ли API управления окнами, используйте:
if ('getScreenDetails' in window) {
// The Window Management API is supported.
}
Разрешение window-management
Прежде чем использовать API управления окнами, я должен запросить у пользователя разрешение на это. Запрос разрешения window-management можно выполнить с помощью 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() . Вызов этой функции отобразит запрос на разрешение, спрашивающий, может ли сайт открывать и размещать окна на моем экране. Функция возвращает промис, который разрешается объектом ScreenDetailed . На моем MacBook Pro 13 с подключенным iPad это включает поле screens с двумя объектами 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
}]
}
*/
Информация о подключенных экранах доступна в массиве screens . Обратите внимание, что значение left для iPad начинается с 1680 , что точно соответствует width встроенного дисплея. Это позволяет мне точно определить, как логически расположены экраны (рядом друг с другом, друг над другом и т. д.). Теперь для каждого экрана также есть данные, показывающие, является ли он isInternal ) или основным isPrimary ). Обратите внимание, что встроенный экран не обязательно является основным .
Поле currentScreen представляет собой динамически изменяемый объект, соответствующий текущему window.screen . Объект обновляется при перемещении окон между экранами или изменении устройства.
Событие screenschange
Единственное, чего сейчас не хватает, — это способа отслеживать изменения в конфигурации экранов. Новое событие, screenschange , делает именно это: оно срабатывает всякий раз, когда изменяется конфигурация экранов. (Обратите внимание, что в названии события "screens" стоит во множественном числе.) Это означает, что событие срабатывает всякий раз, когда новый экран или существующий экран (физически или виртуально в случае Sidecar) подключается или отключается.
Для получения сведений о новом экране необходимо выполнять асинхронный поиск, само событие screenschange эти данные не предоставляет. Для поиска сведений о экране используйте объект live из кэшированного интерфейса 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 . 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);
}
Полиэфирный наполнитель
Использовать полифил для 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, а именно различные события смены экрана и свойство screen объекта FullscreenOptions , никогда не будут срабатывать или будут молча игнорироваться браузерами, не поддерживающими эту функцию.
Демо
Если вы внимательно следите за развитием различных криптовалют, вы можете отслеживать рынки, не вставая с постели, с помощью моего приложения, работающего на одном экране. (Я сам этого делать не делаю, потому что люблю эту планету, но ради этой статьи предположим, что я это делаю.)

Поскольку речь идёт о криптовалютах, рынки могут в любой момент стать очень напряжёнными. В таком случае я могу быстро переместиться к своему рабочему столу, где у меня настроена многоэкранная система. Я могу щёлкнуть по окну любой валюты и быстро увидеть полную информацию в полноэкранном режиме на противоположном экране. Вот моя недавняя фотография, сделанная во время последнего обвала YCY . Она застала меня врасплох, и я закрыл лицо руками .

Попробуйте демо-версию или посмотрите исходный код на GitHub.
Безопасность и права доступа
Команда Chrome разработала и внедрила API управления окнами, используя основные принципы, изложенные в документе «Контроль доступа к мощным функциям веб-платформы» , включая пользовательский контроль, прозрачность и эргономику. API управления окнами предоставляет новую информацию об экранах, подключенных к устройству, увеличивая площадь, по которой можно идентифицировать пользователей, особенно тех, у кого к устройствам постоянно подключено несколько экранов. В качестве одного из способов решения этой проблемы конфиденциальности, предоставляемые свойства экранов ограничены минимальным набором данных, необходимым для распространенных сценариев размещения.
Для получения информации о работе нескольких экранов и размещения окон на других экранах сайтам требуется разрешение пользователя. Хотя Chromium возвращает подробные метки экранов, браузеры могут возвращать менее подробные (или даже пустые) метки.
Управление пользователем
Пользователь полностью контролирует доступность своей системы. Он может принять или отклонить запрос на предоставление разрешений, а также отозвать ранее предоставленное разрешение с помощью функции информации о сайте в браузере.
Управление предприятием
Пользователи Chrome Enterprise могут управлять некоторыми аспектами API управления окнами, как описано в соответствующем разделе настроек групп атомарных политик .
Прозрачность
Информация о том, предоставлено ли разрешение на использование API управления окнами, содержится в информации о сайте браузера, а также может быть получена с помощью API разрешений.
Сохранение разрешений
Браузер сохраняет предоставленные разрешения. Разрешение можно отозвать через информацию о сайте, хранящуюся в браузере.
Обратная связь
Есть ли что-то в API, что работает не так, как вы ожидали? Или отсутствуют методы или свойства, необходимые для реализации вашей идеи? Есть вопрос или комментарий по модели безопасности?
- Создайте заявку в соответствующем репозитории GitHub или поделитесь своими мыслями в уже существующей заявке.
- Сообщите об ошибке, связанной с реализацией в Chrome . Обязательно укажите как можно больше подробностей, инструкции по воспроизведению и введите
Blink>Screen>MultiScreenв поле «Компоненты» .
Показать поддержку API
Планируете ли вы использовать API управления окнами? Ваша публичная поддержка помогает команде Chrome расставлять приоритеты в разработке новых функций и показывает другим производителям браузеров, насколько важно поддерживать их.
- Поделитесь своими планами по его использованию в ветке обсуждения WICG на форуме.
- Отправьте твит @ChromiumDev , используя хэштег
#WindowManagement, и расскажите, где и как вы его используете. - Обратитесь к другим производителям браузеров с просьбой внедрить этот API.
Ресурсы
- Проект проекта
- Публичное пояснение
- Демонстрация API управления окнами | Исходный код демонстрации API управления окнами
- Отслеживание ошибки в Chromium
- Запись на ChromeStatus.com
- Компонент Blink:
Blink>Screen>MultiScreen - Обзор TAG
- Намерение провести эксперимент
Благодарности
Спецификация API управления окнами была отредактирована Виктором Костаном , Джошуа Беллом и Майком Вассерманом . API был реализован Майком Вассерманом и Адриенн Уокер . Рецензирование проводили Джо Медли , Франсуа Бофор и Кейси Баск . Благодарим Лауру Торрент Пуиг за фотографии.