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 (PiP) window.
The Picture-in-Picture window in the Document Picture-in-Picture API is similar
to a blank same-origin window opened using 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.
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
You could use this API in a number of ways, including custom video players, video conferencing, and productivity apps.
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 PiP 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 PiP video experience.
Video conferencing
Users often, temporarily leave the browser tab during a video conferencing session, such as when presenting from another tab to the call, taking notes, or other multi-tasking activities. However, in most cases, the user still wants to see the call, thus this is an ideal use case for Picture-in-Picture. Once again, the current experience a video conferencing website can provide with 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 relying 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 ideal PiP window size. Chrome may reduce 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 in PiP 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 PiP to 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 PiP
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 PiP 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 PiP
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 PiP window
Access elements in the Picture-in-Picture window rom the object returned by documentPictureInPicture.requestWindow(), or with documentPictureInPicture.window.
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 PiP window
Create buttons and controls and respond to user's input events (such as
"click"), as always 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 PiP 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 PiP 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: Play with the Document Picture-in-Picture API VideoJS player demo.
- Tomodoro, a pomodoro web app, takes 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.