Document Picture-in-Picture:置顶窗口
发布于
一句话: 根据 MDN 的说明,“Document Picture-in-Picture API 使得打开一个可以承载任意
HTML 内容的置顶窗口成为可能”,它扩展了此前只能承载单个 <video> 元素的画中画 API。
MDN 将 Document Picture-in-Picture 窗口描述为与 Window.open() 打开的同源窗口类似,但有
几点不同:“浮动于其他窗口之上”、“生命周期不会超出打开它的窗口”、“不能被导航”,并且“网站
无法设置该画中画窗口的位置”。MDN 还指出该窗口“同一时刻每个浏览器标签页最多只能有一个”。
MDN 列举的用例包括始终置顶的自定义视频播放器、视频会议控制面板,以及计时器、待办事项等
始终可见的效率工具。
根据 MDN 的 browser-compat-data,DocumentPictureInPicture 接口及其 requestWindow()
方法自 Chrome 116 起受支持(Edge 与 Chrome 保持一致)、自 Firefox 151 起受支持。同一份
数据将该特性记录为 Safari 不支持,且 Chrome for Android 与 Firefox for Android 也均不
支持(两者的 version_added 均为 false)。
根据 MDN,入口是 Window.documentPictureInPicture,通过调用
DocumentPictureInPicture.requestWindow() 打开该窗口,该方法“返回一个 Promise,
resolve 为窗口自身的 Window 对象”。根据 requestWindow() 参考文档,该调用“需要瞬态
激活(transient activation)”,因此必须在用户手势处理函数(例如点击监听器)内部运行,
并且可以传入包含 width、height、disallowReturnToOpener、
preferInitialWindowPlacement 的选项对象:
// 创建将要移入画中画窗口的内容,以及它停靠在主文档中时的容器。const mainContainer = document.createElement("div");const playerContainer = document.createElement("div");playerContainer.textContent = "正在播放…";mainContainer.append(playerContainer);document.body.append(mainContainer);
const button = document.createElement("button");button.textContent = "弹出播放器";document.body.append(button);
button.addEventListener("click", async () => { if (!("documentPictureInPicture" in window)) { // 不支持 Document Picture-in-Picture——让播放器留在主文档中,而不是抛出异常。 return; }
const pipWindow = await documentPictureInPicture.requestWindow({ width: 320, height: 180, });
// 复制应用的样式表,让移动过去的内容保持相同样式。 [...document.styleSheets].forEach((sheet) => { try { const cssRules = [...sheet.cssRules].map((rule) => rule.cssText).join(""); const style = document.createElement("style"); style.textContent = cssRules; pipWindow.document.head.appendChild(style); } catch (e) { // 跳过任何无法读取的样式表。 } });
pipWindow.document.body.append(playerContainer);
pipWindow.addEventListener("pagehide", () => { // 画中画窗口关闭时,把内容移回主窗口。 mainContainer.append(playerContainer); });});运行时检测与回退
Section titled “运行时检测与回退”在调用 requestWindow() 之前,先对 window 上的 documentPictureInPicture 做特性检测;
不支持时回退为空操作——上面的点击处理函数已经这样做了防护,但该检测也可以单独复用在应用
需要预先判断弹出功能是否可用的任何地方(例如完全隐藏“弹出播放器”按钮):
function supportsDocumentPictureInPicture() { return "documentPictureInPicture" in window;}
if (!supportsDocumentPictureInPicture()) { // 不支持 Document Picture-in-Picture——隐藏任何提供弹出功能的界面, // 让内容始终留在主文档中。 document.querySelectorAll("[data-requires-document-pip]").forEach((el) => { el.hidden = true; });}- 根据 MDN 的
requestWindow()参考文档,该方法需要瞬态激活——只能在点击等用户手势处理 函数内部调用。 - 根据 MDN,画中画窗口“生命周期不会超出打开它的窗口”且“不能被导航”——不要把它当作独立、 长期存在的窗口来依赖。
- 样式表不会自动复制;需要向画中画窗口自身的
document.head追加<style>或<link>元素,移动过去的内容才能正确渲染。 - MDN 的 browser-compat-data 显示该特性在 Chrome for Android、Firefox for Android 以及 Safari 上均不支持,因此应在所有平台上都做特性检测,而不要只假设桌面端存在差异。
- 根据 MDN,
enter事件只在你自己的代码以编程方式打开窗口时触发;当浏览器自身(例如 因为标签页被切换)把内容移入该窗口时,这个事件不会触发——此时应改用类型为enterpictureinpicture的MediaSession动作处理函数。