Firefox 151 just lately shipped the Doc Image-in-Image API. This isn’t the identical factor because the (common?) Image-in-Image API, which pushes movies right into a resizable window that continues to be seen even after switching browser tabs or OS home windows. No, the Doc Image-in-Image API allows us to place something into the window.
I suppose we are able to consider these home windows as internet widgets. We are able to use them for floating inventory tickers, stay chat conversations, playlists, to-do lists, notes, spreadsheets — something that we’d need to carry on the display always.
The final thought is that we create a Doc Image-in-Image window (DPIP window), after which we put HTML, CSS, and JavaScript into it. It’s fairly easy when you concentrate on it, however as we discover how the Doc Image-in-Image API works, we’re going to sort out a barely extra advanced situation that you simply’ll most likely run into.
We’re going to clone a inventory ticker from the principle doc into a DPIP window. This not solely provides us a chance to speak about some related media queries and pseudo-classes, which we’ll use to write down focused CSS for the DPIP window, but it surely’s additionally a stark reminder that taking a HTML element out of context can break the CSS, so that you’ll must preserve that in thoughts.
That is stated inventory ticker:
However for it to work, you’ll must open the demo in debug mode. It is because picture-in-picture doesn’t work in nested shopping contexts similar to CodePen <iframe>s.
As well as, Safari doesn’t help the DPIP API but, so just be sure you’re utilizing Chrome or Firefox.
Prepared to start?
The JavaScript of all of it
First we have to verify if the browser helps the Doc Image-in-Image API. I think about that it’d be a nice-to-have characteristic, so why anticipate Safari help? Sadly although, there’s no approach to question whether or not or not @media (display-mode: picture-in-picture) is supported utilizing characteristic queries (@helps) as a result of the at-rule() perform is simply supported by Chrome, and any plans to help preludes (that’s this half: (display-mode: picture-in-picture)) seem to have been dropped anyway.
To do that would’ve been superior:
@helps at-rule(@media; display-mode: picture-in-picture) {
/* DPIP supported */
}
Notice: Safari Know-how Preview 251 launch notes do point out help for at-rule detection in @helps but it surely’s unclear when that can rollout.
As a substitute we’ve got to verify browser help utilizing JavaScript, eradicating the button if DPIP isn’t supported, or making it create a DPIP window whether it is supported):
if (!("documentPictureInPicture" in window)) {
/* DPIP not supported (take away button) */
doc.querySelector("button").take away();
} else {
/* DPIP supported (hear for button click on) */
doc.querySelector("button").addEventListener("click on", async () => {
/* ... */
});
}
Take into account that the Doc Image-in-Image API is a desktop-only API, so the verify above accounts for that too whereas illustrating precisely why a full-featured at-rule() perform could be so helpful.
As for creating the DPIP window, there’s one factor that we would need to do first — deal with an present DPIP window. DPIP home windows exchange present DPIP home windows, so we don’t want to fret about that a part of it, however we do must resolve what occurs if the button is clicked a second time. The code beneath closes the DPIP window if it’s already open, successfully making the button a toggle button:
doc.querySelector("button").addEventListener("click on", async () => {
/* If the DPIP window is open, shut it */
if (window.documentPictureInPicture.window) {
window.documentPictureInPicture.window.shut();
}
});
The issue is that focus all the time switches to the DPIP window, so toggling the DPIP window off may require two button clicks. One answer to that’s cloning the button into the DPIP window, however the DPIP window already has a “Shut” icon-button, so there’s no level in that. Personally, I wouldn’t do something, letting subsequent button clicks recreate the DPIP window. Actually, if the consumer strikes or resizes the DPIP window, subsequent button clicks will reset it to its authentic place and measurement (with the fitting choices).
On that notice, let’s speak about creating DPIP home windows and stated choices. It’s fairly apparent what the width and peak choices do, however notice that we are able to’t set one with out the opposite, and if we don’t set both, the browser chooses. The preferInitialWindowPlacement choice, if set to true, prevents the browser from saving the place and measurement of the DPIP window. The disallowReturnToOpener choice (not used right here), if set to true, hides the “Again to tab” icon-button (which does the identical factor because the “Shut” icon button, but additionally takes the consumer again to the originating tab).
/* Create the DPIP window */
const DPIP = await window.documentPictureInPicture.requestWindow({
width: 600,
peak: 400,
preferInitialWindowPlacement: true
});
The requestWindow() methodology of the DocumentPictureInPicture interface returns a promise (therefore why we’re utilizing async and await), which signifies that we are able to maintain the whole lot else whereas the window is being ready.
We are able to clone HTML into the DPIP window like this:
/* Choose the element */
const inventory = doc.querySelector("#inventory");
/* Clone the element and append it to the DPIP <physique> */
DPIP.doc.physique.append(inventory.cloneNode(true));
However to clone a number of parts, we’d must take a special strategy. That is what we’re going to do as we clone all <model>s and <hyperlink rel=stylesheet>s (and <script>s for those who want any, or no matter sources the DPIP window requires).
It’s fairly easy, although — use querySelectorAll() to create an array of NodeList objects and createDocumentFragment() to create an arbitrary DOM tree, earlier than looping by way of the array utilizing forEach() and cloning every node into stated off-screen doc fragment. Lastly, append the complete doc fragment to the <head> of the DPIP window, inflicting only one reflow as an alternative of a number of, which is extra performant.
And keep in mind, cloning the whole lot most likely isn’t obligatory, so alter as wanted.
/* Choose all <model>s and <hyperlink rel=stylesheet>s */
const types = doc.querySelectorAll("model, [rel=stylesheet]");
/* Create a doc fragment */
const documentFragment = doc.createDocumentFragment();
/* Clone the types and append them to the DPIP <head> */
types.forEach((factor) =>
documentFragment.append(factor.cloneNode(true))
);
/* Append the doc fragment to the DPIP <head> */
DPIP.doc.head.append(documentFragment);
Right here’s the whole JavaScript snippet from the demo, which you’ll most likely need to develop on (so as to add error dealing with, no less than):
if (!("documentPictureInPicture" in window)) {
/* DPIP not supported (take away button) */
doc.querySelector("button").take away();
} else {
/* DPIP supported (hear for button click on) */
doc.querySelector("button").addEventListener("click on", async () => {
/* Create the DPIP window */
const DPIP = await window.documentPictureInPicture.requestWindow({
width: 600,
peak: 400,
preferInitialWindowPlacement: true
});
/* Choose the element */
const inventory = doc.querySelector("#inventory");
/* Clone the element and append it to the DPIP <physique> */
DPIP.doc.physique.append(inventory.cloneNode(true));
/* Choose all <model>s and <hyperlink rel=stylesheet>s */
const types = doc.querySelectorAll("model, [rel=stylesheet]");
/* Create a doc fragment */
const documentFragment = doc.createDocumentFragment();
/* Clone the types and append them to the DPIP <head> */
types.forEach((factor) =>
documentFragment.append(factor.cloneNode(true))
);
/* Append the doc fragment to the DPIP <head> */
DPIP.doc.head.append(documentFragment);
});
}
Dealing with the CSS
Bear in mind, if taking HTML out of context (together with its CSS) and placing it in a DPIP window, guarantee that the CSS selectors aren’t too particular and are written for each contexts.
That being stated, you may need to write some focused CSS particularly for both window, and that’s the place the display-mode media question comes into it. It’s pretty self-explanatory — right here’s what I’m utilizing within the demo to regulate the container:
#inventory {
width: fit-content;
border-radius: 0.7rem;
@media (display-mode: picture-in-picture) {
width: 100%;
peak: 100%;
border-top-left-radius: 0;
border-top-right-radius: 0;
}
}
Additionally notice that the :picture-in-picture pseudo-class is for the common Image-in-Image API, not the Doc Image-in-Image API.
Wrapping up
I couldn’t consider a superb use for the vaguely named enter occasion, which fires when the DPIP window opens (to not be confused with the enterpictureinpicture occasion for normal picture-in-picture):
documentPictureInPicture.addEventListener("enter", (occasion) => {
/* DPIP window opened */
});
In any other case, I believe that’s a wrap for the Doc Image-in-Image API. It’s not a really massive or difficult API, but it surely sounds prefer it could possibly be actually helpful?

