对任何元素(而不仅仅是“视频”)实现画中画

François Beaufort
François Beaufort

浏览器支持

  • Chrome:116。 <ph type="x-smartling-placeholder">
  • Edge:116。 <ph type="x-smartling-placeholder">
  • Firefox:不支持。 <ph type="x-smartling-placeholder">
  • Safari:不支持。 <ph type="x-smartling-placeholder">

来源

借助 Document Picture-in-Picture API,您可以打开一个始终在顶部填充任意 HTML 内容的窗口。它扩展了适用于 <video> 的现有 Picture-in-Picture API,该 API 仅允许将 HTML <video> 元素放入画中画窗口中。

Document Picture-in-Picture API 中的画中画窗口类似于通过 window.open() 打开的空白同源窗口,但存在一些区别:

  • 画中画窗口悬浮在其他窗口之上。
  • 画中画窗口的存在时间绝不会比打开的窗口长。
  • 无法浏览“画中画”窗口。
  • 网站无法设置“画中画”窗口位置。
。 <ph type="x-smartling-placeholder">
</ph> 画中画窗口,播放 Sintel 预告片视频。
使用 Document Picture-in-Picture API 创建的画中画窗口(演示)。

当前状态

步骤 状态
1. 创建铺垫消息 完成
2. 创建规范的初始草稿 进行中
3. 收集反馈和对设计进行迭代 进行中
4. 源试用 完成
5. 投放 完整播放广告(桌面设备)

使用场景

自定义视频播放器

网站可以使用现有的适用于 <video> 的 Picture-in-Picture API 提供画中画视频体验,但非常有限。现有的“画中画”窗口接受的输入很少,且其样式设置能力有限。在“画中画”模式下拥有完整的文档,网站可以提供自定义控件和输入内容(例如,字幕、播放列表、时间进度条、顶和踩视频),以改善用户的画中画视频体验。

视频会议

用户在视频会议会话期间通常会出于各种原因(例如,在通话中展示其他标签页或多任务处理)离开浏览器标签页,但仍然希望看到通话,因此这就是画中画功能的主要应用场景。再次强调,目前视频会议网站通过适用于 <video> 的 Picture-in-Picture API 提供的体验在风格和输入方面也存在一定限制。借助“画中画”模式的完整文档,网站可以轻松将多个视频流整合到单个画中画窗口中,而无需依赖画布技巧并提供自定义控件(例如发送消息、将其他用户设为静音或举手)。

效率

研究表明,用户需要更多方式在网络上高效工作。“画中画”模式让 Web 应用能够灵活处理更多事务。无论是文本编辑、记事、任务列表、消息传输和聊天,还是设计和开发工具,Web 应用现在都可以让他们的内容随时可供访问。

接口

属性

documentPictureInPicture.window
返回当前的画中画窗口(如果有)。否则,返回 null

方法

documentPictureInPicture.requestWindow(options)

返回一个在画中画窗口打开时进行解析的 promise。 如果在没有用户手势的情况下调用 promise,则会拒绝。 options 字典包含以下可选成员:

width
设置画中画窗口的初始宽度。
height
设置“画中画”窗口的初始高度。
disallowReturnToOpener
隐藏“返回标签页”“画中画”窗口中的“保存”按钮。默认值为 false。

事件

documentPictureInPicture.onenter
documentPictureInPicture在打开画中画窗口时触发。

示例

以下 HTML 设置了一个自定义视频播放器和一个按钮元素,用于在“画中画”窗口中打开视频播放器。

<div id="playerContainer">
  <div id="player">
    <video id="video"></video>
  </div>
</div>
<button id="pipButton">Open Picture-in-Picture window</button>

打开画中画窗口

当用户点击按钮以打开空白的画中画窗口时,以下 JavaScript 会调用 documentPictureInPicture.requestWindow()。返回的 promise 使用画中画窗口 JavaScript 对象进行解析。系统会使用 append() 将视频播放器移至该窗口。

pipButton.addEventListener('click', async () => {
  const player = document.querySelector("#player");

  // Open a Picture-in-Picture window.
  const pipWindow = await documentPictureInPicture.requestWindow();

  // Move the player to the Picture-in-Picture window.
  pipWindow.document.body.append(player);
});

设置“画中画”窗口的尺寸

如需设置画中画窗口的大小,请将 documentPictureInPicture.requestWindow()widthheight 选项设置为所需的画中画窗口大小。如果选项值过大或过小,无法适应方便用户使用的窗口大小,Chrome 可能会限制相应值。

pipButton.addEventListener("click", async () => {
  const player = document.querySelector("#player");

  // Open a Picture-in-Picture window whose size is
  // the same as the player's.
  const pipWindow = await documentPictureInPicture.requestWindow({
    width: player.clientWidth,
    height: player.clientHeight,
  });

  // Move the player to the Picture-in-Picture window.
  pipWindow.document.body.append(player);
});

隐藏“返回标签页”“画中画”窗口的按钮

如需在“画中画”窗口中隐藏可让用户返回“Opener”标签页的按钮,请将 documentPictureInPicture.requestWindow()disallowReturnToOpener 选项设置为 true

pipButton.addEventListener("click", async () => {
  // Open a Picture-in-Picture window which hides the "back to tab" button.
  const pipWindow = await documentPictureInPicture.requestWindow({
    disallowReturnToOpener: true,
  });
});

