画中画

W3C 工作草案,

更多关于本文档的详细信息
此版本:
https://www.w3.org/TR/2026/WD-picture-in-picture-20260616/
最新发布版本:
https://www.w3.org/TR/picture-in-picture/
编辑草案:
https://w3c.github.io/picture-in-picture/
之前版本:
历史记录:
https://www.w3.org/standards/history/picture-in-picture/
反馈:
GitHub
编辑:
(Google LLC)
(Mozilla Foundation)
前任编辑:
(Google LLC)
Web 平台测试:
permissions-policy/
picture-in-picture/

摘要

本规范提供 API,允许网站创建一个始终置顶的悬浮视频窗口,使用户在与其他内容网站或设备上的应用交互时,能够持续观看媒体内容。

本文档状态

本节描述了本文档在发布时的状态。当前 W3C 发布的文档列表及该技术报告的最新修订版可在 W3C 标准与草案索引中查阅。

欢迎对本规范提出反馈和意见。关于本规范的讨论,优先使用 GitHub Issues。此外,您也可以发送邮件至 Media 工作组的邮件列表 public-media-wg@w3.org(存档)。 本草案突出了一些仍待工作组讨论的未决问题,这些问题的结果尚未决定,也未判定其是否有效。

本文档由 Media 工作组 以工作草案形式发布,采用 推荐流程。 本文档旨在成为 W3C 推荐标准。

作为工作草案发布并不意味着 W3C 及其成员的认可。

这是一个草案文档,可能会随时被更新、替换或废止。除作为正在进行的工作外,不适合引用本文件。

本文档由遵循 W3C 专利政策 的组织制定。 W3C 维护着一个公开的专利披露列表,列出了与本组织交付成果相关的任何专利披露;该页面还包括披露专利的说明。任何知晓某项专利且认为其中包含必要权利要求的个人,必须根据《W3C 专利政策》第6节进行信息披露。

本文档受 2025年8月18日 W3C 流程文件 管辖。

1. 简介

本节为非规范性内容。

许多用户希望在与设备上的其他内容、站点或应用程序交互时,继续观看媒体。常见的用户界面工具是画中画(PiP),视频被包含在一个始终位于其他窗口之上的独立小窗口中。即使用户代理不可见,该窗口仍保持可见。画中画是桌面和移动操作系统中常见的平台级功能。

本规范扩展了 HTMLVideoElement, 允许网站通过暴露以下属性来启动和控制此行为:

2. 示例

2.1. 添加自定义画中画按钮

<video id="video" src="https://example.com/file.mp4"></video>

<button id="togglePipButton"></button>

<script>
  const video = document.getElementById("video");
  const togglePipButton = document.getElementById("togglePipButton");

  // 如果不支持或禁用画中画,隐藏按钮。
  togglePipButton.hidden =
    !document.pictureInPictureEnabled || video.disablePictureInPicture;

  togglePipButton.addEventListener("click", async () => {
    // 如果没有元素在画中画中,请求视频进入画中画模式,否则退出画中画。
    try {
      if (document.pictureInPictureElement) {
        await document.exitPictureInPicture();
      } else {
        await video.requestPictureInPicture();
      }
    } catch (err) {
      // 视频未能进入/退出画中画模式。
    }
  });
</script>

2.2. 监控视频画中画的变化

<video id="video" src="https://example.com/file.mp4"></video>

<script>
  const video = document.getElementById("video");

  video.addEventListener("enterpictureinpicture", (event) => {
    // 视频进入画中画模式。
    const pipWindow = event.pictureInPictureWindow;
    console.log(`画中画窗口宽度: ${pipWindow.width}`);
    console.log(`画中画窗口高度: ${pipWindow.height}`);
  });

  video.addEventListener("leavepictureinpicture", () => {
    // 视频退出画中画模式。
  });
</script>

2.3. 根据画中画窗口大小更改调整视频大小

<video id="video" src="https://example.com/file.mp4"></video>

<button id="pipButton"></button>

