获取有关已连接显示屏的信息,并相对于这些显示屏定位窗口。
Window Management API
借助 Window Management API,您可以枚举连接到机器的显示屏,并将窗口放置在特定屏幕上。
建议的应用场景
以下是一些可能使用此 API 的网站示例:
- 类似于 Gimp 的多窗口图形编辑器可以将各种编辑工具放置在精确定位的窗口中。
- 虚拟交易平台可以在多个窗口中显示市场趋势,并且任何窗口都可以全屏模式查看。
- 幻灯片应用可以在内部主屏幕上显示演讲者备注,并在外部投影仪上显示演示文稿。
如何使用 Window Management API
问题
用于控制窗口的经过时间检验的方法 Window.open()
遗憾的是,它无法识别额外的屏幕。虽然此 API 的某些方面(例如其 windowFeatures
DOMString
参数)看起来有点过时,但多年来它一直为我们提供出色的服务。如需指定窗口的位置,您可以将坐标作为 left
和 top
(或分别为 screenX
和 screenY
)传递,并将所需的大小作为 width
和 height
(或分别为 innerWidth
和 innerHeight
)传递。例如,如需打开一个 400x300 的窗口,该窗口距离左侧 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
*/
与大多数技术从业者一样,我不得不适应新的工作现实,并设置自己的个人居家办公室。我的设置如下图所示(如果您有兴趣,可以阅读有关我的设置的完整详细信息)。 我 MacBook 旁边的 iPad 通过 Sidecar 连接到笔记本电脑,因此我可以随时快速将 iPad 用作第二屏幕。

如果我想利用更大的屏幕,可以将上述代码示例中的弹出式窗口放到第二屏幕上。我的做法如下:
popup.moveTo(2500, 50);
这只是一个粗略的猜测,因为我们无法知道第二块屏幕的尺寸。window.screen
中的信息仅涵盖内置屏幕,不涵盖 iPad 屏幕。内置屏幕的报告 width
为 1680
像素,因此移至 2500
像素可能有助于将窗口移至 iPad,因为我恰好知道它位于 MacBook 的右侧。如何在一般情况下实现此目的?事实证明,有一种比猜测更好的方法。这种方式就是 Window Management API。
功能检测
如需检查是否支持 Window Management API,请使用:
if ('getScreenDetails' in window) {
// The Window Management API is supported.
}
window-management
权限
在使用 Window Management API 之前,我必须征得用户同意。可以使用 Permissions API 查询 window-management
权限,如下所示:
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()
获取有关第二个屏幕的更多信息。调用此函数会显示权限提示,询问我是否允许网站在我的屏幕上打开和放置窗口。该函数会返回一个 promise,该 promise 会解析为 ScreenDetailed
对象。在连接了 iPad 的 MacBook Pro 13 上,此字段包含两个 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()
的方法请求以全屏模式显示元素。该方法采用 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);
}
Polyfill
无法对 Window Management API 进行 Polyfill,但您可以对它的形状进行 shim,以便仅针对新 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;
}
API 的其他方面(即各种屏幕更改事件和 FullscreenOptions
的 screen
属性)将永远不会触发,或者会被不支持的浏览器默默忽略。
演示
如果您像我一样,会密切关注各种加密货币的发展。(实际上我非常喜欢这个星球,但为了本文,请假设我并不喜欢。)为了跟踪我拥有的加密货币,我开发了一款 Web 应用,让我可以在各种生活场景中关注市场动态,例如在舒适的床上,我有一个不错的单屏设置。

由于涉及加密货币,市场随时可能变得动荡不安。如果发生这种情况,我可以快速移到办公桌前,那里有多屏幕设置。我可以点击任何币种的窗口,然后在另一屏幕上以全屏视图快速查看完整详情。下图是我在上次 YCY 血洗期间拍摄的近照。这让我措手不及,双手捂脸。

您可以试用下方嵌入的演示,也可以在 GitHub 上查看其源代码。
安全与权限
Chrome 团队在设计和实现 Window Management API 时,遵循了控制对强大的 Web 平台功能的访问权限中定义的核心原则,包括用户控制、透明度和人体工程学。窗口管理 API 会公开有关连接到设备的屏幕的新信息,从而增加用户的指纹识别面,尤其是那些始终将多个屏幕连接到其设备的用户。为了缓解这一隐私问题,公开的屏幕属性仅限于常见展示位置用例所需的最少属性。网站必须获得用户许可,才能获取多屏幕信息并在其他屏幕上放置窗口。虽然 Chromium 会返回详细的屏幕标签,但浏览器可以返回描述性较差(甚至为空)的标签。
用户控制
用户可以完全掌控其设置的公开程度。他们可以接受或拒绝权限提示,还可以通过浏览器中的网站信息功能撤消之前授予的权限。
企业控制
Chrome 企业版用户可以控制 Window Management API 的多个方面,如原子政策组设置的相关部分中所述。
透明度
使用 Window Management API 的权限是否已获授予,会在浏览器的网站信息中公开,并且还可以通过 Permissions API 进行查询。
权限持久性
浏览器会保留权限授予。您可以通过浏览器的网站信息撤消此权限。
反馈
Chrome 团队希望了解您在使用 Window Management API 方面的体验。
介绍 API 设计
API 是否存在未按预期运行的情况?或者,是否有缺少的方法或属性需要您来实现自己的想法?对安全模型有疑问或意见?
- 在相应的 GitHub 代码库中提交规范问题,或在现有问题中添加您的想法。
报告实现方面的问题
您是否发现 Chrome 的实现存在 bug?还是实现与规范不同?
- 在 new.crbug.com 上提交 bug。请务必尽可能详细地说明问题,提供简单的重现说明,并在组件框中输入
Blink>Screen>MultiScreen
。
显示对 API 的支持
您打算使用 Window Management API 吗?您的公开支持有助于 Chrome 团队确定功能的优先级,并向其他浏览器供应商展示支持这些功能的重要性。
- 在 WICG Discourse 帖子中分享您打算如何使用它。
- 使用 #
#WindowManagement
标签向 @ChromiumDev 发送推文,告诉我们您在何处以及如何使用它。 - 要求其他浏览器供应商实现该 API。
实用链接
- 规范草稿
- 公开说明
- Window Management API 演示 | Window Management API 演示源代码
- Chromium 跟踪 bug
- ChromeStatus.com 条目
- Blink 组件:
Blink>Screen>MultiScreen
- TAG Review
- 实验意向
致谢
窗口管理 API 规范由 Victor Costan、Joshua Bell 和 Mike Wasserman 编辑。 该 API 由 Mike Wasserman 和 Adrienne Walker 实现。本文已由 Joe Medley、François Beaufort 和 Kayce Basques 审核。感谢 Laura Torrent Puig 提供照片。