Browser Extension · v7.13

Picture-in-Picture
for every video

A hover-reveal PiP button on every HTML5 video — including Shadow DOM players like Reddit. Auto-PiP on scroll for YouTube. Works on every site, every browser.

Chrome Firefox Safari Edge Opera Free · No Account 📺 YouTube Auto-PiP 👥 Shadow DOM

Features

PiP that actually works,
everywhere

Most browsers have a built-in PiP button buried in right-click menus. PiP Button puts it front and center — on hover — for every video on every site.

Hover-Reveal Button

A 34×34px PiP button fades in at the top-right corner of any HTML5 video when you hover over it — and disappears cleanly when you move away. No UI clutter.

Shadow DOM Support

Recursively walks Shadow DOM trees to find videos hidden inside web components — including Reddit's video player. Works where other PiP extensions fail entirely.

YouTube Auto-PiP

On YouTube watch pages, the video automatically enters PiP when you scroll it out of view — and exits PiP when you scroll back. Seamless multitasking with zero clicks.

Dual PiP API Support

Uses requestPictureInPicture() for Chromium-based browsers and webkitSetPresentationMode for WebKit/Safari — automatically picking the right API per browser.

Dynamic Page Support

A throttled MutationObserver watches the DOM for newly added videos — so videos loaded after the initial page render (infinite scroll feeds, SPAs) get a button too.

Click-Accurate Targeting

Pointer event capture ensures your click on the PiP button is never hijacked by the video player beneath it — even on YouTube's notoriously aggressive event-swallowing controls.

How It Works

Smart detection,
instant action

The extension injects a content script at document_idle that scans the page for HTML5 video elements, attaches a fixed-position button to each one, and keeps everything in sync as the page changes.

Scans the full DOM including Shadow roots

On load, the script recursively walks every Shadow DOM tree to locate all <video> elements — even those hidden inside closed web components.

Attaches a fixed-position button per video

A position: fixed button is appended directly to document.body for each video. Its position is recalculated on scroll, resize, and layout changes via ResizeObserver and requestAnimationFrame.

Show/hide via global mousemove

Rather than relying on mouseenter (which can be swallowed by Shadow DOM), the script tracks the global cursor position and checks if it falls within the video's bounding rect each frame.

Clicks captured at the window level

All pointer, click, and mousedown events are intercepted at the capture phase before they reach the video player — ensuring the PiP toggle always fires even on heavily instrumented players.

Self-cleaning on video removal

A MutationObserver watches the video's ancestor for DOM changes. If the video is removed (e.g., navigating away in an SPA), its button is immediately removed and its observer is disconnected.

🌐 Shadow DOM Traversal

The findAllVideos() function recursively walks any node, entering shadow roots via el.shadowRoot. A Set guards against circular traversal. New shadow roots opened after load are captured by patching Element.prototype.attachShadow.

📺 YouTube Integration

On /watch pages, an IntersectionObserver (50% threshold) automatically triggers PiP entry when the player scrolls off-screen. It wires to YouTube's native .ytp-pip-button in the controls bar and survives YouTube's SPA navigation via URL change detection.

📌 Position Sync

The button's left and top CSS values are recalculated each animation frame when the user scrolls or resizes the window. Off-screen videos have their button hidden via visibility: hidden rather than removed, to avoid repeated DOM mutations.

🔀 API Fallback Chain

PiP entry tries webkitSetPresentationMode('picture-in-picture') first (Safari/WebKit), then falls back to video.requestPictureInPicture() (all Chromium browsers). Both paths are guarded against paused videos, which cannot enter PiP.

Browser Compatibility

Full cross-browser support

PiP Button handles the API differences across every major browser so you don't have to think about it.

Browser PiP API Used Hover Button YouTube Auto-PiP Shadow DOM
Chrome requestPictureInPicture()
Firefox requestPictureInPicture()
Safari webkitSetPresentationMode()
Edge requestPictureInPicture()
Opera requestPictureInPicture()

Videos with disablePictureInPicture set by the site owner are intentionally skipped. Videos smaller than 100×60px are also skipped as they are unlikely to be primary content.

FAQ

Common questions

Videos inside Shadow DOM trees are isolated from the main document's styling. Injecting a button as a child of the video (or its shadow root) is often impossible or unreliable. Instead, the button is appended to document.body at position: fixed and its coordinates are continuously synced to the video's bounding rect. This approach works universally across all sites and DOM structures.

Yes. Reddit's video player renders inside a Shadow DOM. The extension patches Element.prototype.attachShadow to intercept new shadow roots as they're created, then attaches a MutationObserver inside each one to detect video elements as they load.

On YouTube /watch pages, the extension attaches an IntersectionObserver to the main video element with a 50% visibility threshold. When the video scrolls more than 50% off-screen and is playing, PiP is triggered automatically. When you scroll back and the video is at least 50% visible again, PiP exits and playback resumes inline. YouTube's SPA navigation is also monitored — the observer resets cleanly when you navigate to a new video.

The button is always rendered on hover regardless of playback state — but clicking it on a paused video has no effect. The PiP specification requires a video to be actively playing before it can enter Picture-in-Picture mode. This is a browser API restriction, not a limitation of the extension.

The DOM mutation scan is throttled — new video detection runs at most once every 600ms. Position syncing uses requestAnimationFrame to batch updates. mousemove tracking is marked passive so it never blocks scrolling. The extension is designed to be imperceptible in profiler traces on pages with heavy video content.

No. PiP Button makes zero network requests, stores nothing, and has no analytics or telemetry of any kind. It is a pure content script with no background persistence and no external communication.

Version History

Changelog

v7.13 Latest

Shadow DOM patch & YouTube SPA stability

Element.prototype.attachShadow patched to intercept late-created shadow roots

YouTube observer now cleanly disconnects and resets on SPA navigation between watch pages

Fixed edge case where button persisted after video element was removed from a dynamic feed

Blocked pointers tracked per pointerId to prevent duplicate click events on multi-touch

Button visibility now uses visibility: hidden (not display: none) for off-screen videos to reduce layout thrashing

v6.x

YouTube Auto-PiP with IntersectionObserver

Added scroll-triggered auto-PiP for YouTube watch pages at 50% threshold

YouTube native PiP button wired and repositioned to right controls bar

Survival observer keeps YouTube button visible through player DOM re-renders

v5.x

Fixed-position button architecture

Switched from in-player button injection to position: fixed overlay on document.body

Global mousemove tracking replaces unreliable mouseenter on shadow DOM elements

ResizeObserver added for accurate position sync on fluid layout changes

v1.0

Initial userscript release

Basic PiP button injected on HTML5 video hover

Dual API support: W3C requestPictureInPicture + WebKit webkitSetPresentationMode

Published to Greasy Fork as a userscript

Free Extension

Put every video
in your corner

Install PiP Button and hover any video to get an instant Picture-in-Picture toggle. No setup, no sign-in, no fuss.

Download Free Report an Issue