Implementing interactive floating windows using Picture-in-Picture API
Imagine watching a tutorial on a streaming platform or a video on YouTube and needing to navigate other sections of the page without losing track of what you are watching. Among all the many ways to accomplish this, putting the video in a floating window is one of the most convenient ones.
- What is Picture-in-Picture API?
- Floating windows beyond videos
- Implementing the thing
- The blank screen of PiP
- Putting back the content in the parent window
- In closing
What is Picture-in-Picture API?
Floating windows are essentially a way to display a window on top of other windows persistently. These windows are usually small and stuck in one of the corners of the screen. Of course, you can drag the window wherever you want. You might already know that this sort of floating, always-on-top video windows can be implemented using the Picture-in-Picture API.
This is pretty common these days but I recently discovered a part of this API—Document Picture-in-Picture API—can be extended to put any part of an HTML page in a floating window and I instantly had an idea to implement this in one of my projects.
Floating windows beyond videos
As I said previously, using the Document Picture-in-Picture API, you can put any part of an HTML page in a floating window. Now, I have a Notepad app which is essentially a progressive web app and I wanted to do something with it using the PiP API.
I thought why not put the text area in a floating window and let the user interact with it on top of all the other windows? In my opinion, it can prove to be pretty useful to put down notes conveniently across other apps.
So, I embarked on a journey to make this a reality!
Implementing the thing
First things first, I needed to put an icon on the UI somewhere that can be used to trigger the floating window. Here’s what that looks like.
The one thing I needed to take care of was to make sure this icon only appears when the Picture-in-Picture API is supported in the browser.
So, I made the icon hidden by default, and using the following piece of code, I made it visible only when the API is supported.
if ('documentPictureInPicture' in window) {
$('#pipButtonContainer').show();
}
Next, I added a click event listener to the icon and used the documentPictureInPicture.requestWindow()
method to create a floating window like so.
const pipButton = document.getElementById('pip');
pipButton.addEventListener('click', async() => {
// Open a Picture-in-Picture window.
const pipWindow = await documentPictureInPicture.requestWindow({
width: 350,
height: 500,
});
});
As you can tell, we can pass a width
and height
to the requestWindow()
method to specify the size of the floating window when it is created.
Once we acquire the floating window, it’s time to put the content inside it. In my case, I just need to put the textarea of my Notepad app inside the floating window. So, I grabbed the textarea container by its ID and appended it in the body of the floating window like so.
pipButton.addEventListener('click', async() => {
// Open a Picture-in-Picture window.
const pipWindow = await documentPictureInPicture.requestWindow({
width: 350,
height: 500,
});
// Grab the textarea container by ID.
const appTextarea = document.getElementById("textareaContainer");
// Move the textarea to the Picture-in-Picture window.
pipWindow.document.body.append(appTextarea);
});
And that’s all I needed to do to put the textarea “magically” in a floating window and call it a day! But I would be lying if I said that I was all but done since that was only like 50% of the work.
The blank screen of PiP
Since we are appending the textarea container to the floating window, the browser will take out that part of the page and port it over to the floating window and the parent window will left with the space without any content—making it pretty bland.
Here’s what it looks like.
To combat this issue, we can use the enter
event of documentPictureInPicture
object to detect when the floating window is triggered and apply some classes to the wrapping container of the textarea which is still there in the parent window.
documentPictureInPicture.addEventListener("enter", (event) => {
const mainContainer = document.querySelector("#mainContainer");
mainContainer.classList.add("pip");
});
As you can tell, I’m adding a class called pip
to the main container of the page when the floating window is activated. Now, we can use this class to style the empty area of the floating window.
.pip {
display: flex;
align-items: center;
justify-content: center;
height: 100%;
color: #7e7d7d;
font-weight: bold;
&::after {
content: "Floating Window is Activated";
}
}
So, once the pip
class is applied to the wrapper container, we can show a message that says “Floating Window is Activated” in the center of the parent window. This can effectively convey to the user that the floating window is activated and they can interact with it and as an added bonus, the blank area is not blank anymore.
Putting back the content in the parent window
The last thing I needed to do was to put the textarea container back when the floating window was closed.
For this, we can use the pagehide
event on the acquired floating window object. We can then grab the textarea container from the floating window and append it back to the parent window in the same click event handler like so.
pipButton.addEventListener('click', async() => {
// Open a Picture-in-Picture window.
const pipWindow = await documentPictureInPicture.requestWindow({
width: 350,
height: 500,
});
// code commented out for brevity
// Move the textarea back when the Picture-in-Picture window closes.
pipWindow.addEventListener("pagehide", (event) => {
const mainContainer = document.querySelector("#mainContainer");
const textareaContainer = event.target.querySelector("#textareaContainer");
mainContainer.append(textareaContainer);
mainContainer.classList.remove("pip");
});
});
You may have also noticed that I’m removing the pip
class from the main container since we don’t need the “Floating Window is Activated” message shown anymore.
In closing
And that’s pretty much it. I did a few extra things like adding an overlay to the parent window and disabling all the pointer events so that the user can’t interact with the parent window until the floating window is closed.
If you’re interested, you can refer to this commit on Notepad’s GitHub repository to know more about it.
Like this article?
Buy me a coffee👋 Hi there! I'm Amit. I write articles about all things web development. You can become a sponsor on my blog to help me continue my writing journey and get your brand in front of thousands of eyes.