Picture-in-picture para qualquer elemento, não apenas <video>

François Beaufort
François Beaufort

Browser Support

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

Source

A API Document Picture-in-Picture permite abrir uma janela sempre no topo que pode ser preenchida com conteúdo HTML arbitrário. Ela estende a API Picture-in-Picture para <video>, que permite apenas que um elemento HTML <video> seja colocado em uma janela picture-in-picture (PiP).

A janela picture-in-picture na API Document Picture-in-Picture é semelhante a uma janela em branco de mesma origem aberta usando window.open(), com algumas diferenças:

  • A janela picture-in-picture flutua sobre outras janelas.
  • A janela picture-in-picture nunca dura mais do que a janela de abertura.
  • Não é possível navegar na janela picture-in-picture.
  • A posição da janela picture-in-picture não pode ser definida pelo site.
Uma janela picture-in-picture reproduzindo o trailer do vídeo Sintel.
Uma janela picture-in-picture criada com a API Document Picture-in-Picture (demonstração).

Status

Etapa Status
1. Criar explicação Concluído
2. Criar o rascunho inicial da especificação Em andamento
3. Coletar feedback e iterar o design Em andamento
4. Teste de origem Concluído
5. Iniciar Concluído (computador)

Casos de uso

Você pode usar essa API de várias maneiras, incluindo players de vídeo personalizados, videoconferências e apps de produtividade.

Player de vídeo personalizado

Um site pode oferecer uma experiência de vídeo picture-in-picture com a API Picture-in-Picture para <video>, mas ela é muito limitada. A janela picture-in-picture atual aceita poucos comandos e tem capacidade limitada de estilização. Com um documento completo em picture-in-picture, o site pode fornecer controles e entradas personalizados (por exemplo, legendas, playlists, barra de tempo, marcação de "Gostei" e "Não gostei" em vídeos) para melhorar a experiência de vídeo PiP do usuário.

Videoconferências

Os usuários costumam sair temporariamente da guia do navegador durante uma sessão de videoconferência, por exemplo, ao fazer uma apresentação de outra guia para a chamada, fazer anotações ou realizar outras atividades multitarefa. No entanto, na maioria dos casos, o usuário ainda quer ver a chamada. Portanto, esse é um caso de uso ideal para o picture-in-picture. Mais uma vez, a experiência atual que um site de videoconferência pode oferecer com a API Picture-in-Picture para <video> é limitada em estilo e entrada. Com um documento completo em picture-in-picture, o site pode combinar facilmente vários fluxos de vídeo em uma única janela PiP, sem depender de hacks de tela, e fornecer controles personalizados, como enviar uma mensagem, silenciar outro usuário ou levantar a mão.

Produtividade

Pesquisas mostram que os usuários precisam de mais maneiras de serem produtivos na Web. O recurso "Documento em picture-in-picture" oferece aos apps da Web a flexibilidade para fazer mais. Seja para edição de texto, anotações, listas de tarefas, mensagens e chat ou ferramentas de design e desenvolvimento, os apps da Web agora podem manter o conteúdo sempre acessível.

Interface

Propriedades

documentPictureInPicture.window
Retorna a janela picture-in-picture atual, se houver. Caso contrário, retorna null.

Métodos

documentPictureInPicture.requestWindow(options)

Retorna uma promessa que é resolvida quando uma janela picture-in-picture é aberta. A promessa será rejeitada se for chamada sem um gesto do usuário. O dicionário options contém os seguintes membros opcionais:

width
Define a largura inicial da janela picture-in-picture.
height
Define a altura inicial da janela picture-in-picture.
disallowReturnToOpener
Oculta o botão "Voltar à guia" na janela picture-in-picture se for verdadeiro. O padrão é "false".
preferInitialWindowPlacement
Abra a janela picture-in-picture na posição e no tamanho padrão se for verdadeiro. O valor padrão é "false".

Eventos

documentPictureInPicture.onenter
Disparado em documentPictureInPicture quando uma janela picture-in-picture é aberta.

Exemplos

O HTML a seguir configura um player de vídeo personalizado e um elemento de botão para abrir o player em uma janela picture-in-picture.

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

Abrir uma janela picture-in-picture

O JavaScript a seguir chama documentPictureInPicture.requestWindow() quando o usuário clica no botão para abrir uma janela Picture-in-Picture em branco. A promessa retornada é resolvida com um objeto JavaScript de janela picture-in-picture. O player de vídeo é movido para essa janela usando 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);
});

Definir o tamanho da janela picture-in-picture

Para definir o tamanho da janela picture-in-picture, defina as opções width e height de documentPictureInPicture.requestWindow() para o tamanho ideal da janela PiP. O Chrome pode reduzir os valores das opções se eles forem muito grandes ou pequenos para caber em um tamanho de janela fácil de usar.

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);
});

Ocultar o botão "Voltar à guia" na janela picture-in-picture

Para ocultar o botão na janela picture-in-picture que permite ao usuário voltar à guia de abertura, defina a opção disallowReturnToOpener de documentPictureInPicture.requestWindow() como 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,
  });
});

Abrir o PiP na posição e no tamanho padrão

Para não reutilizar a posição ou o tamanho da janela picture-in-picture anterior, defina a opção preferInitialWindowPlacement de documentPictureInPicture.requestWindow() como true.

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

Copiar folhas de estilo para o PiP

Para copiar todas as folhas de estilo CSS da janela de origem, faça um loop em styleSheets explicitamente vinculadas ou incorporadas ao documento e anexe-as à janela picture-in-picture. Essa é uma cópia única.

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);
});

Processar quando a janela PIP for fechada

Ouça o evento "pagehide" da janela para saber quando a janela picture-in-picture é fechada (porque o site iniciou ou o usuário fechou manualmente). O gerenciador de eventos é um bom lugar para extrair os elementos da janela picture-in-picture, como mostrado aqui.

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);
  });
});

Feche a janela picture-in-picture de forma programática usando o método close().

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

Ouvir quando o site entra no modo PiP

Detecte o evento "enter" em documentPictureInPicture para saber quando uma janela picture-in-picture é aberta. O evento contém um objeto window para acessar a janela picture-in-picture.

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

Acessar elementos na janela picture-in-picture

Acesse elementos na janela picture-in-picture do objeto retornado por documentPictureInPicture.requestWindow() ou com 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;
}

Processar eventos da janela picture-in-picture

Crie botões e controles e responda a eventos de entrada do usuário (como "click"), como sempre em 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);

Redimensionar a janela picture-in-picture

Use os métodos de janela resizeBy() e resizeTo() para redimensionar a janela picture-in-picture. Os dois métodos exigem um gesto do usuário.

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);

Focar na janela de abertura

Use o método focus() Window para focar a janela de abertura na janela picture-in-picture. Esse método exige um gesto do usuário.

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

Modo de exibição picture-in-picture do CSS

Use o modo de exibição picture-in-picture do CSS para escrever regras específicas que só são aplicadas quando (parte do) o web app é mostrado no modo picture-in-picture.

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

Detecção de recursos

Para verificar se a API Picture-in-Picture de documentos é compatível, use:

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

Demonstrações

Tomodoro, um app da Web Pomodoro.
Uma janela picture-in-picture no Tomodoro.

Envie feedback

Registre problemas no GitHub com sugestões e perguntas.