Picture-in-Picture for any Element, not just <video>

François Beaufort
François Beaufort

Browser Support

  • Chrome: 116.
  • Edge: 116.
  • Firefox: not supported.
  • Safari: not supported.

Source

The Document Picture-in-Picture API makes it possible to open an always-on-top window that can be populated with arbitrary HTML content. It extends the existing Picture-in-Picture API for <video> that only allows an HTML <video> element to be put into a Picture-in-Picture window.

The Picture-in-Picture window in the Document Picture-in-Picture API is similar to a blank same-origin window opened via window.open(), with some differences:

  • The Picture-in-Picture window floats on top of other windows.
  • The Picture-in-Picture window never outlives the opening window.
  • The Picture-in-Picture window cannot be navigated.
  • The Picture-in-Picture window position cannot be set by the website.
A Picture-in-Picture window playing Sintel trailer video.
A Picture-in-Picture window created with the Document Picture-in-Picture API (demo).

Current status

Step Status
1. Create explainer Complete
2. Create initial draft of specification In progress
3. Gather feedback & iterate on design In progress
4. Origin trial Complete
5. Launch Complete (Desktop)

Use cases

Custom video player

A website can provide a Picture-in-Picture video experience with the existing Picture-in-Picture API for <video>, however it is very limited. The existing Picture-in-Picture window accepts few inputs, and has limited ability for styling them. With a full Document in Picture-in-Picture, the website can provide custom controls and inputs (for example, captions, playlists, time scrubber, liking and disliking videos) to improve the user's Picture-in-Picture video experience.

Video conferencing

It is common for users to leave the browser tab during a video conferencing session for various reasons (for example, presenting another tab to the call or multitasking) while still wishing to see the call, so it's a prime use case for Picture-in-Picture. Once again, the current experience a video conferencing website can provide via the Picture-in-Picture API for <video> is limited in style and input. With a full Document in Picture-in-Picture, the website can easily combine multiple video streams into a single PiP window without having to rely on canvas hacks and provide custom controls such as sending a message, muting another user, or raising a hand.

Productivity

Research has shown that users need more ways to be productive on the web. Document in Picture-in-Picture gives web apps the flexibility to accomplish more. Whether it's text editing, note-taking, task lists, messaging and chat, or design and development tools, web apps can now keep their content always accessible.

Interface

Properties

documentPictureInPicture.window
Returns the current Picture-in-Picture window if any. Otherwise, returns null.

Methods

documentPictureInPicture.requestWindow(options)

Returns a promise that resolves when a Picture-in-Picture window is opened. The promise rejects if it's called without a user gesture. The options dictionary contains the optional following members:

width
Sets the initial width of the Picture-in-Picture window.
height
Sets the initial height of the Picture-in-Picture window.
disallowReturnToOpener
Hides the "back to tab" button in the Picture-in-Picture window if true. It is false by default.
preferInitialWindowPlacement
Open the Picture-in-Picture window in its default position and size if true. It is false by default.

Events

documentPictureInPicture.onenter
Fired on documentPictureInPicture when a Picture-in-Picture window is opened.

Examples

The following HTML sets up a custom video player and a button element to open the video player in a Picture-in-Picture window.

<div id="playerContainer">   <div id="player">     <video id="video"></video>   </div> </div> <button id="pipButton">Open Picture-in-Picture window</button> 

Open a Picture-in-Picture window

The following JavaScript calls documentPictureInPicture.requestWindow() when the user clicks the button to open a blank Picture-in-Picture window. The returned promise resolves with a Picture-in-Picture window JavaScript object. The video player is moved to that window using append().

