使用窗口管理 API

本指南介绍如何使用 窗口管理 API。下面看到的示例代码取自我们的 多窗口学习环境 示例(参见 源代码)。

功能检测

您可以通过检查当前 window 实例中是否存在 getScreenDetails 来检测窗口管理 API 的功能。例如,您可能希望提供一个按钮来打开多窗口显示(如果 API 支持),或者提供不同的体验,例如创建指向不同页面的链接(如果 API 不支持)。

js
if ("getScreenDetails" in window) {
  // The Window Management API is supported
  createButton();
} else {
  // The Window Management API is not supported
  createLinks(sites);
}

基本用法

窗口管理 API 的核心是 Window.getScreenDetails() 方法,该方法返回一个包含用户系统所有可用屏幕详细信息的对象。

js
const screenDetails = await window.getScreenDetails();

// Return the number of screens
const noOfScreens = screenDetails.screens.length;

当调用 getScreenDetails() 时,系统将询问用户是否允许在所有显示器上管理窗口(可以使用 Permissions.query() 查询 window-management 来检查此权限的状态)。如果用户授予权限,则将返回一个 ScreenDetails 对象。此对象包含以下属性。

  • screens: 一个 ScreenDetailed 对象数组,每个对象都包含有关系统可用单独屏幕的详细信息(见下文)。此数组也可用于确定可用屏幕的数量,方法是使用 screens.length
  • currentScreen: 一个单独的 ScreenDetailed 对象,包含有关当前浏览器窗口显示所在屏幕的详细信息。

ScreenDetailed 对象继承了 Screen 接口的属性,并包含有关在特定屏幕上放置窗口的有用信息。

注意:您可以根据用户是否有多个屏幕可用,来对功能进行限制,方法是使用 Window.screen.isExtended 属性。如果设备有多个屏幕,则此属性返回 true,否则返回 false

打开窗口

您仍然需要使用 Window.open() 来打开和管理窗口,但上述内容提供了更多信息,以便您在多屏幕环境中进行操作。例如,实用程序函数可能如下所示。

js
// Array to hold references to the currently open windows
let windowRefs = [];

// ...

function openWindow(left, top, width, height, url) {
  const windowFeatures = `left=${left},top=${top},width=${width},height=${height}`;
  const windowRef = window.open(
    url,
    "_blank", // needed for it to open in a new window
    windowFeatures,
  );

  if (windowRef === null) {
    // If the browser is blocking new windows, close any windows that were
    // able to open and display instructions to help the user fix this problem
    closeAllWindows();
    popoverElem.showPopover();
  } else {
    // Store a reference to the window in the windowRefs array
    windowRefs.push(windowRef);
  }
}

然后,您可以调用此函数并在特定屏幕上打开窗口,如下所示。

js
const screen1 = screenDetails.screens[0];
const screen2 = screenDetails.screens[1];
// Windows will be a third the width and the full height of the screen
// The available width of screen1, minus 3 times the horizontal browser chrome
// width, divided by 3
const windowWidth = Math.floor((screen1.availWidth - 3 * WINDOW_CHROME_X) / 3);
// The available height of screen1, minus the vertical browser chrome width
const windowHeight = Math.floor(screen1.availHeight - WINDOW_CHROME_Y);

// Open a window a third of the width and the entire height of the primary screen
openWindow(
  screen1.availLeft,
  screen1.availTop,
  windowWidth,
  windowHeight,
  sites[1].url,
);

// ...

关闭所有窗口

打开每个窗口后,我们都会将对该窗口的引用添加到 windowRefs 数组中。这使您能够例如,在某个窗口关闭时关闭所有窗口。

js
function closeAllWindows() {
  // Loop through all window refs and close each one
  windowRefs.forEach((windowRef) => {
    windowRef.close();
  });
  windowRefs = [];
}

// Check whether one of our popup windows has been closed
// If so, close them all

closeMonitor = setInterval(checkWindowClose, 250);

function checkWindowClose() {
  if (windowRefs.some((windowRef) => windowRef.closed)) {
    closeAllWindows();
    clearInterval(closeMonitor);
  }
}

