使用 Window Management API 管理多个显示屏

获取已连接显示屏的信息,以及相对于这些显示屏放置窗口。

窗口管理 API

借助 Window Management API,您可以枚举连接到计算机的显示屏 以及将窗口放置在特定屏幕上

建议的用例

可以使用此 API 的网站示例包括:

  • 多窗口图形编辑器 Gimp 可以在 Google Cloud 上 编辑工具。
  • 虚拟交易公司可通过多个窗口显示市场趋势,其中任一窗口均可查看 全屏模式。
  • 幻灯片应用可以在内部主屏幕中显示演讲者备注,并在 外部投影仪

如何使用 Window Management API

问题

久经考验的窗户控制方法 Window.open(),很遗憾 而不知道额外屏幕虽然该 API 的某些方面有点陈旧, windowFeatures DOMString 参数,但这些年来它仍为我们提供了很大的帮助。要指定窗口的 position 之后,您可以传递 坐标为 lefttop(或分别为 screenXscreenY),并传递所需的 sizewidthheight(或分别为 innerWidthinnerHeight)。例如,要打开 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
*/

与大多数技术行业员工一样,我必须适应新的工作现实, 个人家庭办公室。我的矿物在下面的照片上看起来很像(如果您感兴趣,可以阅读 有关我的设置的完整详细信息)。 我的 MacBook 旁边的 iPad 通过 Sidecar,这样每当需要时,我都可以快速 iPad 切换到第二屏幕。

<ph type="x-smartling-placeholder">
</ph> 两把椅子上的学校长椅。学校的长凳顶部有支着一台笔记本电脑的鞋盒,周围环绕着两台 iPad。
跨屏设置。

如果我想利用更大的屏幕,可以将弹出式窗口从 代码示例。我处理 如下所示:

popup.moveTo(2500, 50);

这是粗略的猜测,因为我们无法知道第二个屏幕的尺寸。信息 (来自 window.screen)仅覆盖内置屏幕,不会覆盖 iPad 屏幕。报告的width 1680像素,因此2500像素可能 因为我恰好知道它位于 MacBook 的右侧。方法 在一般情况下,我可以这样做吗?但事实证明,还有比猜测更好的方法。这样, Window Management API。

功能检测

如需检查 Window Management API 是否受支持,请使用以下命令:

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

window-management 权限

我必须先向用户请求权限,然后才能使用 Window Management API。 您可以使用window-management Permissions 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 属性。它会返回 truefalse。对于我的设置,它返回了 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,left1680 开头,这正是内置显示屏的 width。本次 我可以准确确定屏幕的逻辑排列方式(相邻, 相互通信等等)。系统现在还会提供每个屏幕的数据,以显示相应屏幕是否是 isInternal 屏幕 以及是否为 isPrimary。请注意,内置屏幕 不一定是主屏幕

currentScreen 字段是与当前 window.screen 对应的有效对象。对象 会随着跨屏窗口展示位置或设备变化而更新。

screenschange 事件

现在唯一缺少的就是检测屏幕设置何时发生变化。一个新活动 screenschange 就是这样做的:每当修改屏幕全景荟萃时,它都会触发。(通知 “屏幕”在事件名称中是复数形式。)这意味着每当有新的屏幕或 现有屏幕(如果是 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);
}

聚酯纤维

无法对 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 的其他方面,即各种屏幕更改事件和 screen 属性 FullscreenOptions 只会被触发,或被静默忽略, 浏览器不受支持。

演示

如果您像我一样,就要密切关注各种工具的发展 以及加密货币。(实际上,我其实并不爱这颗星球,但是, 在本文中,假设 did.)为了跟踪我拥有的加密货币,我开发了一款 Web 应用, 留意生活中各种情形的市场,比如在舒适的床上, 单屏设置

<ph type="x-smartling-placeholder">
</ph> 床尾的大电视屏幕,作者的腿部分可见。屏幕上是一个假的加密货币交易柜台。
放松身心,关注市场行情。

这与加密货币有关,市场可能随时都很忙。如果发生这种情况 移到我的办公桌上,那里有一个跨屏设备。我可以点击任意币种对应的窗口 在另一屏幕的全屏视图中快速查看全部细节。以下是最近拍摄的照片 这是我上次 YCY 血浴期间拍摄的。它抓住了我 我完全不知所措,留下了我 双手放在脸上

<ph type="x-smartling-placeholder">
</ph> 作者将双手放在恐慌的脸上,盯着假冒的加密货币交易柜台。
目睹 YCY 血浴。

您可以播放下方嵌入的演示,也可以查看其源代码,查看是否存在故障。

安全与权限

Chrome 团队设计并实施了 Window Management API,该 API 的核心是 控制对强大的网络平台功能的访问权限中定义的原则, 包括用户控制、透明度和人体工程学。Window Management API 提供 与连接到设备的屏幕有关的新信息,扩大了指纹的指纹识别面 用户,尤其是那些有多个屏幕一直连接到其设备的用户。合并为一个 为缓解这一隐私问题,公开的屏幕属性被限制在 了解常见的展示位置用例。网站需要用户权限才能使用跨屏功能 并将窗口放置在其他屏幕上。Chromium 会返回详细的屏幕标签, 浏览器可以随意返回较少的描述性内容(甚至是空标签)。

用户控制

用户可完全控制其设置的公开范围。他们可以接受或拒绝 权限提示,以及通过 。

企业控制

Chrome 企业版用户可以从多个方面控制 Window Management API,例如 请参阅 原子政策组 设置。

透明度

“是否有权使用 Window Management API”这一事实为 还会显示在浏览器的网站信息中,并且可通过 Permissions API 查询。

权限保留

浏览器会保持权限授予状态。可通过浏览器的网站撤消权限 信息。

反馈

Chrome 团队希望了解您使用 Window Management API 的体验。

向我们介绍 API 设计

API 是否有什么无法按预期运行?或者是否缺少方法 需要哪些资源或属性来实现您的想法?对安全性有疑问或意见 模型?

  • 在相应的 GitHub 代码库中提交规范问题,或将您的想法添加到现有 问题。

报告实现存在的问题

您在 Chrome 的实现过程中是否发现了错误?或者,实现是否与规范不同?

  • new.crbug.com 上提交 bug。请务必提供尽可能多的细节信息 以及简单的重现说明,然后在Blink>Screen>MultiScreen 组件框中。Glitch 非常适用于分享轻松快速的重现问题。

表示对 API 的支持

您打算使用 Window Management API 吗?您的公开支持对 Chrome 很有帮助 确定各项功能的优先级,并向其他浏览器供应商展示支持这些功能的重要性。

实用链接

致谢

Window Management API 规范的修改者 Victor CostanJoshua BellMike Wasserman。 此 API 由 Mike WassermanAdrienne Walker。本文由以下人员审核: Joe MedleyFrançois Beaufort、 以及凯斯·巴斯克语感谢 Laura Torrent Puig 提供照片。