能力 · API
Document Picture-in-Picture API
发布于
window.documentPictureInPicture.requestWindow() 打开一个同源、置顶的窗口,你可以往它的 document 里放任意 HTML;旧的 Picture-in-Picture API 只接受单个 <video> 元素。这个窗口浮在其他窗口之上,随打开它的页面关闭而关闭,不能导航,每个标签页最多一个。
Chrome 116 与 Edge 116 仅在桌面端提供;BCD 对 Android 上的 Chrome 记录为 version_added: false。Firefox 151 在桌面端加入,同样没有 Android 支持。Safari 没有实现(BCD api.DocumentPictureInPicture)。
documentPictureInPicture.requestWindow()documentPictureInPicture.requestWindow(options)
documentPictureInPicture.windowdocumentPictureInPicture.addEventListener("enter", listener)requestWindow() 返回 Promise<Window>,以新窗口自己的 Window 对象兑现;其文档初始为 about:blank,但 base URL 继承自打开它的文档,所以相对路径的样式表和图片按打开方页面解析。window 取值器在画中画窗口打开期间返回它,否则返回 null。enter 事件在 documentPictureInPicture 上触发,事件对象是 DocumentPictureInPictureEvent,其 window 属性为新窗口;只有你自己调用 requestWindow() 打开的窗口才会触发它。
requestWindow() 接受一个可选的 options 参数,类型为 DocumentPictureInPictureOptions 字典,每个成员都可选。
| 成员 | 类型 | 必填 | 说明 |
|---|---|---|---|
width |
unsigned long long |
否 | 请求的视口宽度,单位 CSS 像素。默认 0,由浏览器决定。过大或过小的值浏览器可能会收紧。 |
height |
unsigned long long |
否 | 请求的视口高度,规则与 width 相同。只设 width 与 height 其中之一会抛 RangeError。 |
disallowReturnToOpener |
boolean |
否 | 默认 false。为 true 时提示浏览器不要在窗口上显示「返回标签页」按钮。Chrome 124。 |
preferInitialWindowPlacement |
boolean |
否 | 默认 false。为 true 时提示浏览器使用请求的尺寸与默认位置,而不是恢复上一个已关闭画中画窗口的尺寸和位置。Chrome 130。 |
两个布尔成员都只是提示;规范用的是 should 与 may,两者都不遵守的浏览器也符合规范。
requestWindow() 在下列情况下拒绝其 Promise,顺序即规范的检查顺序。
| 异常 | 条件 |
|---|---|
NotSupportedError |
浏览器的 Document Picture-in-Picture 支持标志为 false(例如被策略禁用)。 |
NotAllowedError |
调用方窗口不是顶层可遍历对象(iframe);或调用方本身就是画中画窗口;或窗口没有瞬时用户激活。 |
RangeError |
width 大于零但 height 缺失或为零,或反过来。 |
在同一标签页再开一个窗口不算错误:规范先关闭已有的画中画窗口,再以新窗口兑现。
每个示例都先检查 "documentPictureInPicture" in window,检查不通过时内容留在主文档中。requestWindow() 的调用留在 click 处理函数内,因为它会消耗瞬时激活。
把播放器连同样式表弹出,关闭时再放回原位
Section titled “把播放器连同样式表弹出,关闭时再放回原位”新窗口一开始是空的,所以先复制打开方的样式表,再把节点移过去。监听画中画窗口的 pagehide,用户关闭窗口时把播放器放回原来的容器。
const mainContainer = document.querySelector("#player-slot");const player = document.querySelector("#player");const popOut = document.querySelector("#pop-out");
if (!("documentPictureInPicture" in window)) { popOut.hidden = true; // 该浏览器没有弹出能力,播放器留在页面内}
popOut.addEventListener("click", async () => { const pipWindow = await documentPictureInPicture.requestWindow({ width: 320, height: 180, });
for (const sheet of document.styleSheets) { try { const css = [...sheet.cssRules].map((rule) => rule.cssText).join(""); const style = document.createElement("style"); style.textContent = css; pipWindow.document.head.append(style); } catch { const link = document.createElement("link"); link.rel = "stylesheet"; link.href = sheet.href; // 跨源样式表读不到规则,改为按 URL 链接 pipWindow.document.head.append(link); } }
pipWindow.document.body.append(player); pipWindow.addEventListener("pagehide", () => mainContainer.append(player));});读取跨源样式表的 cssRules 会抛 SecurityError,所以 catch 分支改为按 URL 链接该样式表。
复用已打开的窗口而不是再开一个
Section titled “复用已打开的窗口而不是再开一个”documentPictureInPicture.window 能告诉你是否已有窗口打开。复用它可以避免规范要求的「先关再开」带来的闪烁。
async function getPipWindow(button) { if (!("documentPictureInPicture" in window)) return null;
const existing = documentPictureInPicture.window; if (existing) return existing;
try { return await documentPictureInPicture.requestWindow({ disallowReturnToOpener: true, }); } catch (err) { console.error(`${err.name}: ${err.message}`); return null; }}
button.addEventListener("click", async () => { const pipWindow = await getPipWindow(button); if (!pipWindow) { button.textContent = "无法弹出"; return; } pipWindow.document.body.textContent = `打开于 ${new Date().toLocaleTimeString()}`;});辅助函数返回 null,调用方就能渲染页内状态,而不是留下一个未处理的拒绝。
- Media Session API,其
enterpictureinpicture动作处理函数是不用点击也能打开该窗口的唯一途径 - Window Management API,用于把普通窗口放到多块屏幕上
- Screen Wake Lock API,浮动播放器运行时保持屏幕常亮
- Document Picture-in-Picture: requestWindow() method(wicg.github.io)
- Document Picture-in-Picture API(developer.chrome.com)
规范
| 规范 | 状态 |
|---|---|
| Document Picture-in-Picture: requestWindow() method | WICG 草案 |
| Document Picture-in-Picture: DocumentPictureInPictureOptions dictionary | WICG 草案 |