获取有关已连接显示屏的信息,并相对于这些显示屏放置窗口。
窗口管理 API
借助 Window Management API,您可以枚举连接到计算机的显示屏,并将窗口放置在特定屏幕上。
建议的用例
可以使用此 API 的网站示例包括:
- 类似 Gimp 的多窗口图形编辑器可以将各种编辑工具放置在精确定位的窗口中。
- 虚拟交易台可以在多个窗口中显示市场趋势,其中任一窗口都可以在全屏模式下查看。
- 幻灯片应用可以在内部主屏幕上显示演讲者备注,并在外接投影仪上显示演示文稿。
如何使用 Window Management API
问题
遗憾的是,用于控制窗口的经过时间测试的方法 Window.open()
不知道存在额外的屏幕。虽然此 API 的某些方面(例如其 windowFeatures
DOMString
参数)似乎有点过时,但多年来,它仍然为我们服务。如需指定窗口的位置,您可以将坐标作为 left
和 top
(分别为 screenX
和 screenY
)传递,并将所需的大小作为 width
和 height
(分别为 innerWidth
和 innerHeight
)传递。例如,如需在距离左侧 50 像素、距离顶部 50 像素的位置打开一个 400×300 的窗口,您可以使用以下代码:
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 的权限,然后才能使用该 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()
获取有关第二个屏幕的更多信息。调用此函数会显示一个权限提示,询问我是否允许该网站在我的屏幕上打开和放置窗口。该函数将返回一个使用 ScreenDetailed
对象进行解析的 promise。在已连接 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
。Window Management 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,但您可以对其形状进行修补,以便专门针对新 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 血浴照片。这让我完全不知所措,双手放在脸上。
您可以玩一下下面嵌入的演示,也可以在 Glitch 上查看其源代码。
安全与权限
Chrome 团队使用控制对强大 Web 平台功能的访问权限中定义的核心原则(包括用户控制、透明度和人体工学)设计和实现了 Window Management API。Window Management API 提供了有关连接到设备的屏幕的新信息,增加了用户的指纹识别面,尤其是那些有多个屏幕始终连接到其设备的用户。为了缓解此隐私问题,我们将公开的屏幕属性限制为常见展示位置用例所需的最低限度。网站必须获得用户许可,才能获取多屏幕信息并在其他屏幕上放置窗口。虽然 Chromium 会返回详细的屏幕标签,但浏览器可以自由返回不太具描述性(甚至空白)的标签。
用户控制
用户可以完全控制其设置的公开范围。他们可以接受或拒绝权限提示,以及通过浏览器中的网站信息功能撤消之前授予的权限。
企业控制
Chrome 企业版用户可以控制 Window Management API 的多个方面,如 Atomic Policy Groups 设置的相关部分所述。
透明度
浏览器的网站信息中会显示是否已授予使用 Window Management API 的权限,您也可以通过 Permissions API 查询此信息。
权限持久性
浏览器会保留已授予的权限。您可以通过浏览器的网站信息撤消此权限。
反馈
Chrome 团队希望了解您使用 Window Management API 的体验。
请向我们说明 API 设计
API 是否有什么无法按预期运行?或者,您是否缺少实现想法所需的方法或属性?对安全模型有疑问或意见?
- 在相应的 GitHub 代码库中提交规范问题,或在现有问题中添加您的想法。
报告实现方面的问题
您是否发现了 Chrome 实现中的 bug?或者,实现是否与规范不同?
- 请访问 new.crbug.com 提交 bug。请务必提供尽可能详细的信息、简单的重现说明,并在 Components 框中输入
Blink>Screen>MultiScreen
。故障非常适合分享快速简便的重现步骤。
显示对该 API 的支持
您打算使用 Window Management API 吗?您的公开支持有助于 Chrome 团队确定功能的优先级,并向其他浏览器供应商表明支持这些功能的重要性。
- 在 WICG Discourse 会话中分享您打算如何使用该工具。
- 使用
#WindowManagement
标签向 @ChromiumDev 发送推文,告诉我们您在哪里以及如何使用该工具。 - 要求其他浏览器供应商实现该 API。
实用链接
- 规范草稿
- 公开说明文
- Window Management API 演示 | Window Management API 演示源代码
- Chromium 跟踪 bug
- ChromeStatus.com 条目
- 闪烁组件:
Blink>Screen>MultiScreen
- TAG 审核
- 意图进行实验
致谢
Window Management API 规范由 Victor Costan、Joshua Bell 和 Mike Wasserman 修改。此 API 由 Mike Wasserman 和 Adrienne Walker 实现。本文由 Joe Medley、François Beaufort 和 Kayce Basques 审核。感谢 Laura Torrent Puig 提供照片。