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

François Beaufort
François Beaufort

Browser Support

  • Chrome: 116.
  • Edge: 116.
  • Firefox: not supported.
  • Safari: not supported.

Source

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

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

  • 画中画窗口会浮动在其他窗口之上。
  • 画中画窗口的生命周期绝不会超过打开的窗口。
  • 无法浏览画中画窗口。
  • 网站无法设置画中画窗口位置。
一个画中画窗口,其中播放着 Sintel 预告片视频。
使用 Document 画中画 API 创建的画中画窗口(演示)。

当前状态

步骤 状态
1. 创建铺垫消息 完成
2. 创建规范的初始草稿 进行中
3. 收集反馈并迭代设计 进行中
4. 来源试用 完成
5. 启动 完成(桌面设备)

使用场景

自定义视频播放器

网站可以使用现有的 <video> 专用画中画 API 提供画中画视频体验,但功能非常有限。现有的画中画窗口接受的输入较少,并且对其样式的控制能力有限。在画中画模式下显示完整文档后,网站可以提供自定义控件和输入内容(例如字幕、播放列表、时间刮刀、赞和踩视频),以改善用户的画中画视频体验。

视频会议

用户在视频会议期间出于各种原因(例如,在通话中展示其他标签页或进行多任务处理)离开浏览器标签页,但仍希望查看通话内容,这在视频会议中很常见,因此是画中画的主要用例。再次提醒一下,视频会议网站目前可通过 <video> 的画中画 API 提供的体验在样式和输入方面受到限制。借助画中画模式下的完整文档,网站可以轻松将多个视频串流合并到单个画中画窗口中,而无需依赖 canvas 黑客攻击,还可以提供发送消息、将其他用户静音或举手等自定义控件。

效率

研究表明,用户需要更多方式来提高在网络上的效率。借助画中画模式中的文档,Web 应用可以灵活地完成更多工作。无论是文本编辑、记事、任务列表、消息和聊天,还是设计和开发工具,Web 应用现在都可以让用户随时访问其内容。

接口

属性

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

方法

documentPictureInPicture.requestWindow(options)

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

width
设置画中画窗口的初始宽度。
height
设置画中画窗口的初始高度。
disallowReturnToOpener
如果为 true,则隐藏画中画窗口中的“返回标签页”按钮。默认值为 false。
preferInitialWindowPlacement
如果为 true,则以默认位置和大小打开画中画窗口。默认值为 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); }); 

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

如需在画中画窗口中隐藏用于让用户返回打开器标签页的按钮,请将 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,   }); }); 

以默认位置和大小打开画中画窗口

如需不重复使用上一个画中画窗口的位置或大小,请将 documentPictureInPicture.requestWindow()preferInitialWindowPlacement 选项设置为 true

pipButton.addEventListener("click", async () => {   // Open a Picture-in-Picture window in its default position / size.   const pipWindow = await documentPictureInPicture.requestWindow({     preferInitialWindowPlacement: 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() 窗口方法调整画中画窗口的大小。这两种方法都需要用户手势。

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); 

将焦点置于打开器窗口

使用 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 规则,这些规则仅在网页应用(部分)以画中画模式显示时应用。

@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 画中画 API 的 VideoJS 播放器演示版。请务必查看源代码

Pomodoro

Tomodoro 是一个番茄工作法 Web 应用,也利用了 Document Picture-in-Picture API(如果可用)。请参阅他们的 GitHub 拉取请求

Tomodoro,一个番茄工作法 Web 应用。
Tomodoro 中的画中画窗口。

分享反馈

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