画中画

W3C 工作草案,

更多关于本文档的详细信息
此版本:
https://www.w3.org/TR/2026/WD-picture-in-picture-20260303/
最新发布版本:
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)
前任编辑:
(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. 内部槽位定义

一个用户代理拥有:

  1. 一个活动画中画会话的发起者列表,包含零个或多个来源,该列表初始为空。

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

如果在活动画中画会话的发起者中的任何来源与原始来源同源域,则该来源被认为有一个活动的画中画会话。

3.2. 请求画中画

当带有video请求画中画算法被调用时, 用户代理必须运行以下步骤:

  1. 如果画中画支持false,则抛出NotSupportedError并中止这些步骤。

  2. 如果文档不被允许使用名为受政策控制的功能"picture-in-picture",则抛出SecurityError并中止这些步骤。

  3. 如果videoreadyState属性为HAVE_NOTHING,则抛出InvalidStateError并中止这些步骤。

  4. 如果video没有视频轨道,则抛出InvalidStateError并中止这些步骤。

  5. 如果videodisablePictureInPicturetrue,则用户代理可以抛出InvalidStateError并中止这些步骤。

  6. 如果 pictureInPictureElementnull

    1. 如果 this相关全局对象(relevant global object) 不具有 瞬时激活(transient activation),则抛出一个 NotAllowedError 并中止这些步骤。

    2. 消耗用户激活(Consume user activation) 的方式,给定 this相关全局对象(relevant global object)

  7. 如果videopictureInPictureElement,则中止这些步骤。

  8. pictureInPictureElement设置为video

  9. 画中画窗口设为一个与pictureInPictureElement关联的PictureInPictureWindow新实例。

  10. 相关设置对象来源附加到活动画中画会话的发起者

  11. 排队一个任务触发一个事件,该事件名为enterpictureinpicture,使用PictureInPictureEvent,其bubbles属性初始化为truepictureInPictureWindow属性初始化为画中画窗口

  12. 如果pictureInPictureElement全屏元素,建议退出全屏

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

当视频在画中画模式中播放时,状态应过渡为类似于内嵌播放。这意味着事件应该同时触发,调用方法应具有相同的行为,等等。然而,当视频元素进入与画中画不兼容的状态时,用户代理可能会退出画中画模式。

应用于video的样式(例如不透明度、可见性、变换等)不得应用于画中画窗口。其纵横比基于视频大小。

还建议画中画窗口具有最大和最小尺寸。例如,它可以被限制为屏幕尺寸的四分之一到二分之一之间。

当一个 DocumentOrShadowRootpictureInPictureElement 属性已被设置时, 即使该 DocumentOrShadowRoot相关全局对象(relevant global object)关联 Document(associated Document)可见性状态(visibility state) 为 "hidden",画中画(Picture-in-Picture)窗口也必须(MUST)保持可见。 用户代理(user agent)应当(SHOULD)为用户提供一种手动关闭画中画窗口的方式。

3.3. 退出画中画

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

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

  2. 针对与 pictureInPictureElement 相关联的 画中画(Picture-in-Picture) 窗口,运行 关闭窗口算法(close window algorithm)

  3. 将一个任务入队(Queue a task),以在 video触发(fire)一个事件, 该事件名为 leavepictureinpicture, 使用 PictureInPictureEvent, 其 bubbles 属性被初始化为 true,其 pictureInPictureWindow 属性被初始化为与 pictureInPictureElement 相关联的 画中画(Picture-in-Picture)窗口

  4. 取消设置(Unset) pictureInPictureElement

  5. 活动画中画会话的发起方(initiators of active Picture-in-Picture sessions) 中,移除一个与 相关设置对象(relevant settings object)源(origin)相匹配的项(item)

不建议在调用退出画中画算法时更改视频播放状态。如果由网站发起,网站应控制体验。然而,用户代理可以暴露画中画窗口控件以更改视频播放状态(例如暂停)。

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

3.4. 禁用画中画

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

disablePictureInPictureID属性必须反映相同名称的内容属性。

如果视频元素上存在disablePictureInPicture属性,用户代理可以阻止视频元素在画中画模式下播放或阻止任何相关的用户界面。

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

  1. 拒绝requestPictureInPicture()方法返回的所有待处理承诺,并抛出InvalidStateError

  2. 如果videopictureInPictureElement,运行退出画中画算法

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() 方法在调用时,必须返回一个新的承诺promise并并行运行以下步骤:

  1. video为调用该方法的视频元素。

  2. 运行请求画中画算法,使用video

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

  4. 使用与pictureInPictureElement关联的画中画窗口解析promise

4.2. 扩展 Document

partial interface Document {
  readonly attribute boolean pictureInPictureEnabled;

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

pictureInPictureEnabled 属性的getter必须返回true,如果画中画支持true,且this允许使用由属性名称picture-in-picture指示的功能,否则返回false

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

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

  1. 运行退出画中画算法

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

  3. 解析promise

4.3. 扩展 DocumentOrShadowRoot

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

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

  1. 如果 this 是一个 shadow root,且它的 host 未被 连接(connected),则返回 null 并中止这些步骤。

  2. candidate 为将 Picture-in-Picture 元素相对于 this 进行重定向(retargeting)的结果。

  3. 如果 candidatethis 位于同一棵 树(tree) 中, 则返回 candidate 并中止这些步骤。

  4. 返回 null

4.4. 接口 PictureInPictureWindow

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

  attribute EventHandler onresize;
};

PictureInPictureWindow 实例表示一个与 HTMLVideoElement 相关联的 画中画(Picture-in-Picture) 窗口。在被实例化时, PictureInPictureWindow 实例的 state 被设置为 opened

当以某个 PictureInPictureWindow 的实例作为参数调用 关闭窗口 算法(close window algorithm) 时,它的 state 被设置为 closed

width 属性在 stateopened 时,必须(MUST)返回与 pictureInPictureElement 相关联的 画中画窗口(Picture-in-Picture window) 的宽度(以 CSS 像素为单位)。否则,必须返回 0。

height 属性在 stateopened 时,必须(MUST)返回与 pictureInPictureElement 相关联的 画中画窗口(Picture-in-Picture window) 的高度(以 CSS 像素为单位)。否则,必须返回 0。

当某个 画中画窗口(Picture-in-Picture window) pipWindow 的尺寸发生变化时, 用户代理必须(MUST)将一个任务入队(queue a task),以在 pipWindow触发(fire)一个事件, 该事件名为 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,以及pictureInPictureEnabledtrue还是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;
};