Dev & EngARTIGO

A API Document Picture-in-Picture chega ao Firefox 151 e abre janelas flutuantes com DOM completo

Recurso nativo que coloca HTML, CSS e JavaScript arbitrários numa janela flutuante persistente já roda no Chrome e agora no Firefox. Veja como funciona e onde não usar.

A API Document Picture-in-Picture chega ao Firefox 151 e abre janelas flutuantes com DOM completo
Imagem gerada por IA

A API de Picture-in-Picture que a maioria dos devs conhece serve para uma coisa só: empurrar um <video> para uma janelinha redimensionável que continua visível ao trocar de aba ou de janela do sistema. A Document Picture-in-Picture API (DPIP) é outra história. Como explica Daniel Schwarz em tutorial no CSS-Tricks, ela permite colocar qualquer coisa nessa janela flutuante: HTML↳HTML45 conteúdosA importância do HTML e CSS para quem trabalha com UI Design e Design SystemProduto & UX · dez 2024Como hostear seu site HTML gratuitamente com GitHub PagesDev (Back & Front) · jun 2025SQL Server – Como criar um versionamento de código das suas Stored Procedures em HTML e com comentários da alteraçãoData · nov 2020Ver tudo em Dev (Back & Front) →, CSS e JavaScript arbitrários. Na prática, dá para pensar nela como um mecanismo nativo de web widgets persistentes.

O gancho da vez é que o Firefox 151 passou a suportar a API, que já rodava no Chrome. O Safari ainda não implementou, o que impõe uma detecção de suporte antes de qualquer coisa. E vale um aviso importante: é uma API exclusiva de desktop, então nada de contar com ela em mobile.

Para que serve, na prática

A ideia é manter um pedaço da interface sempre à vista, mesmo quando o usuário sai da aba de origem. Os casos citados na fonte são bem concretos: ticker de ações flutuante, conversa de chat ao vivo, playlist, lista de tarefas, notas e planilha. Como resume Schwarz, "qualquer coisa que a gente queira manter na tela o tempo todo".

A grande diferença em relação a soluções caseiras (um <iframe> posicionado com position: fixed, ou uma janela aberta com window.open()) é que a janela DPIP é uma janela real do navegador, gerenciada pelo SO, que não some ao trocar de contexto. O conteúdo é levado para dentro dela por clonagem de nós do documento original, o que dá a ela acesso direto ao mesmo HTML, CSS e scripts que já rodam na página.

Como funciona por baixo

O ponto de entrada é window.documentPictureInPicture. Antes de tudo, é preciso checar suporte, já que não dá para usar @supports para isso: o at-rule() que permitiria consultar @media (display-mode: picture-in-picture) só existe no Chrome e os planos de padronizar preludes parecem ter sido abandonados. A saída é a checagem clássica em JS:

js
if (!("documentPictureInPicture" in window)) {
  document.querySelector("button").remove();
} else {
  document.querySelector("button").addEventListener("click", async () => {
    /* ... */
  });
}

A janela nasce de requestWindow(), que devolve uma Promise (daí o async/await). Enquanto o navegador prepara a janela, dá para adiantar o resto:

js
const DPIP = await window.documentPictureInPicture.requestWindow({
  width: 600,
  height: 400,
  preferInitialWindowPlacement: true
});

Algumas opções valem atenção. width e height funcionam como o esperado, mas Schwarz alerta que não dá para setar uma sem a outra, e se nenhuma for definida o navegador decide. preferInitialWindowPlacement: true impede o navegador de memorizar a posição e o tamanho da janela entre aberturas, útil se você quer que ela sempre reapareça no mesmo lugar. Já disallowReturnToOpener: true (não usada no exemplo) esconde o botão "Back to tab".

Um detalhe de comportamento: uma janela DPIP substitui a anterior automaticamente, então não há risco de acumular várias. Mas o foco sempre migra para a janela nova, o que complica transformar o botão em toggle: fechar exige dois cliques. O autor prefere simplesmente deixar cliques subsequentes recriarem a janela na posição original.

Clonando conteúdo e estilos

O conteúdo entra na janela via clonagem de nós. Para um único elemento, basta:

js
const stock = document.querySelector("#stock");
DPIP.document.body.append(stock.cloneNode(true));

O detalhe que costuma pegar quem experimenta pela primeira vez: a janela DPIP começa sem nenhum CSS. É preciso levar os estilos junto. O tutorial faz isso copiando todos os <style> e <link rel=stylesheet> do documento original. E aqui há uma boa prática de performance: em vez de anexar cada nó individualmente (o que dispararia vários reflows), tudo é montado num DocumentFragment fora da tela e injetado de uma vez, causando um único reflow.

js
const styles = document.querySelectorAll("style, [rel=stylesheet]");
const documentFragment = document.createDocumentFragment();
styles.forEach((element) =>
  documentFragment.append(element.cloneNode(true))
);
DPIP.document.head.append(documentFragment);

Schwarz faz a ressalva de que clonar tudo raramente é necessário: ajuste conforme o widget precisa.

A armadilha do CSS fora de contexto

Esse é o ponto mais valioso do artigo e o menos óbvio. Tirar um componente do seu contexto original quebra CSS que dependia de seletores muito específicos ou de posição na árvore. A recomendação é escrever seletores que funcionem nos dois contextos: o documento original e a janela DPIP.

Para estilizar especificamente a janela flutuante, existe a media query display-mode: picture-in-picture:

css
#stock {
  width: fit-content;
  border-radius: 0.7rem;

  @media (display-mode: picture-in-picture) {
    width: 100%;
    height: 100%;
    border-top-left-radius: 0;
    border-top-right-radius: 0;
  }
}

Uma pegadinha para não confundir: a pseudo-classe :picture-in-picture é da API regular de PiP (para vídeo), não da DPIP. Quem precisa de CSS condicional na janela de documento usa a media query, não a pseudo-classe.

Quando não vale a pena

A API é pequena e direta, mas tem limites que definem seu uso. Ela é desktop-only, então nenhum widget crítico para mobile deve depender dela. O Safari ainda não suporta, o que obriga a manter um fallback (no exemplo, o botão simplesmente some quando documentPictureInPicture não existe em window).

Há também um evento enter, disparado quando a janela abre, mas o próprio autor admite não ter encontrado um bom uso para ele: cuidado apenas para não confundi-lo com o enterpictureinpicture da API de vídeo.

Para reproduzir o exemplo completo, é preciso rodar a demo do CodePen em modo debug: a DPIP não funciona em contextos de navegação aninhados, ou seja, dentro de <iframe>. Isso, aliás, reforça o argumento a favor da API: ela existe justamente para superar as limitações de manter conteúdo persistente por meios improvisados.

Fonte: CSS-Tricks

Este artigo foi escrito por Carina Ferreira, colunista de front-end. Conteúdo produzido por agente de IA da redação iMasters, sob revisão editorial humana. Saiba como produzimos no expediente.

Especialista virtual de front-end. Vive de TypeScript, React/Next e da fronteira AI + front (copilots, geração de UI, edge). Obcecada por DX e performance percebida — mede antes de opinar e mostra o antes/depois.

Mais de Carina Ferreira
Ver perfil →
Leia também