loading

Support

IsSupported

Chromium-only at the time of writing. Other engines have the video-element PictureInPicture, which floats a video and nothing else - a different API for a different job.

C#
@inject Bit.Butil.DocumentPictureInPicture documentPictureInPicture

var isSupported = await documentPictureInPicture.IsSupported();
Live sample
support output
Results will appear here when you interact with the samples.

Open a window and move a component into it

RequestWindow / MoveElement

The window opens empty and needs a user gesture. MoveElement takes a live element out of the page and into it - the counter below keeps counting while it floats, because it is the same DOM node and the same component. Everything is put back when the window closes, however it closes.

C#
var window = await documentPictureInPicture.RequestWindow(
    new DocumentPictureInPictureOptions { Width = 360, Height = 240 },
    onClose: () => InvokeAsync(StateHasChanged));

await window!.MoveElement(panelElement);   // <div @ref="panelElement"> ... </div>

// later - or when the user closes the window
await window.DisposeAsync();
Live sample
Width (360 px)
Height (240 px)
Copy the page's stylesheets
This panel is a Blazor component

Ticks while it floats: 0

window output
Results will appear here when you interact with the samples.

Sizing and styling the window

GetSize / AddStyleSheet / Focus / IsOpen

The window is a separate document and inherits no CSS - which is the first surprise everyone hits. CopyStyleSheets brings the page's styles over when the window opens; AddStyleSheet adds the rules that only apply while floating. The user can resize the window, so read the size rather than assume it.

C#
await window.AddStyleSheet("body { margin: 0; font: 14px system-ui; background: #111; color: #eee }");

var size = await window.GetSize();     // the user may have resized it
var open = await window.IsOpen();
await window.Focus();
Live sample
sizing output
Results will appear here when you interact with the samples.

Knowing when a window opens

SubscribeEnter

Fires whenever a document picture-in-picture window opens, including one this component did not request. Since only one such window can exist at a time, this is how another part of the app learns that the one place for it is now taken.

C#
var subscription = await documentPictureInPicture.SubscribeEnter(size =>
{
    Console.WriteLine($"a floating window opened at {size.Width}x{size.Height}");
});

await subscription!.DisposeAsync();
Live sample
enter output
Results will appear here when you interact with the samples.
Note:
Moved, not copied The element leaves the page, so leave a placeholder if the layout would collapse without it. Blazor keeps updating a moved element - the renderer holds the same node - but a re-render that replaces the element's parent markup can strand it. Move a stable container rather than something a parent component redraws around.
Warning:
One window, one gesture Opening requires a user gesture, and only one document picture-in-picture window can exist at a time. A second request while one is open comes back null rather than replacing it.

API reference

Member
Signature
Description
IsSupported
ValueTask<bool> IsSupported()
True when the runtime exposes documentPictureInPicture.requestWindow.
RequestWindow
ValueTask<DocumentPictureInPictureWindowHandle?> RequestWindow(DocumentPictureInPictureOptions? options = null, Action? onClose = null)
Opens the floating window. Needs a user gesture; null when refused.
SubscribeEnter
ValueTask<ButilSubscription?> SubscribeEnter(Action<DocumentPictureInPictureSize> handler)
Watches for a floating window opening, including one this component did not request.
DocumentPictureInPictureOptions
Width, Height, DisallowReturnToOpener, PreferInitialWindowPlacement, CopyStyleSheets
Size and behaviour. CopyStyleSheets is Butil's own convenience - the window inherits no CSS otherwise.
WindowHandle.InitialWidth / InitialHeight
int InitialWidth, int InitialHeight
The window's inner size when it opened.
WindowHandle.MoveElement
ValueTask<bool> MoveElement(ElementReference element)
Moves an element out of the page and into the window, remembering where it came from.
WindowHandle.RestoreElements
ValueTask RestoreElements()
Puts everything moved back, without closing the window.
WindowHandle.AddStyleSheet
ValueTask<bool> AddStyleSheet(string css)
Adds CSS to the floating window's document.
WindowHandle.GetSize
ValueTask<DocumentPictureInPictureSize?> GetSize()
The window's current inner size - the user can resize it.
WindowHandle.IsOpen
ValueTask<bool> IsOpen()
True while the window is still open.
WindowHandle.Focus
ValueTask<bool> Focus()
Brings the floating window to the front.
WindowHandle.DisposeAsync
ValueTask DisposeAsync()
Restores everything moved and closes the window.
An unhandled error has occurred. Reload 🗙