任何元素的子母畫面,而不只是 <影片

François Beaufort
François Beaufort

Browser Support

  • Chrome: 116.
  • Edge: 116.
  • Firefox: not supported.
  • Safari: not supported.

Source

文件子母畫面 API 可開啟永遠置頂的視窗,並填入任意 HTML 內容。這項 API 擴充了現有的子母畫面 API,<video>只允許將 HTML <video> 元素放入子母畫面視窗。

Document Picture-in-Picture API 中的子母畫面視窗與使用 window.open() 開啟的空白同源視窗類似,但有以下差異:

  • 子母畫面視窗會浮動在其他視窗上方。
  • 子母畫面視窗的生命週期不會超過開啟視窗。
  • 無法瀏覽子母畫面視窗。
  • 網站無法設定子母畫面視窗位置。
子母畫面視窗正在播放 Sintel 預告片。
使用 Document Picture-in-Picture API 建立的子母畫面視窗 (示範)。

狀態

步驟 狀態
1. 建立說明 完成
2. 草擬規格初稿 In progress
3. 收集意見回饋並逐步調整設計 In progress
4. 來源試用 完成
5. 啟動 完成 (電腦版)

用途

這項 API 的用途十分廣泛,包括自訂影片播放器、視訊會議和生產力應用程式。

自訂影片播放器

網站可透過現有的 <video> 子母畫面 API 提供子母畫面影片體驗,但功能非常有限。現有的子母畫面視窗接受的輸入內容很少,且樣式設定能力有限。如果文件以子母畫面模式顯示,網站可以提供自訂控制項和輸入內容 (例如字幕、播放清單、時間軸、喜歡和不喜歡的影片),提升使用者的子母畫面影片體驗。

視訊會議

使用者在視訊會議期間經常會暫時離開瀏覽器分頁,例如從其他分頁向通話對象展示畫面、做筆記,或是執行其他多工活動。不過,在大多數情況下,使用者仍想看到通話畫面,因此子母畫面是理想的用途。再次提醒,目前視訊會議網站透過 <video> 適用的子母畫面 API 提供的體驗,在樣式和輸入方面都有限制。有了子母畫面中的完整文件,網站就能輕鬆將多個影片串流合併到單一子母畫面視窗,不必依賴 canvas 破解,並提供自訂控制項,例如傳送訊息、將其他使用者設為靜音或舉手。

效率提升

研究顯示,使用者需要更多網頁生產力工具。網頁應用程式可透過子母畫面中的文件,彈性完成更多工作。無論是文字編輯、記事、工作清單、訊息和即時通訊,還是設計和開發工具,網頁應用程式現在都能確保內容隨時可存取。

介面

屬性

documentPictureInPicture.window
傳回目前的子母畫面視窗 (如有)。否則會傳回 null

方法

documentPictureInPicture.requestWindow(options)

傳回在開啟子母畫面視窗時會解析的 Promise。 如果沒有使用者手勢就呼叫這個方法,Promise 會遭到拒絕。 options 字典包含下列選用成員:

width
設定子母畫面視窗的初始寬度。
height
設定子母畫面視窗的初始高度。
disallowReturnToOpener
如果為 true,則隱藏子母畫面視窗中的「返回分頁」按鈕。預設值為 false。
preferInitialWindowPlacement
如果為 true,則以預設位置和大小開啟子母畫面視窗。預設值為 false。

事件

documentPictureInPicture.onenter
開啟子母畫面視窗時,會在 documentPictureInPicture 上觸發。

範例

下列 HTML 會設定自訂影片播放器和按鈕元素,以便在子母畫面視窗中開啟影片播放器。

<div id="playerContainer">
  <div id="player">
    <video id="video"></video>
  </div>
</div>
<button id="pipButton">Open Picture-in-Picture window</button>

開啟子母畫面視窗

使用者點選按鈕時,下列 JavaScript 會呼叫 documentPictureInPicture.requestWindow(),開啟空白的子母畫面視窗。傳回的 Promise 會使用子母畫面視窗 JavaScript 物件解析。影片播放器會使用 append() 移至該視窗。

pipButton.addEventListener('click', async () => {
  const player = document.querySelector("#player");

  // Open a Picture-in-Picture window.
  const pipWindow = await documentPictureInPicture.requestWindow();

  // Move the player to the Picture-in-Picture window.
  pipWindow.document.body.append(player);
});

設定子母畫面視窗大小

如要設定子母畫面視窗大小,請將 documentPictureInPicture.requestWindow()widthheight 選項設為理想的子母畫面視窗大小。如果選項值太大或太小,無法配合使用者友善的視窗大小,Chrome 可能會縮減選項值。

pipButton.addEventListener("click", async () => {
  const player = document.querySelector("#player");

  // Open a Picture-in-Picture window whose size is
  // the same as the player's.
  const pipWindow = await documentPictureInPicture.requestWindow({
    width: player.clientWidth,
    height: player.clientHeight,
  });

  // Move the player to the Picture-in-Picture window.
  pipWindow.document.body.append(player);
});

隱藏子母畫面視窗中的「返回分頁」按鈕

如要隱藏子母畫面視窗中的按鈕,讓使用者無法返回開啟器分頁,請將 documentPictureInPicture.requestWindow()disallowReturnToOpener 選項設為 true

pipButton.addEventListener("click", async () => {
  // Open a Picture-in-Picture window which hides the "back to tab" button.
  const pipWindow = await documentPictureInPicture.requestWindow({
    disallowReturnToOpener: true,
  });
});

