Signatur
Beschreibung
Die schreibgeschützte Eigenschaft pictureInPictureElement eines ShadowRoot liefert das Element, das aktuell im Bild-in-Bild-Fenster (Picture-in-Picture, kurz PiP) angezeigt wird, sofern es zu diesem Shadow-Tree gehört. Ist kein Element im PiP-Modus oder liegt es außerhalb dieses Shadow-Trees, ist der Wert null.
Die Eigenschaft ist das Gegenstück zu Document.pictureInPictureElement. Wegen der Kapselung von Shadow DOM gibt document.pictureInPictureElement bei einem Video innerhalb einer Web-Komponente nur den Shadow-Host zurück (Retargeting), nicht das eigentliche <video>. Innerhalb der Komponente fragt man deshalb this.shadowRoot.pictureInPictureElement ab, um das konkrete Element zu erhalten.
Typische Einsatzfälle sind eigene Video-Player als Custom Elements: Schaltflächen-Zustände („PiP ein/aus“) aktualisieren, prüfen, ob das eigene Video bereits schwebt, oder vor dem Entfernen der Komponente den PiP-Modus sauber beenden.
Rückgabewert
HTMLVideoElement) innerhalb dieses Shadow-Trees, sonst null.Beispiele
PiP-Umschalter in einer Web-Komponente
class VideoPlayer extends HTMLElement {
constructor() {
super();
const root = this.attachShadow({ mode: 'open' });
root.innerHTML = `
<video src="film.mp4" controls></video>
<button>Bild-in-Bild</button>`;
this.video = root.querySelector('video');
root.querySelector('button').addEventListener('click', () => this.togglePip());
}
async togglePip() {
try {
if (this.shadowRoot.pictureInPictureElement) {
await document.exitPictureInPicture();
} else {
await this.video.requestPictureInPicture();
}
} catch (err) {
console.error('PiP fehlgeschlagen:', err.message);
}
}
}
customElements.define('video-player', VideoPlayer);
Unterschied zwischen Document und ShadowRoot (Retargeting)
const player = document.querySelector('video-player');
const video = player.shadowRoot.querySelector('video');
video.addEventListener('enterpictureinpicture', () => {
console.log(document.pictureInPictureElement === player); // Host
console.log(player.shadowRoot.pictureInPictureElement === video); // echtes Video
});
button.addEventListener('click', () => video.requestPictureInPicture());
Button-Zustand synchron halten und beim Entfernen aufräumen
class PipVideo extends HTMLElement {
connectedCallback() {
const root = this.shadowRoot ?? this.attachShadow({ mode: 'open' });
root.innerHTML = '<video src="clip.mp4" controls></video><button>PiP</button>';
const video = root.querySelector('video');
const btn = root.querySelector('button');
const update = () => {
btn.textContent = root.pictureInPictureElement ? 'PiP beenden' : 'PiP starten';
};
video.addEventListener('enterpictureinpicture', update);
video.addEventListener('leavepictureinpicture', update);
btn.disabled = !document.pictureInPictureEnabled;
update();
}
disconnectedCallback() {
if (this.shadowRoot?.pictureInPictureElement) {
document.exitPictureInPicture().catch(() => {});
}
}
}
customElements.define('pip-video', PipVideo);
// Wichtig · Fallstricke
- Browser-Kompatibilität: In Chromium-Browsern (Chrome, Edge, Opera) und Safari verfügbar, Firefox unterstützt die Picture-in-Picture-API nicht (nur eine eigene UI-Funktion ohne JS-API). Vorher per
'pictureInPictureElement' in ShadowRoot.prototypebzw.document.pictureInPictureEnabledprüfen. - Die Eigenschaft ist schreibgeschützt. PiP startet man mit
HTMLVideoElement.requestPictureInPicture()und beendet es mitdocument.exitPictureInPicture(). Eine Methode dafür aufShadowRootgibt es nicht. - Es kann dokumentweit immer nur ein Element im PiP-Modus sein. Liegt es außerhalb dieses Shadow-Trees, ist der Wert
null, auch wenn gerade ein PiP-Fenster offen ist. - Bei geschlossenen Shadow-Roots (
mode: 'closed') kommt man von außen nicht anshadowRootheran. Die Referenz auf den Root muss man dann in der Komponente selbst speichern. requestPictureInPicture()braucht eine Nutzeraktion (z. B. einen Klick). Sonst wird das Promise mitNotAllowedErrorabgelehnt.- Nicht zu verwechseln mit der neueren Document Picture-in-Picture API (
documentPictureInPicture), die beliebiges HTML in einem PiP-Fenster anzeigt.