注意:在我们的实验中,上面显示的 setInterval() 轮询方法在检测多个窗口关闭的情况下似乎效果最佳。使用诸如 beforeunloadpagehidevisibilitychange 等事件被证明不可靠,因为在同时打开多个窗口时,焦点/可见性的快速转变似乎会过早地触发处理函数。

注意:上面示例中的一点担忧是,它使用常量值来表示计算中 Chrome 窗口 UI 部分的大小(WINDOW_CHROME_XWINDOW_CHROME_Y),以使窗口大小计算正确。为了在 API 的其他未来实现中创建精确大小的窗口,您需要保留一个小型浏览器 chrome 大小库,并使用浏览器检测来发现哪个浏览器正在渲染您的应用程序,并选择正确的尺寸进行计算。或者,您可以依赖不太精确的窗口大小。

处理浏览器弹出窗口拦截器

在现代浏览器中,出于安全目的,每次 Window.open() 调用都需要一个单独的用户手势事件。这可以防止网站向用户发送大量窗口。但是,这会给多窗口应用程序带来问题。为了解决此限制,您可以设计您的应用程序以

  • 一次只打开一个新窗口。
  • 重新使用现有窗口来显示不同的页面。
  • 建议用户更新浏览器设置以允许打开多个窗口。

在我们的演示应用程序中,我们选择了第三种选择。我们的 openWindow() 函数包含以下部分。

js
// ...

if (windowRef === null) {
  // If the browser is blocking new windows, close any windows that were
  // able to open and display instructions to help the user fix this problem
  closeAllWindows();
  popoverElem.showPopover();
} else {
  // Store a reference to the window in the windowRefs array
  windowRefs.push(windowRef);
}

// ...

如果浏览器阻止了新窗口,则生成的 windowRef 将为 null。在这种情况下,我们会运行我们的 closeAllWindows() 函数,以关闭在阻止开始之前成功打开的任何窗口,并显示一个 弹出式元素,解释如何禁用弹出窗口拦截器。

每个显示器只有一个简单窗口的情况

如果您希望在每个可用显示器上打开一个单个窗口,使其宽度和高度都与显示器一致,则可以使用如下所示的模式。

js
// Open a window on each screen of the device
for (const screen of screenDetails.screens) {
  openWindow(
    screen.availLeft,
    screen.availTop,
    screen.availWidth,
    screen.availHeight,
    url,
  );
}

窗口管理事件

窗口管理 API 提供了一些事件,用于响应可用屏幕的变化。

ScreenDetails screenschange 事件触发时。

当屏幕连接到系统或从系统断开连接时触发。

ScreenDetails currentscreenchange 事件触发时。

当窗口的当前屏幕发生某种变化时触发。

Screen change 事件触发时。

当特定屏幕发生某种变化时触发。

例如,您可以使用 screenschange 事件来检测可用屏幕何时发生变化(可能是当某个屏幕插入或拔出时),报告变化,关闭所有窗口,并更新窗口排列以适应新的配置。

js
screenDetails.addEventListener("screenschange", () => {
  // If the new number of screens is different to the old number of screens,
  // report the difference
  if (screenDetails.screens.length !== noOfScreens) {
    console.log(
      `The screen count changed from ${noOfScreens} to ${screenDetails.screens.length}`,
    );
  }

  // If the windows are open, close them and then open them again
  // So that they fit with the new screen configuration
  if (windowRefs.length > 0) {
    closeAllWindows();
    openWindows();
  }
});

requestFullscreen()screen 选项

窗口管理 API 向 requestFullscreen() 方法添加了一个新的 screen 选项,该选项允许您指定要在哪个屏幕上将元素设置为全屏模式。例如,如果您希望将其在主操作系统屏幕上设置为全屏,则可以执行以下操作。

js
try {
  const primaryScreen = (await getScreenDetails()).screens.find(
    (screen) => screen.isPrimary,
  );
  await document.body.requestFullscreen({ screen: primaryScreen });
} catch (err) {
  console.error(err.name, err.message);
}