<script>
  const video = document.getElementById("video");
  const pipButton = document.getElementById("pipButton");

  pipButton.addEventListener("click", async () => {
    try {
      await video.requestPictureInPicture();
    } catch (error) {
      // 视频未能进入画中画模式。
    }
  });

  video.addEventListener("enterpictureinpicture", (event) => {
    // 视频进入画中画模式。
    const pipWindow = event.pictureInPictureWindow;
    updateVideoSize(pipWindow.width, pipWindow.height);
    pipWindow.addEventListener("resize", onPipWindowResize);
  });

  video.addEventListener("leavepictureinpicture", (event) => {
    // 视频退出画中画模式。
    const pipWindow = event.pictureInPictureWindow;
    pipWindow.removeEventListener("resize", onPipWindowResize);
  });

  function onPipWindowResize(event) {
    // 画中画窗口已调整大小。
    const { width, height } = event.target;
    updateVideoSize(width, height);
  }

  function updateVideoSize(width, height) {
    // TODO: 根据画中画窗口的宽度和高度调整视频大小。
  }
</script>

3. 概念

3.1. 定义

画中画窗口是一个显示 video 元素的窗口。

DocumentOrShadowRoot 具有:

  1. 一个画中画元素,它是一个 Element 或 null,初始为 null。

一个可遍历导航器具有:

  1. 一个画中画并行队列,它是一个通过并行队列 启动一个新的并行队列创建的队列。

一个用户 代理具有:

  1. 一个活动画中画 会话的发起者 列表,由零个或多个源组成,初始为空。

    注: 如果一个用户代理 支持多个画中画窗口,则该列表允许重复项。

如果活动画中画会话的发起者中的任何源 与该源是同一源域,则称该源具有活动的画中画会话。

3.2. 画中画

建议不要同时在页面和画中画窗口中渲染视频帧;但如果同时渲染,则必须保持 同步。

当视频以画中画模式播放时,状态应当像内联播放一样转换。这意味着事件应当在相同 时间触发,调用方法应当具有相同的行为,等等。不过,当 video 元素进入被认为 与画中画不兼容的状态时,用户代理可以退出画中画。

应用于 video 的样式(例如 opacity、visibility、transform 等)不得 应用到画中画窗口中。其宽高比基于视频尺寸。

还建议画中画窗口具有最大和 最小尺寸。例如,可以将其限制在屏幕某一维度的四分之一到 一半之间。

当一个 DocumentOrShadowRoot 的 画中画元素被设置时, 画中画窗口必须可见,即使该 DocumentOrShadowRoot 的 相关全局对象的关联 Document的 可见性状态为 "hidden"。 用户代理应当为用户提供一种手动关闭 画中画窗口的方式。

3.3. 退出画中画

当退出画中画算法被调用时, 用户代理必须运行以下步骤:

  1. 如果 pictureInPictureElement 为 null,则抛出一个 InvalidStateError 并 中止这些步骤。

  2. 使用与 pictureInPictureElement 关联的画中画 窗口运行关闭窗口算法。

  3. 排入一个任务,以便在 video 上使用 PictureInPictureEvent 触发一个 事件,其名称为 leavepictureinpicture, 其 bubbles 属性初始化为 true,其 pictureInPictureWindow 属性初始化为与 pictureInPictureElement 关联的画中画窗口。

  4. 取消设置 pictureInPictureElement。

  5. 从活动 画中画会话的发起者中移除一个与相关设置对象的源匹配的项目。

不建议在退出 画中画算法被调用时改变视频播放状态。如果这是由网站发起的, 则该体验应当由网站控制。不过,用户代理可以公开会改变视频播放状态 的画中画窗口控件(例如 暂停)。

作为卸载文档清理步骤之一,运行退出 画中画算法。

3.4. 禁用画中画

某些页面可能希望禁用某个 video 元素的画中画模式;例如, 在某些情况下,它们可能希望阻止用户代理建议使用 画中画上下文菜单。 为了支持这些用例,一个新的 disablePictureInPicture 属性被添加到 video 元素的内容属性列表中。

disablePictureInPicture IDL 属性必须反映同名的内容 属性。

如果 video 元素上存在 disablePictureInPicture 属性, 用户代理可以阻止该 video 元素以画中画 模式播放,或阻止显示任何用于这样做的 UI。

