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.
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
optionsdictionary 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
documentPictureInPicturewhen 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.
Share your feedback
File issues on GitHub with suggestions and questions.