将样式表复制到“画中画”窗口

要从源窗口中复制所有 CSS 样式表,请循环遍历明确链接到或嵌入文档中的 styleSheets,然后将它们附加到画中画窗口。请注意,这是一次性副本。

pipButton.addEventListener("click", async () => {
  const player = document.querySelector("#player");

  // Open a Picture-in-Picture window.
  const pipWindow = await documentPictureInPicture.requestWindow();

  // Copy style sheets over from the initial document
  // so that the player looks the same.
  [...document.styleSheets].forEach((styleSheet) => {
    try {
      const cssRules = [...styleSheet.cssRules].map((rule) => rule.cssText).join('');
      const style = document.createElement('style');

      style.textContent = cssRules;
      pipWindow.document.head.appendChild(style);
    } catch (e) {
      const link = document.createElement('link');

      link.rel = 'stylesheet';
      link.type = styleSheet.type;
      link.media = styleSheet.media;
      link.href = styleSheet.href;
      pipWindow.document.head.appendChild(link);
    }
  });

  // Move the player to the Picture-in-Picture window.
  pipWindow.document.body.append(player);
});

“画中画”窗口关闭时的处理

监听窗口 "pagehide" 事件,以了解画中画窗口何时关闭(是因为网站启动了该窗口,还是用户手动关闭了窗口)。事件处理脚本非常适合将元素从画中画窗口中取回,如下所示。

pipButton.addEventListener("click", async () => {
  const player = document.querySelector("#player");

  // Open a Picture-in-Picture window.
  const pipWindow = await documentPictureInPicture.requestWindow();

  // Move the player to the Picture-in-Picture window.
  pipWindow.document.body.append(player);

  // Move the player back when the Picture-in-Picture window closes.
  pipWindow.addEventListener("pagehide", (event) => {
    const playerContainer = document.querySelector("#playerContainer");
    const pipPlayer = event.target.querySelector("#player");
    playerContainer.append(pipPlayer);
  });
});

使用 close() 方法以编程方式关闭画中画窗口。

// Close the Picture-in-Picture window programmatically. 
// The "pagehide" event will fire normally.
pipWindow.close();

监听网站何时进入“画中画”模式

监听 documentPictureInPicture 上的 "enter" 事件,以了解画中画窗口何时打开。该事件包含一个用于访问“画中画”窗口的 window 对象。

documentPictureInPicture.addEventListener("enter", (event) => {
  const pipWindow = event.window;
});

访问“画中画”窗口中的元素

您可以通过 documentPictureInPicture.requestWindow() 返回的对象或使用 documentPictureInPicture.window 访问画中画窗口中的元素,如下所示。

const pipWindow = documentPictureInPicture.window;
if (pipWindow) {
  // Mute video playing in the Picture-in-Picture window.
  const pipVideo = pipWindow.document.querySelector("#video");
  pipVideo.muted = true;
}

处理来自画中画窗口的事件

创建按钮和控件,并响应用户的输入事件(例如 "click"),就像您在 JavaScript 中所做的那样。

// Add a "mute" button to the Picture-in-Picture window.
const pipMuteButton = pipWindow.document.createElement("button");
pipMuteButton.textContent = "Mute";
pipMuteButton.addEventListener("click", () => { 
  const pipVideo = pipWindow.document.querySelector("#video");
  pipVideo.muted = true;
});
pipWindow.document.body.append(pipMuteButton);

调整画中画窗口的大小

使用 resizeBy()resizeTo() Window 方法可调整画中画窗口的大小。这两种方法都需要用户手势。

const resizeButton = pipWindow.document.createElement('button');
resizeButton.textContent = 'Resize';
resizeButton.addEventListener('click', () => {
  // Expand the Picture-in-Picture window's width by 20px and height by 30px.
  pipWindow.resizeBy(20, 30);
});
pipWindow.document.body.append(resizeButton);

将焦点置于 Opener 窗口

使用 focus() 窗口方法,从画中画窗口聚焦于打开程序窗口。此方法需要用户手势。

const returnToTabButton = pipWindow.document.createElement("button");
returnToTabButton.textContent = "Return to opener tab";
returnToTabButton.addEventListener("click", () => {
  window.focus();
});
pipWindow.document.body.append(returnToTabButton);

CSS 画中画显示模式

您可以使用 CSS picture-in-picture 显示模式编写特定的 CSS 规则,这些规则仅在 Web 应用(部分)在画中画模式下显示时应用。

@media all and (display-mode: picture-in-picture) {
  body {
    margin: 0;
  }
  h1 {
    font-size: 0.8em;
  }
}

功能检测

如需检查 Document Picture-in-Picture API 是否受支持,请使用以下命令:

if ('documentPictureInPicture' in window) {
  // The Document Picture-in-Picture API is supported.
}

演示

VideoJS 播放器

您可以使用 Document Picture-in-Picture API VideoJS 播放器演示进行播放。请务必查看源代码

番茄

Tomodoro 是一款 POmodoro Web 应用,该款应用也会在出现 Document Picture-in-Picture API 时利用该 API(请参阅 GitHub 拉取请求)。

<ph type="x-smartling-placeholder">
</ph> Tomodoro 的屏幕截图,一款番茄 Web 应用。
Tomodoro 的画中画窗口。

反馈

在 GitHub 上提交问题,提供建议和问题。

致谢

主打图片由 Jakob Owens 拍摄。