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
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 below.
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. Be sure to check out the source code.
Pomodoro
Tomodoro, a pomodoro web app, is also taking advantage of the Document Picture-in-Picture API when available (see GitHub pull request).
Feedback
Please file issues on GitHub with suggestions and questions.
Useful links
- Public explainer
- WICG specification
- Chromium tracking bug
- ChromeStatus.com entry
- Blink Component:
Blink>Media>PictureInPicture
- TAG Review
- Intent to Experiment
- Intent to Ship
Acknowledgements
Hero image by Jakob Owens.