当 disablePictureInPicture 属性被添加到 video 元素时, 用户代理可以运行以下步骤:

  1. 用 InvalidStateError 拒绝由 requestPictureInPicture() 方法返回的任何待处理 promise。

  2. 如果 video 是 pictureInPictureElement, 则运行退出 画中画算法。

3.5. 与全屏的交互

建议在 pictureInPictureElement 的全屏标志被设置时,运行退出画中画算法。

3.6. 与页面可见性的交互

用户代理在判定 系统可见性状态 是否已发生变化时, 不得将画中画窗口的可见性纳入考虑, 无论该 可遍历可导航对象 的状态如何。

3.7. 唯一的画中画窗口

具有画中画 API 的操作系统通常限制画中画模式只能有一个窗口。是否只允许一个窗口处于画中画模式将由实现和平台决定。但由于仅有一个画中画窗口的限制,本规范假设每个 Document 只会拥有一个画中画窗口。

当已经有一个窗口处于画中画模式时再次发起画中画请求的行为,将由具体实现决定:可能会关闭当前的画中画窗口,拒绝新的画中画请求,甚至允许创建两个画中画窗口。无论哪种情况,用户代理都必须触发相应事件,以便通知网站画中画状态的变化。

4. API

4.1. 扩展 HTMLVideoElement

partial interface HTMLVideoElement {
  [NewObject] Promise<PictureInPictureWindow> requestPictureInPicture();

  attribute EventHandler onenterpictureinpicture;
  attribute EventHandler onleavepictureinpicture;

  [CEReactions] attribute boolean disablePictureInPicture;
};

requestPictureInPicture() 方法步骤请求画中画:

  1. 如果画中画支持为 false, 则返回一个被拒绝的 promise,其拒绝原因为 NotSupportedError DOMException。

  2. 令 doc 为 this 的节点文档。

  3. 如果 doc 未被允许使用名为 "picture-in-picture" 的策略控制特性, 则返回一个被拒绝的 promise,其拒绝原因为 NotAllowedError DOMException。

  4. 如果 this 的 readyState 属性为 HAVE_NOTHING, 则返回一个被拒绝的 promise,其拒绝原因为 InvalidStateError DOMException。

  5. 如果 this 没有视频轨道,则返回一个被拒绝的 promise,其拒绝原因为 InvalidStateError DOMException。

  6. 如果 this 的 disablePictureInPicture 为 true,用户代理可以返回一个被拒绝的 promise,其拒绝原因为 InvalidStateError DOMException。

  7. 如果 doc 的画中画元素为 null:

    1. 如果 this 的相关全局对象没有瞬态激活,则返回一个被拒绝的 promise,其拒绝原因为 NotAllowedError DOMException。

    2. 给定 this 的相关全局对象,消耗用户激活。

  8. 如果 this 是 doc 的画中画元素:

    1. 返回一个兑现的 promise,其兑现值为与 doc 的画中画元素关联的画中画窗口。

  9. 令 global 为 this 的相关全局对象。

  10. 令 p 为在 this 的相关领域中创建的新 promise。

  11. 返回 p,并将以下步骤入队到 doc 的画中画并行队列:

    1. 如果 this 是 doc 的画中画元素:

      1. 给定 global,在媒体元素事件任务源上排入一个全局任务,以用与 this 关联的画中画窗口 兑现 p。

      2. 中止这些步骤。

    2. 尝试将一个画中画窗口与 this 关联。

    3. 如果上一步失败:

      1. 给定 global,在媒体元素事件任务 源上排入一个全局任务,以用 InvalidStateError DOMException 拒绝 p。

      2. 中止这些步骤。

    4. 令 pipWindow 为 PictureInPictureWindow 的一个新实例,它表示 this 的关联画中画窗口。

    5. 给定 global,在媒体元素事件任务源上排入一个全局任务,以执行 以下步骤:

      1. 如果 pictureInPictureElement 不是 null,则运行退出画中画 算法。

      2. 将 doc 的画中画元素设置为 this。

      3. 将相关设置对象的源追加到活动画中画会话的发起者。

      4. 如果 this 是 fullscreenElement,则退出全屏。

      5. 在 this 上使用 PictureInPictureEvent 触发一个事件,其名称为 enterpictureinpicture, 其 bubbles 属性初始化为 true,其 pictureInPictureWindow 属性初始化为 画中画窗口。

      6. 用 pipWindow 兑现 p。

