-
Notifications
You must be signed in to change notification settings - Fork 0
usePip
A cutting-edge hook that leverages the new Document Picture-in-Picture API (different from the standard Video PIP API). It allows you to portal an entire React component—complete with interactivity and styles—into a floating window that stays on top of other applications even when the user switches browser tabs.
Warning
The Document PIP API is a modern browser feature (Chrome 116+). usePip gracefully degrades by setting isSupported: false on older browsers or Firefox/Safari.
Opening a simple Picture-in-Picture window.
import { usePip } from "react-hook-lab";
function FloatingTimer() {
const { isSupported, isOpen, openPip, closePip, Pip } = usePip();
if (!isSupported) return <p>Your browser doesn't support Document PIP.</p>;
return (
<div>
<button onClick={() => isOpen ? closePip() : openPip()}>
{isOpen ? "Close PIP" : "Open Floating Timer"}
</button>
{/* The <Pip> component portals everything inside it into the floating window! */}
<Pip width={300} height={200}>
<div style={{ padding: 20, background: 'black', color: 'white' }}>
<h2>10:00</h2>
<button>Pause Timer</button>
</div>
</Pip>
</div>
);
}Using the <Pip> component to safely fallback. You can conditionally render the content in the main DOM if PIP is closed, or move it to the PIP window when opened.
import { usePip } from "react-hook-lab";
function VideoCall() {
const { isOpen, openPip, closePip, Pip } = usePip();
const VideoFeed = () => (
<div className="video-feed">
<video src="stream.mp4" autoPlay />
<button>Mute</button>
</div>
);
return (
<div>
<button onClick={() => isOpen ? closePip() : openPip()}>
Toggle Pop-out
</button>
{/* If PIP is open, render inside it. Otherwise, render normally. */}
{isOpen ? (
<Pip width={400} height={300}>
<VideoFeed />
</Pip>
) : (
<VideoFeed />
)}
</div>
);
}Returns an object of type UsePipResult containing:
-
isSupported(boolean): True if the browser supportswindow.documentPictureInPicture. -
isOpen(boolean): True if the PIP window is currently active. -
openPip((options?: UsePipOptions) => Promise<Window | undefined>): Opens the PIP window. You can pass{ width, height }as options. -
closePip(() => void): Closes the active PIP window. -
Pip(FC<UsePipOptions & { children: ReactNode }>): A React wrapper component that portals its children into the PIP window.
Unlike the legacy <video>.requestPictureInPicture() which only floats a raw video stream, usePip uses the modern documentPictureInPicture API to create a literal second browser window.
-
Initialization: When
openPip()is called, the browser creates a new floating Window object. -
CSS Syncing: The hook immediately iterates through
document.styleSheetsin the main window and copies all CSS rules (or<link>tags) over to the new PIP window's<head>. This ensures your React component looks identical in the pop-out. -
Portaling: The
<Pip>component returned by the hook is a wrapper around React'screatePortal. When it renders, it mounts itschildreninto a<div id="pip-root">inside the new floating window. All React state, Contexts, and event listeners continue to function perfectly across the window boundary!
Built with ❤️ by Saurav-TB-Pandey. | Report an Issue | NPM