视频会议开到一半要切出去看文档?浏览器这个 API 把整个页面变成了画中画——Document PiP 把这件事彻底变了

你有没有遇到过这种场景:视频会议开着,但切到别的标签页就看不见对方了;在线课要边看教程边做笔记,但播放窗口老是被别的页面挡住。以前这种需求要么靠浏览器原生画中画(只能放一个video标签),要么靠 window.open 自己写悬浮窗(导航和层级问题一堆)。

Chrome 116 给了一个新选项:Document Picture-in-Picture API。它本质上是一个「带脑子」的 window.open()——内容自己定、永远浮在顶层、不会自己跳转、关了还不会留孤儿窗口。

和传统 PiP 的本质区别

传统 <video> 的 Picture-in-Picture 只能塞一个视频进去,控件都是浏览器生成的,没法自定义。Document PiP 直接给你一个空白窗口,你可以往里面塞任意 HTML——视频、按钮、进度条、整个聊天面板,随你怎么玩。

const btn = document.querySelector("#openPip");
btn.addEventListener("click", async () => {
  // 检测浏览器支持
  if (!("documentPictureInPicture" in window)) {
    alert("浏览器不支持 Document PiP");
    return;
  }

  const pipWindow = await window.documentPictureInPicture.requestWindow({
    width: 640,
    height: 480,
  });

  // 把页面里的播放器移进 PiP 窗口
  const player = document.querySelector("#player");
  pipWindow.document.body.append(player);
});

这段代码打开一个 640×480 的空白悬浮窗,然后把页面里的 #player 元素移进去。用户可以把窗口拖到副屏,继续干别的事,主窗口里的内容还在原位。

跨窗口通信:两个文档是隔离的

这是最容易踩的坑:PiP 窗口和主页面是两个完全隔离的 JavaScript 上下文,不能共享变量,不能直接调用对方函数,数据同步只能走 postMessage

// 主页面发消息到 PiP 窗口
const pipWindow = documentPictureInPicture.window;
pipWindow.postMessage({ type: "PLAY" }, "*");

// PiP 窗口监听
window.addEventListener("message", (event) => {
  if (event.source !== documentPictureInPicture.window) return;
  if (event.data.type === "PLAY") video.play();
});

反过来同理,PiP 窗口里的脚本也可以 postMessage 回去通知主页面。关键是要校验 event.source,否则有 XSS 风险。

getstream.io 在视频会议场景里的最佳实践还提到一点:把样式表从主窗口复制到 PiP 窗口里,否则里面是裸 HTML。requestWindow() 返回的窗口没有任何样式。

window.document.head
  .querySelectorAll("link[rel="stylesheet"], style")
  .forEach((node) => {
    pipWindow.document.head.appendChild(node.cloneNode(true));
  });

还有一个很多人不知道的状态坑

PiP 窗口关闭后,documentPictureInPicture.window 不会自动置空,还保留着对那个已关闭窗口的引用。如果不检查就再次调用 requestWindow(),会抛出 InvalidStateError

// 监听窗口关闭事件
documentPictureInPicture.addEventListener("leave", () => {
  // 窗口被关了,清理状态
  pipWindow = null;
});

// 打开前先检查
async function openPip() {
  if (documentPictureInPicture.window) {
    // 窗口已存在,先关掉
    documentPictureInPicture.window.close();
  }
  pipWindow = await documentPictureInPicture.requestWindow();
}

浏览器支持现状

目前只有 Chromium 内核完整支持:Chrome 116+、Edge 116+、Opera 102+。Firefox 151 也支持了。Safari 全系列没有,iOS 更不用说了。

这个现状决定了它必须走渐进增强的路子:先保证主功能在所有浏览器里可用,再给支持的浏览器加上 Document PiP 作为增强选项。

什么场景值得用

目前最成熟的三个场景:

  1. 自定义视频播放器:告别浏览器原生的那几个按钮,做自己的播放/暂停/进度/音量控制
  2. 视频会议悬浮窗口:把参会者画面或控制面板单独拎出来,切到别的标签页也能看见
  3. 在线教育边看边做:代码编辑器+浏览器预览的组合站,把预览区弹出到副屏,主屏留给编辑器

简单来说,只要你想把「某个 UI 区域」从主窗口里解放出来同时保持交互,Document PiP 就比 window.open 靠谱得多。

下一步

Chrome 116 是 2023 年 8 月的版本,如果你的用户主要是 Chromium 内核用户,现在已经可以上了。检测方法只要一行:

const supported = "documentPictureInPicture" in window;

不支持的浏览器走原来的方案降级,别让这个 API 成为可用性的阻碍。

评论区

0 条评论

登录后可评论。

阿速·性能优化 12 阅读