4.2. 扩展 Document

partial interface Document {
  readonly attribute boolean pictureInPictureEnabled;

  [NewObject] Promise<undefined> exitPictureInPicture();
};

pictureInPictureEnabled 属性的 getter 必须在 画中画支持为 true 且 this 被允许使用由属性名 picture-in-picture 指示的特性时,返回 true;否则返回 false。

画中画 支持在存在禁用它的用户偏好 或平台限制时为 false。否则为 true。

exitPictureInPicture() 方法在被调用时,必须 返回一个新的 promise promise,并并行运行以下步骤:

  1. 运行退出画中画算法。

  2. 如果上一步抛出了异常,则用该 异常拒绝 promise,并中止这些步骤。

  3. 兑现 promise。

4.3. 扩展 DocumentOrShadowRoot

partial interface mixin DocumentOrShadowRoot {
  readonly attribute Element? pictureInPictureElement;
};

pictureInPictureElement 属性的 getter 必须运行以下步骤:

  1. 如果 this 是一个影子根,且其宿主 未连接,则返回 null 并中止这些步骤。

  2. 令 candidate 为针对 this 重定向画中画元素的结果。

  3. 如果 candidate 和 this 位于同一树中, 则返回 candidate 并中止这些步骤。

  4. 返回 null。

4.4. 接口 PictureInPictureWindow

[Exposed=Window]
interface PictureInPictureWindow : EventTarget {
  readonly attribute long width;
  readonly attribute long height;

  attribute EventHandler onresize;
};

PictureInPictureWindow 实例表示一个与 HTMLVideoElement 关联的画中画 窗口。 实例化时,一个 PictureInPictureWindow 实例会将其 state 设置为 opened。

当带有 PictureInPictureWindow 实例的关闭窗口 算法被调用时,其 state 会被设置为 closed。

width 属性必须返回与 pictureInPictureElement 关联的画中画窗口的以 CSS 像素表示的宽度, 如果 state 为 opened。否则,它必须返回 0。

height 属性必须返回与 pictureInPictureElement 关联的画中画窗口的以 CSS 像素表示的高度, 如果 state 为 opened。否则,它必须返回 0。

当一个画中画窗口 pipWindow 的大小发生变化时, 用户代理必须 排入一个任务,以便在 pipWindow 上触发一个 事件,其名称为 resize。

4.5. 事件类型

[Exposed=Window]
interface PictureInPictureEvent : Event {
    constructor(DOMString type, PictureInPictureEventInit eventInitDict);
    [SameObject] readonly attribute PictureInPictureWindow pictureInPictureWindow;
};

dictionary PictureInPictureEventInit : EventInit {
    required PictureInPictureWindow pictureInPictureWindow;
};
enterpictureinpicture

当HTMLVideoElement进入画中画模式时触发。

leavepictureinpicture

当HTMLVideoElement离开画中画模式时触发。

resize

当PictureInPictureWindow改变大小时触发。

4.6. 任务源

任务源对于本规范中排队的所有任务是相关视频元素的媒体元素事件任务源。

4.7. CSS伪类

:picture-in-picture 伪类必须匹配画中画元素。它不同于pictureInPictureElement,因为它不适用于影子宿主链。

5. 安全性考量

本节为非规范性内容。

为了限制通过欺骗行为的潜在滥用,API 仅适用于HTMLVideoElement。 对画中画窗口的用户交互有意限制为仅影响画中画窗口本身或正在播放的媒体。

5.1. 安全上下文

该 API 不仅限于[SECURE-CONTEXTS],因为它为 Web 应用程序提供了用户代理通常在所有媒体上原生提供的功能,无论浏览上下文如何。

5.2. 权限策略

本规范定义了一个名为 "picture-in-picture" 的策略控制特性,它控制请求 画中画 是否返回 SecurityError 以及 pictureInPictureEnabled 是 true 还是 false。

该功能的默认允许列表为*。

6. 致谢

感谢 Jennifer Apacible、Zouhir Chahoud、Marcos Cáceres、Philip Jägenstedt、Jeremy Jones、Chris Needham、Jer Noble、Justin Uberti、Yoav Weiss 和 Eckhart Wörner 对本文档的贡献。