以預設位置和大小開啟子母畫面

如要避免重複使用先前子母畫面視窗的位置或大小,請將 documentPictureInPicture.requestWindow()preferInitialWindowPlacement 選項設為 true

pipButton.addEventListener("click", async () => {
  // Open a Picture-in-Picture window in its default position / size.
  const pipWindow = await documentPictureInPicture.requestWindow({
    preferInitialWindowPlacement: true,
  });
});

將樣式表複製到 PiP

如要從來源視窗複製所有 CSS 樣式表,請透過明確連結或內嵌至文件中的 styleSheets 迴圈,並將這些樣式表附加至子母畫面視窗。請注意,這項作業只會執行一次。

pipButton.addEventListener("click", async () => {
  const player = document.querySelector("#player");

  // Open a Picture-in-Picture window.
  const pipWindow = await documentPictureInPicture.requestWindow();

  // Copy style sheets over from the initial document
  // so that the player looks the same.
  [...document.styleSheets].forEach((styleSheet) => {
    try {
      const cssRules = [...styleSheet.cssRules].map((rule) => rule.cssText).join('');
      const style = document.createElement('style');

      style.textContent = cssRules;
      pipWindow.document.head.appendChild(style);
    } catch (e) {
      const link = document.createElement('link');

      link.rel = 'stylesheet';
      link.type = styleSheet.type;
      link.media = styleSheet.media;
      link.href = styleSheet.href;
      pipWindow.document.head.appendChild(link);
    }
  });

  // Move the player to the Picture-in-Picture window.
  pipWindow.document.body.append(player);
});

處理子母畫面視窗關閉時的情況

監聽視窗 "pagehide" 事件,即可瞭解子母畫面視窗何時關閉 (可能是網站啟動或使用者手動關閉)。事件處理常式是從子母畫面視窗中取回元素的好地方,如下所示。

pipButton.addEventListener("click", async () => {
  const player = document.querySelector("#player");

  // Open a Picture-in-Picture window.
  const pipWindow = await documentPictureInPicture.requestWindow();

  // Move the player to the Picture-in-Picture window.
  pipWindow.document.body.append(player);

  // Move the player back when the Picture-in-Picture window closes.
  pipWindow.addEventListener("pagehide", (event) => {
    const playerContainer = document.querySelector("#playerContainer");
    const pipPlayer = event.target.querySelector("#player");
    playerContainer.append(pipPlayer);
  });
});

使用 close() 方法,以程式輔助方式關閉子母畫面視窗。

// Close the Picture-in-Picture window programmatically.
// The "pagehide" event will fire normally.
pipWindow.close();

聆聽網站進入子母畫面模式的音效

監聽 documentPictureInPicture 上的 "enter" 事件,瞭解何時開啟子母畫面視窗。事件包含 window 物件,可存取子母畫面視窗。

documentPictureInPicture.addEventListener("enter", (event) => {
  const pipWindow = event.window;
});

存取子母畫面視窗中的元素

透過 documentPictureInPicture.requestWindow() 傳回的物件或 documentPictureInPicture.window,存取子母畫面視窗中的元素。

const pipWindow = documentPictureInPicture.window;
if (pipWindow) {
  // Mute video playing in the Picture-in-Picture window.
  const pipVideo = pipWindow.document.querySelector("#video");
  pipVideo.muted = true;
}

處理子母畫面視窗的事件

建立按鈕和控制項,並回應使用者輸入事件 (例如 "click"),就像在 JavaScript 中一樣。

// Add a "mute" button to the Picture-in-Picture window.
const pipMuteButton = pipWindow.document.createElement("button");
pipMuteButton.textContent = "Mute";
pipMuteButton.addEventListener("click", () => {
  const pipVideo = pipWindow.document.querySelector("#video");
  pipVideo.muted = true;
});
pipWindow.document.body.append(pipMuteButton);

調整子母畫面視窗大小

使用 resizeBy()resizeTo() Window 方法調整子母畫面視窗大小。這兩種方法都需要使用者手勢。

const resizeButton = pipWindow.document.createElement('button');
resizeButton.textContent = 'Resize';
resizeButton.addEventListener('click', () => {
  // Expand the Picture-in-Picture window's width by 20px and height by 30px.
  pipWindow.resizeBy(20, 30);
});
pipWindow.document.body.append(resizeButton);

聚焦在開啟器視窗

使用 focus() Window 方法,從子母畫面視窗聚焦在開啟器視窗。 這個方法需要使用者手勢。

const returnToTabButton = pipWindow.document.createElement("button");
returnToTabButton.textContent = "Return to opener tab";
returnToTabButton.addEventListener("click", () => {
  window.focus();
});
pipWindow.document.body.append(returnToTabButton);

CSS PiP 顯示模式

使用 CSS picture-in-picture 顯示模式,編寫僅在網頁應用程式 (部分) 以子母畫面模式顯示時套用的特定 CSS 規則。

@media all and (display-mode: picture-in-picture) {
  body {
    margin: 0;
  }
  h1 {
    font-size: 0.8em;
  }
}

特徵偵測

如要檢查是否支援 Document Picture-in-Picture API,請使用:

if ('documentPictureInPicture' in window) {
  // The Document Picture-in-Picture API is supported.
}

示範

Tomodoro:蕃茄工作法網頁應用程式。
Tomodoro 的子母畫面視窗。

提供意見

歡迎前往 GitHub 提報問題,並提供建議和提出疑問。