pipButton.addEventListener('click', async () => {   const player = document.querySelector("#player");    // Open a Picture-in-Picture window.   const pipWindow = await documentPictureInPicture.requestWindow();    // Move the player to the Picture-in-Picture window.   pipWindow.document.body.append(player); }); 

Set the size of the Picture-in-Picture window

To set the size of the Picture-in-Picture window, set the width and height options of documentPictureInPicture.requestWindow() to the desired Picture-in-Picture window size. Chrome may clamp the option values if they are too large or too small to fit a user-friendly window size.

pipButton.addEventListener("click", async () => {   const player = document.querySelector("#player");    // Open a Picture-in-Picture window whose size is   // the same as the player's.   const pipWindow = await documentPictureInPicture.requestWindow({     width: player.clientWidth,     height: player.clientHeight,   });    // Move the player to the Picture-in-Picture window.   pipWindow.document.body.append(player); }); 

Hide the "back to tab" button of the Picture-in-Picture window

To hide the button in the Picture-in-Picture window that allows the user to go back to the opener tab, set the disallowReturnToOpener option of documentPictureInPicture.requestWindow() to true.

pipButton.addEventListener("click", async () => {   // Open a Picture-in-Picture window which hides the "back to tab" button.   const pipWindow = await documentPictureInPicture.requestWindow({     disallowReturnToOpener: true,   }); }); 

Open the Picture-in-Picture window in its default position and size

To not reuse the position or size of the previous Picture-in-Picture window, set the preferInitialWindowPlacement option of documentPictureInPicture.requestWindow() to true.

pipButton.addEventListener("click", async () => {   // Open a Picture-in-Picture window in its default position / size.   const pipWindow = await documentPictureInPicture.requestWindow({     preferInitialWindowPlacement: true,   }); }); 

Copy style sheets to the Picture-in-Picture window

To copy all CSS style sheets from the originating window, loop through styleSheets explicitly linked into or embedded in the document and append them to the Picture-in-Picture window. Note that this is a one-time copy.

pipButton.addEventListener("click", async () => {   const player = document.querySelector("#player");    // Open a Picture-in-Picture window.   const pipWindow = await documentPictureInPicture.requestWindow();    // Copy style sheets over from the initial document   // so that the player looks the same.   [...document.styleSheets].forEach((styleSheet) => {     try {       const cssRules = [...styleSheet.cssRules].map((rule) => rule.cssText).join('');       const style = document.createElement('style');        style.textContent = cssRules;       pipWindow.document.head.appendChild(style);     } catch (e) {       const link = document.createElement('link');        link.rel = 'stylesheet';       link.type = styleSheet.type;       link.media = styleSheet.media;       link.href = styleSheet.href;       pipWindow.document.head.appendChild(link);     }   });    // Move the player to the Picture-in-Picture window.   pipWindow.document.body.append(player); }); 

Handle when the Picture-in-Picture window closes

Listen to the window "pagehide" event to know when the Picture-in-Picture window gets closed (either because the website initiated it or the user manually closed it). The event handler is a good place to get the elements back out of the Picture-in-Picture window as shown here.

pipButton.addEventListener("click", async () => {   const player = document.querySelector("#player");    // Open a Picture-in-Picture window.   const pipWindow = await documentPictureInPicture.requestWindow();    // Move the player to the Picture-in-Picture window.   pipWindow.document.body.append(player);    // Move the player back when the Picture-in-Picture window closes.   pipWindow.addEventListener("pagehide", (event) => {     const playerContainer = document.querySelector("#playerContainer");     const pipPlayer = event.target.querySelector("#player");     playerContainer.append(pipPlayer);   }); }); 

Close the Picture-in-Picture window programmatically by using the close() method.

// Close the Picture-in-Picture window programmatically.  // The "pagehide" event will fire normally. pipWindow.close(); 

Listen to when the website enters Picture-in-Picture

Listen to the "enter" event on documentPictureInPicture to know when a Picture-in-Picture window is opened. The event contains a window object to access the Picture-in-Picture window.

documentPictureInPicture.addEventListener("enter", (event) => {   const pipWindow = event.window; }); 

Access elements in the Picture-in-Picture window

Access elements in the Picture-in-Picture window either from the object returned by documentPictureInPicture.requestWindow(), or with documentPictureInPicture.window as shown below.

const pipWindow = documentPictureInPicture.window; if (pipWindow) {   // Mute video playing in the Picture-in-Picture window.   const pipVideo = pipWindow.document.querySelector("#video");   pipVideo.muted = true; } 

Handle events from the Picture-in-Picture window

Create buttons and controls and respond to user's input events such as "click" as you would do normally in JavaScript.

// Add a "mute" button to the Picture-in-Picture window. const pipMuteButton = pipWindow.document.createElement("button"); pipMuteButton.textContent = "Mute"; pipMuteButton.addEventListener("click", () => {    const pipVideo = pipWindow.document.querySelector("#video");   pipVideo.muted = true; }); pipWindow.document.body.append(pipMuteButton); 

Resize the Picture-in-Picture window

Use the resizeBy() and resizeTo() Window methods to resize Picture-in-Picture window. Both methods require a user gesture.

const resizeButton = pipWindow.document.createElement('button'); resizeButton.textContent = 'Resize'; resizeButton.addEventListener('click', () => {   // Expand the Picture-in-Picture window's width by 20px and height by 30px.   pipWindow.resizeBy(20, 30); }); pipWindow.document.body.append(resizeButton); 

Focus the opener window

Use the focus() Window method to focus the opener window from the Picture-in-Picture window. This method requires a user gesture.

const returnToTabButton = pipWindow.document.createElement("button"); returnToTabButton.textContent = "Return to opener tab"; returnToTabButton.addEventListener("click", () => {   window.focus(); }); pipWindow.document.body.append(returnToTabButton); 

CSS picture-in-picture display mode

Use the CSS picture-in-picture display mode to write specific CSS rules that are only applied when (part of the) the web app is shown in Picture-in-Picture mode.

@media all and (display-mode: picture-in-picture) {   body {     margin: 0;   }   h1 {     font-size: 0.8em;   } } 

Feature detection

To check if the Document Picture-in-Picture API is supported, use:

if ('documentPictureInPicture' in window) {   // The Document Picture-in-Picture API is supported. } 

Demos

VideoJS player

You can play with the Document Picture-in-Picture API VideoJS player demo.

Pomodoro

Tomodoro, a pomodoro web app, is also taking advantage of the Document Picture-in-Picture API when available. See their GitHub pull request.

Tomodoro, a pomodoro web app.
A Picture-in-Picture window in Tomodoro.

Share your feedback

File issues on GitHub with suggestions and questions.