一致性

文档约定

一致性要求通过描述性断言和 RFC 2119 术语的组合来表达。 在本文档的规范部分中,关键字“MUST”、“MUST NOT”、“REQUIRED”、“SHALL”、“SHALL NOT”、“SHOULD”、“SHOULD NOT”、“RECOMMENDED”、“MAY”和“OPTIONAL”应按照 RFC 2119 中的描述进行解释。 然而,为了提高可读性,本规范中这些词不会全部大写。

除非明确标记为非规范性的章节、示例和注释,本规范的所有文本都是规范性的。[RFC2119]

本规范中的示例以“例如”开头或通过 class="example" 与规范文本区分开,如下所示:

这是一个说明性示例。

说明性注释以“注”字开头,并通过 class="note" 与规范文本区分开,如下所示:

注,这是一个说明性注释。

一致性算法

作为算法一部分的命令式要求(例如“去除任何前导空格字符”或“返回 false 并中止这些步骤”) 应按照引入算法时使用的关键字(如“必须”、“应该”、“可以”等)的含义进行解释。

以算法或特定步骤措辞的一致性要求可以通过任何方式实现,只要最终结果等效即可。 特别是,本规范中定义的算法旨在易于理解,而非高效执行。鼓励实现者进行优化。

索引

本规范定义的术语

通过引用定义的术语

参考文献

规范性引用

[CSS-VALUES-4]
Tab Atkins Jr.; Elika Etemad。CSS 数值与单位模块 4 级。2024年3月12日。工作草案。网址:https://www.w3.org/TR/css-values-4/
[DOM]
Anne van Kesteren。DOM 标准。现行标准。网址:https://dom.spec.whatwg.org/
[FULLSCREEN]
Philip Jägenstedt。全屏 API 标准。现行标准。网址:https://fullscreen.spec.whatwg.org/
[HTML]
Anne van Kesteren;等。HTML 标准。现行标准。网址:https://html.spec.whatwg.org/multipage/
[INFRA]
Anne van Kesteren;Domenic Denicola。基础标准。现行标准。网址:https://infra.spec.whatwg.org/
[PERMISSIONS-POLICY-1]
Ian Clelland。权限策略。2025年10月6日。工作草案。网址:https://www.w3.org/TR/permissions-policy-1/
[RFC2119]
S. Bradner。用于 RFC 的关键字以指示需求级别。1997年3月。最佳现行实践。网址:https://datatracker.ietf.org/doc/html/rfc2119
[SELECTORS-4]
Elika Etemad;Tab Atkins Jr.。选择器 4 级。2026年1月22日。工作草案。网址:https://www.w3.org/TR/selectors-4/
[WEBIDL]
Edgar Chen;Timothy Gu。Web IDL 标准。现行标准。网址:https://webidl.spec.whatwg.org/

参考性引用

[SECURE-CONTEXTS]
Mike West。安全环境。2023年11月10日。候选推荐。网址:https://www.w3.org/TR/secure-contexts/

IDL 索引

partial interface HTMLVideoElement {
  [NewObject] Promise<PictureInPictureWindow> requestPictureInPicture();

  attribute EventHandler onenterpictureinpicture;
  attribute EventHandler onleavepictureinpicture;

  [CEReactions] attribute boolean disablePictureInPicture;
};

partial interface Document {
  readonly attribute boolean pictureInPictureEnabled;

  [NewObject] Promise<undefined> exitPictureInPicture();
};

partial interface mixin DocumentOrShadowRoot {
  readonly attribute Element? pictureInPictureElement;
};

[Exposed=Window]
interface PictureInPictureWindow : EventTarget {
  readonly attribute long width;
  readonly attribute long height;

  attribute EventHandler onresize;
};

[Exposed=Window]
interface PictureInPictureEvent : Event {
    constructor(DOMString type, PictureInPictureEventInit eventInitDict);
    [SameObject] readonly attribute PictureInPictureWindow pictureInPictureWindow;
};

dictionary PictureInPictureEventInit : EventInit {
    required PictureInPictureWindow pictureInPictureWindow;
};