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 assume we are able to consider these home windows as internet widgets. We will use them for floating inventory tickers, stay chat conversations, playlists, to-do lists, notes, spreadsheets — something that we’d wish to carry on the display screen 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 deal with a barely extra advanced situation that you just’ll in all probability run into.
We’re going to clone a inventory ticker from the principle doc into a DPIP window. This not solely offers us a possibility to speak about some related media queries and pseudo-classes, which we’ll use to put in writing focused CSS for the DPIP window, nevertheless it’s additionally a stark reminder that taking a HTML element out of context can break the CSS, so that you’ll have to preserve that in thoughts.
That is stated inventory ticker:
However for it to work, you’ll have to open the demo in debug mode. It’s because picture-in-picture doesn’t work in nested looking contexts resembling CodePen 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 function, so why look ahead to Safari help? Sadly although, there’s no option to question whether or not or not @media (display-mode: picture-in-picture) is supported utilizing function queries (@helps) as a result of the at-rule() perform is barely 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 */
}
Be aware: Safari Know-how Preview 251 launch notes do point out help for at-rule detection in @helps nevertheless it’s unclear when that may rollout.
As a substitute now we have 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 (pay attention for button click on) */
doc.querySelector("button").addEventListener("click on", async () => {
/* ... */
});
}
Remember 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’d wish to do first — deal with an present DPIP window. DPIP home windows substitute present DPIP home windows, so we don’t want to fret about that a part of it, however we do have to resolve what occurs if the button is clicked a second time. The code under 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 at all times switches to the DPIP window, so toggling the DPIP window off may require two button clicks. One resolution 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. In actual fact, if the consumer strikes or resizes the DPIP window, subsequent button clicks will reset it to its authentic place and dimension (with the proper choices).
On that notice, let’s discuss 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 dimension 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() technique of the DocumentPictureInPicture interface returns a promise (therefore why we’re utilizing async and await), which signifies that we are able to care for every little thing else whereas the window is being ready.
We will 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 */
DPIP.doc.physique.append(inventory.cloneNode(true));
However to clone a number of components, we’d have to take a special method. That is what we’re going to do as we clone all s and s (and s in case you 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 via the array utilizing forEach() and cloning every node into stated off-screen doc fragment. Lastly, append the complete doc fragment to the of the DPIP window, inflicting only one reflow as a substitute of a number of, which is extra performant.
And keep in mind, cloning every little thing in all probability isn’t mandatory, so modify as wanted.
/* Choose all
Right here’s the whole JavaScript snippet from the demo, which you’ll in all probability wish to increase on (so as to add error dealing with, at the very least):
if (!("documentPictureInPicture" in window)) {
/* DPIP not supported (take away button) */
doc.querySelector("button").take away();
} else {
/* DPIP supported (pay attention 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 */
DPIP.doc.physique.append(inventory.cloneNode(true));
/* Choose all
Dealing with the CSS
Keep in mind, if taking HTML out of context (together with its CSS) and placing it in a DPIP window, ensure that the CSS selectors aren’t too particular and are written for each contexts.
That being stated, you may wish 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 very good 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 feel that’s a wrap for the Doc Image-in-Image API. It’s not a really giant or sophisticated API, nevertheless it sounds prefer it might be actually helpful?









