获取有关已连接显示屏的信息,并相对于这些显示屏放置窗口。
Window Management 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
像素,因此,由于 我知道它位于 MacBook 的右侧,因此将其移至 2500
像素或许可以将窗口移至 iPad。在一般情况下,我该如何执行此操作?事实证明,除了猜测之外,还有更好的方法。这种方式就是 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,由 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
。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 会公开与设备连接的屏幕的新信息,从而增加用户(尤其是与设备持续连接的多个屏幕的用户)的指纹识别 Surface。作为缓解此隐私问题的一种缓解措施,公开的屏幕属性会被限制为常见展示位置用例所需的最低限度。网站必须获得用户许可,才能获取多屏幕信息并在其他屏幕上放置窗口。虽然 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
。Glitch 非常适用于分享轻松快速的重现问题。
显示对该 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 审核
- 愿意进行实验
致谢
Window Management API 规范由 Victor Costan、Joshua Bell 和 Mike Wasserman 编辑。 此 API 由 Mike Wasserman 和 Adrienne Walker 实现。本文由 Joe Medley、François Beaufort 和 Kayce Basques 审核。感谢 Laura Torrent Puig 提供照片。