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

François Beaufort
François Beaufort

瀏覽器支援

  • Chrome:116。
  • Edge:116。
  • Firefox:不支援。
  • Safari:不支援。

資料來源

Document Picture-in-Picture API 可讓您開啟一律置頂的視窗,並在其中填入任意 HTML 內容。這個 API 擴充了 <video> 的現有 子母畫面 API,該 API 只允許將 HTML <video> 元素放入子母畫面視窗。

Document Picture-in-Picture API 中的子母畫面視窗與透過 window.open() 開啟的空白同源視窗類似,但也有幾項差異:

  • 子母畫面視窗會浮動顯示在其他視窗上方。
  • 子母畫面視窗一律不會比開啟的視窗還長。
  • 無法瀏覽子母畫面視窗。
  • 網站無法設定子母畫面視窗的位置。
播放 Sintel 預告片的子母畫面視窗。
使用 Document Picture-in-Picture API 建立的子母畫面視窗 (示範)。

目前狀態

步驟 狀態
1. 建立說明 完成
2. 建立規格初稿 進行中
3. 收集意見回饋並重複設計 進行中
4. 來源試用 完成
5. 啟動 完成 (電腦)

用途

自訂影片播放器

網站可以使用現有的 <video> 的子母畫面 API,提供子母畫面影片體驗,但相當有限。現有的子母畫面視窗只接受少數輸入內容,且無法提供太多樣式。透過網站提供完整的子母畫面功能,網站可提供自訂控制選項和輸入內容 (例如字幕、播放清單、時間進度控制鈕、對影片按讚和按倒讚),藉此改善使用者的子母畫面影片體驗。

視訊會議

使用者在視訊會議期間,常會因各種原因 (例如在通話中顯示其他分頁或進行多工處理) 而離開瀏覽器分頁,但仍希望能查看通話內容,因此這類情況是子母畫面的主要用途。再次提醒,透過 <video> 的 Picture-in-Picture API 提供的視訊會議網站目前體驗,在樣式和輸入方式上受到限制。透過 Picture-in-Picture 中的完整文件,網站可以輕鬆將多個影片串流合併為單一 PiP 視窗,而不必仰賴畫布駭客攻擊,並提供傳送訊息、將其他使用者靜音或舉手等自訂控制選項。

效率提升

研究顯示,使用者需要更多方式在網路上提高工作效率。子母畫面中的文件可靈活地透過網頁應用程式完成更多工作。無論是文字編輯、筆記、工作清單、訊息和聊天,還是設計和開發工具,網頁應用程式現在都能讓使用者隨時存取內容。

介面

屬性

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

方法

documentPictureInPicture.requestWindow(options)

在開啟子母畫面視窗時,會傳回解析的承諾。如果未在使用者手勢的情況下呼叫,承諾會拒絕。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>

開啟子母畫面視窗

當使用者點選按鈕開啟空白的 Picture-in-Picture 視窗時,下列 JavaScript 會呼叫 documentPictureInPicture.requestWindow()。傳回的 promise 會以 Picture-in-Picture 視窗 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,
  });
});

將樣式表複製到子母畫面視窗

如要從原始視窗複製所有 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() 視窗方法,調整子母畫面視窗的大小。這兩種方法都需要使用者手勢。

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() 視窗方法,從子母畫面視窗聚焦開啟視窗。這個方法需要使用者手勢。

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

CSS 子母畫面顯示模式

使用 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.
}

示範

VideoJS 播放器

如要暢玩 Document Picture-in-Picture API,即可享受 VideoJS 播放器示範影片。請務必查看原始碼

Pomodoro

Tomodoro 是 pomodoro 網頁應用程式,在可用時也會利用 Document Picture-in-Picture API。查看他們的 GitHub 提取要求

Tomodoro,一個 pomodoro 網頁應用程式。
Tomodoro 中的子母畫面視窗。

提供意見

在 GitHub 回報問題,提供建議和問題。