Pop-ups: eles estão ressurgindo!

O objetivo da iniciativa de interface aberta é facilitar a criação de ótimas experiências do usuário para os desenvolvedores. Para isso, estamos tentando resolver os padrões mais problemáticos que os desenvolvedores enfrentam. Para isso, oferecemos APIs e componentes integrados melhores para a plataforma.

Uma dessas áreas problemáticas são os pop-ups, descritos na interface aberta como "Popovers".

Os pop-ups têm uma reputação bastante polarizada há muito tempo. Isso ocorre, em parte, devido à forma como eles são criados e implantados. Eles não são um padrão fácil de criar, mas podem gerar muito valor ao direcionar os usuários a determinadas coisas ou informar sobre o conteúdo do seu site, especialmente quando usados de maneira adequada.

Geralmente, há duas grandes preocupações ao criar pop-ups:

  • Como garantir que ele seja colocado acima do resto do conteúdo em um lugar adequado.
  • Como torná-lo acessível (compatível com teclado, focalizável etc.).

A API Popover integrada tem várias finalidades, todas com o mesmo objetivo geral de facilitar a criação desse padrão para os desenvolvedores. As principais são:

  • Facilite a exibição de um elemento e seus descendentes acima do restante do documento.
  • Torne-o acessível.
  • Não requer JavaScript para a maioria dos comportamentos comuns (exclusão leve, singleton, empilhamento etc.).

Confira a especificação completa de pop-ups no site da OpenUI.

Compatibilidade com navegadores

Onde é possível usar a API Popover integrada agora? No momento, ele tem suporte no Chrome Canary com a flag "Recursos experimentais da plataforma da Web".

Para ativar essa flag, abra o Chrome Canary e acesse chrome://flags. Em seguida, ative a flag "Recursos experimentais da plataforma da Web".

Há também um teste do Origin para desenvolvedores que queiram testar isso em um ambiente de produção.

Por fim, há um polyfill em desenvolvimento para a API. Confira o repositório em github.com/oddbird/popup-polyfill.

Você pode verificar o suporte a pop-ups com:

const supported = HTMLElement.prototype.hasOwnProperty("popover");

Soluções atuais

O que você pode fazer para promover seu conteúdo acima de tudo? Se ele tiver suporte no seu navegador, use o elemento de diálogo HTML. Você precisa usá-lo no formulário "Modal". E isso requer o uso de JavaScript.

Dialog.showModal();

Há algumas considerações sobre acessibilidade. É recomendável usar a11y-dialog, por exemplo, se você estiver atendendo usuários do Safari abaixo da versão 15.4.

Você também pode usar uma das muitas bibliotecas baseadas em pop-up, alerta ou dica. Muitas delas funcionam de maneira semelhante.

  • Anexe um contêiner ao corpo para mostrar pop-ups.
  • Dê um estilo para que ele fique acima de tudo.
  • Crie um elemento e anexe-o ao contêiner para mostrar um pop-up.
  • Para ocultá-lo, remova o elemento de pop-up do DOM.

Isso exige uma dependência extra e mais decisões dos desenvolvedores. Também é necessário pesquisar para encontrar uma oferta que ofereça tudo o que você precisa. A API Popover tem como objetivo atender a vários cenários, incluindo dicas. O objetivo é cobrir todos esses cenários comuns, evitando que os desenvolvedores precisem tomar mais uma decisão para que possam se concentrar na criação das experiências.

Seu primeiro pop-up

Isso é tudo o que você precisa.

<div id="my-first-popover" popover>Popover Content!</div>
<button popovertoggletarget="my-first-popover">Toggle Popover</button>

Mas o que está acontecendo aqui?

  • Não é necessário colocar o elemento de pop-up em um contêiner ou algo assim. Ele fica oculto por padrão.
  • Não é necessário escrever JavaScript para que ele apareça. Isso é tratado pelo atributo popovertoggletarget.
  • Quando ele aparece, é promovido para a camada superior. Isso significa que ele é promovido acima do document na janela de visualização. Você não precisa gerenciar z-index ou se preocupar com a localização do pop-up no DOM. Ele pode estar profundamente aninhado no DOM, com ancestrais de recorte. Também é possível conferir quais elementos estão na camada superior usando as Ferramentas do desenvolvedor. Para saber mais sobre a camada superior, confira este artigo.

GIF de demonstração do suporte à camada superior do DevTools

  • O recurso "Encerrar com rapidez" já está disponível. Isso significa que você pode fechar o pop-up com um sinal de fechamento, como clicar fora dele, navegar até outro elemento usando o teclado ou pressionar a tecla Esc. Abra de novo e teste.

O que mais você recebe com o pop-up? Vamos analisar melhor o exemplo. Considere esta demonstração com algum conteúdo na página.

Esse botão de ação flutuante tem posicionamento fixo com uma z-index alta.

.fab {
  position: fixed;
  z-index: 99999;
}

O conteúdo do pop-up está aninhado no DOM, mas, quando você abre o pop-up, ele é promovido acima desse elemento de posição fixa. Não é necessário definir estilos.

O popover agora tem um pseudoelemento ::backdrop. Todos os elementos na camada de cima recebem um pseudoelemento ::backdrop estilizável. Este exemplo estiliza ::backdrop com uma cor de plano de fundo alfa reduzida e um filtro de plano de fundo, que desfoca o conteúdo.

Aplicação de estilo a um pop-up

Vamos estilizar o popover. Por padrão, um pop-up tem uma posição fixa e um padding aplicado. Ele também tem display: none. Você pode substituir isso para mostrar um pop-up. Mas isso não a promoveria para a camada superior.

[popover] { display: block; }

Independentemente de como você promover o pop-up, depois de promover um pop-up para a camada superior, talvez seja necessário organizá-lo ou posicioná-lo. Não é possível segmentar a camada superior e fazer algo como

:open {
  display: grid;
  place-items: center;
}

Por padrão, um pop-up será exibido no centro da janela de visualização usando margin: auto. No entanto, em alguns casos, talvez seja necessário especificar o posicionamento. Exemplo:

[popover] {
  top: 50%;
  left: 50%;
  translate: -50%;
}

Se você quiser definir o layout do conteúdo dentro do popover usando a grade CSS ou o flexbox, é recomendável envolver isso em um elemento. Caso contrário, você precisará declarar uma regra separada que mude o display quando o pop-up estiver na camada de cima. A configuração padrão faria com que ele fosse mostrado por padrão, substituindo display: none.

[popover]:open {
 display: flex;
}

Se você testou essa demonstração, vai notar que o pop-up agora está fazendo a transição para dentro e para fora. É possível fazer a transição de popovers usando o pseudoseletor :open. O pseudoseletor :open corresponde aos pop-ups que estão sendo mostrados (e, portanto, na camada superior).

Este exemplo usa uma propriedade personalizada para conduzir a transição. Também é possível aplicar uma transição ao ::backdrop do pop-up.

[popover] {
  --hide: 1;
  transition: transform 0.2s;
  transform: translateY(calc(var(--hide) * -100vh))
            scale(calc(1 - var(--hide)));
}

[popover]::backdrop {
  transition: opacity 0.2s;
  opacity: calc(1 - var(--hide, 1));
}


[popover]:open::backdrop  {
  --hide: 0;
}

Uma dica é agrupar transições e animações em uma consulta de mídia para movimento. Isso também pode ajudar a manter seus horários. Isso ocorre porque não é possível compartilhar valores entre popover e ::backdrop usando a propriedade personalizada.

@media(prefers-reduced-motion: no-preference) {
  [popover] { transition: transform 0.2s; }
  [popover]::backdrop { transition: opacity 0.2s; }
}

Até agora, você aprendeu a usar popovertoggletarget para mostrar um popover. Para dispensar, usamos a "dispensação leve". Mas você também tem os atributos popovershowtarget e popoverhidetarget que pode usar. Vamos adicionar um botão a um pop-up que o oculta e mudar o botão de alternância para usar popovershowtarget.

<div id="code-popover" popover>
  <button popoverhidetarget="code-popover">Hide Code</button>
</div>
<button popovershowtarget="code-popover">Reveal Code</button>

Como mencionado anteriormente, a API Popover abrange mais do que apenas nossa noção histórica de pop-ups. Você pode criar para todos os tipos de cenários, como notificações, menus, dicas etc.

Alguns desses cenários precisam de padrões de interação diferentes. Interações como passar o cursor. O uso de um atributo popoverhovertarget foi experimentado, mas não está implementado no momento.

<div popoverhovertarget="hover-popover">Hover for Code</div>

A ideia é que você passe o cursor sobre um elemento para mostrar o alvo. Esse comportamento pode ser configurado por meio de propriedades CSS. Essas propriedades do CSS definem o período de tempo para passar o cursor sobre um elemento e o período de tempo para que um popover reaja. O comportamento padrão que foi experimentado mostrava um pop-up após uma 0.5s explícita de :hover. Em seguida, seria necessário uma dispensa leve ou a abertura de outro pop-up para dispensar (mais informações sobre isso em breve). Isso ocorreu porque a duração de ocultação do pop-up foi definida como Infinity.

Enquanto isso, você pode usar JavaScript para polimorfismo de tipo de dados dessa funcionalidade.

let hoverTimer;
const HOVER_TRIGGERS = document.querySelectorAll("[popoverhovertarget]");
const tearDown = () => {
  if (hoverTimer) clearTimeout(hoverTimer);
};
HOVER_TRIGGERS.forEach((trigger) => {
  const popover = document.querySelector(
    `#${trigger.getAttribute("popoverhovertarget")}`
  );
  trigger.addEventListener("pointerenter", () => {
    hoverTimer = setTimeout(() => {
      if (!popover.matches(":open")) popover.showPopOver();
    }, 500);
    trigger.addEventListener("pointerleave", tearDown);
  });
});

O benefício de definir uma janela de passagem explícita é que ela garante que a ação do usuário seja intencional (por exemplo, um usuário passa o cursor sobre um destino). Não queremos mostrar o pop-up, a menos que essa seja a intenção.

Teste esta demonstração em que você pode passar o cursor sobre o alvo com a janela definida como 0.5s.


Antes de analisarmos alguns casos de uso e exemplos comuns, vamos conferir algumas informações.


Tipos de pop-up

Já abordamos o comportamento de interação que não é JavaScript. Mas e o comportamento do pop-up como um todo? E se você não quiser usar a "Descartar com leveza"? Ou você quer aplicar um padrão de singleton aos seus pop-ups?

A API Popover permite especificar três tipos de pop-up com comportamentos diferentes.

[popover=auto]/[popover]:

  • Suporte para aninhamento. Isso não significa apenas aninhamento no DOM. A definição de um pop-up ancestral é:
    • relacionados pela posição do DOM (filho).
    • relacionados por atributos de acionamento em elementos filhos, como popovertoggletarget, popovershowtarget e assim por diante.
    • relacionadas pelo atributo anchor (API de ancoragem CSS em desenvolvimento).
  • Luz apagada.
  • A abertura dispensa outros pop-ups que não são pop-ups ancestrais. Confira a demonstração abaixo que destaca como funciona o aninhamento com popovers ancestrais. Veja como mudar algumas das instâncias popoverhidetarget/popovershowtarget para popovertoggletarget muda as coisas.
  • A luz de dispensar uma dispensa todas, mas a dispensa de uma na pilha dispensa apenas as que estão acima dela.

[popover=manual]:

  • Não fecha outros popovers.
  • Não há luz de dispensa.
  • Requer dispensa explícita pelo elemento de acionamento ou JavaScript.

JavaScript API

Quando você precisa de mais controle sobre os pop-ups, é possível usar o JavaScript. Você recebe um método showPopover e hidePopover. Você também tem eventos popovershow e popoverhide para detectar:

Mostrar um pop-up js popoverElement.showPopover() Ocultar um pop-up:

popoverElement.hidePopover()

Detecte quando um pop-up for mostrado:

popoverElement.addEventListener('popovershow', doSomethingWhenPopoverShows)

Detecte um pop-up sendo mostrado e cancele a exibição dele:

popoverElement.addEventListener('popovershow',event => {
  event.preventDefault();
  console.warn(We blocked a popover from being shown);
})

Detectar um pop-up sendo ocultado:

popoverElement.addEventListener('popoverhide', doSomethingWhenPopoverHides)

Não é possível cancelar um popover que está sendo ocultado:

popoverElement.addEventListener('popoverhide',event => {
  event.preventDefault();
  console.warn("You aren't allowed to cancel the hiding of a popover");
})

Verifique se um popover está na camada superior:

popoverElement.matches(':open')

Isso oferece mais potência para alguns cenários menos comuns. Por exemplo, mostrar um pop-up após um período de inatividade.

Esta demonstração tem pop-ups com sons audíveis. Portanto, vamos precisar de JavaScript para reproduzir o áudio. Ao clicar, o popover é ocultado, o áudio é reproduzido e mostrado novamente.

Acessibilidade

A acessibilidade está no centro do pensamento com a API Popover. Os mapeamentos de acessibilidade associam o pop-up ao elemento acionador, conforme necessário. Isso significa que não é necessário declarar atributos aria-*, como aria-haspopup, se você usar um dos atributos de acionamento, como popovertoggletarget.

Para o gerenciamento de foco, use o atributo autofocus para mover o foco para um elemento dentro de um pop-up. Isso é igual ao de uma caixa de diálogo, mas a diferença ocorre ao retornar o foco, e isso ocorre por causa da dispensa leve. Na maioria dos casos, o fechamento de um pop-up retorna o foco ao elemento focado anteriormente. Mas o foco é movido para um elemento clicado na dispensação leve, se ele puder receber o foco. Confira a seção sobre gerenciamento de foco na explicação.

Você precisa abrir a versão em tela cheia desta demonstração para que ela funcione.

Nesta demonstração, o elemento em foco recebe um contorno verde. Tente usar as teclas de tabulação na interface com o teclado. Observe onde o foco é retornado quando um pop-up é fechado. Você também pode notar que, se você clicar em "Sobre", o pop-up será fechado. Isso ocorre por design. Embora os pop-ups tenham gerenciamento de foco, eles não prendem o foco. E a navegação pelo teclado identifica um sinal de fechamento quando o foco sai do pop-up.

Ancoragem (em desenvolvimento)

No caso dos pop-ups, um padrão complicado é ancorar o elemento ao gatilho. Por exemplo, se uma dica de ferramenta for definida para aparecer acima do acionador, mas o documento for rolado. Essa dica pode ser cortada pela viewport. Há ofertas atuais de JavaScript para lidar com isso, como UI flutuante. Ele reposiciona a dica para que isso não aconteça e depende de uma ordem de posição desejada.

Mas queremos que você possa definir isso com seus estilos. Há uma API complementar em desenvolvimento com a API Popover para resolver esse problema. A API CSS Anchor Positioning permite que você conecte elementos a outros, reposicionando-os para que não sejam cortados pela janela de visualização.

Esta demonstração usa a API Anchoring no estado atual. A posição do barco responde à posição da âncora na janela de visualização.

Confira um snippet do CSS que faz essa demonstração funcionar. Não é necessário usar JavaScript.

.anchor {
  --anchor-name: --anchor;
}
.anchored {
  position: absolute;
  position-fallback: --compass;
}
@position-fallback --compass {
  @try {
    bottom: anchor(--anchor top);
    left: anchor(--anchor right);
  }
  @try {
    top: anchor(--anchor bottom);
    left: anchor(--anchor right);
  }
}

Confira a especificação aqui. Também haverá um polyfill para essa API.

Exemplos

Agora que você já conhece o que o pop-up tem a oferecer e como, vamos analisar alguns exemplos.

Notificações

Esta demonstração mostra uma notificação "Copiar para a área de transferência".

  • Usa [popover=manual]
  • Em ação, mostre o pop-up com showPopover.
  • Após um tempo limite de 2000ms, oculte-o com hidePopover.

Avisos

Esta demonstração usa a camada superior para mostrar notificações do tipo "toast".

  • Um pop-up com o tipo manual atua como o contêiner.
  • As novas notificações são anexadas ao pop-up e ele é mostrado.
  • Elas são removidas com a API de animações da Web ao clicar e removidas do DOM.
  • Se não houver avisos para mostrar, o pop-up será oculto.

Menu aninhado

Esta demonstração mostra como um menu de navegação aninhado pode funcionar.

  • Use [popover=auto], porque ele permite popovers aninhados.
  • Use autofocus no primeiro link de cada menu suspenso para navegar com o teclado.
  • Esse é um candidato perfeito para a API CSS Anchoring. No entanto, para esta demonstração, você pode usar um pouco de JavaScript para atualizar as posições usando propriedades personalizadas.
const ANCHOR = (anchor, anchored) => () => {
  const { top, bottom, left, right } = anchor.getBoundingClientRect();
  anchored.style.setProperty("--top", top);
  anchored.style.setProperty("--right", right);
  anchored.style.setProperty("--bottom", bottom);
  anchored.style.setProperty("--left", left);
};

PRODUCTS_MENU.addEventListener("popovershow", ANCHOR(PRODUCT_TARGET, PRODUCTS_MENU));

Como esta demonstração usa autofocus, ela precisa ser aberta na visualização em tela cheia para navegação com o teclado.

Popover de mídia

Esta demonstração mostra como você pode abrir a mídia.

  • Usa [popover=auto] para dispensar a luz.
  • O JavaScript detecta o evento play do vídeo e o exibe.
  • O evento popoverhide dos popovers pausa o vídeo.

Pop-ups no estilo Wiki

Esta demonstração mostra como criar dicas de ferramentas de conteúdo inline que contêm mídia.

  • Usa [popover=auto] A exibição de um deles oculta os outros, porque eles não são ancestrais.
  • Mostrado em pointerenter com JavaScript.
  • Outro candidato perfeito para a API CSS Anchoring.

Esta demonstração cria uma gaveta de navegação usando um pop-up.

  • Usa [popover=auto] para dispensar a luz.
  • Usa autofocus para focar o primeiro item de navegação.

Como gerenciar planos de fundo

Esta demonstração mostra como gerenciar planos de fundo para vários pop-ups em que você quer que apenas um ::backdrop fique visível.

  • Use o JavaScript para manter uma lista dos pop-ups que estão visíveis.
  • Aplique um nome de classe ao pop-up mais baixo na camada de cima.

Pop-up de cursor personalizado

Esta demonstração mostra como usar popover para promover um canvas à camada superior e usá-lo para mostrar um cursor personalizado.

  • Promova canvas para a camada superior com showPopover e [popover=manual].
  • Quando outros pop-ups forem abertos, oculte e mostre o pop-up canvas para garantir que ele esteja na parte de cima.

Popover da página de ações

Esta demonstração mostra como usar um pop-up como uma action sheet.

  • O pop-up mostrado por padrão substitui display.
  • A Actionsheet é aberta com o gatilho do pop-up.
  • Quando o pop-up é mostrado, ele é promovido para a camada superior e traduzido para a visualização.
  • A dispensa leve pode ser usada para retornar.

Popover ativado pelo teclado

Esta demonstração mostra como usar o pop-up para a interface no estilo da paleta de comandos.

  • Use cmd + j para mostrar o pop-up.
  • O input está focado com autofocus.
  • A caixa de combinação é um segundo popover posicionado abaixo da entrada principal.
  • A dispensa leve fecha a paleta se o menu suspenso não estiver presente.
  • Outro candidato para a API Anchoring

Pop-up programado

Esta demonstração mostra um popover de inatividade após quatro segundos. Um padrão de interface usado com frequência em apps que armazenam informações seguras sobre um usuário para mostrar um modal de encerramento de sessão.

  • Use JavaScript para mostrar o pop-up após um período de inatividade.
  • Ao mostrar o pop-up, redefina o timer.

Protetor de tela

Assim como na demonstração anterior, você pode adicionar um toque de fantasia ao seu site e um protetor de tela.

  • Use JavaScript para mostrar o pop-up após um período de inatividade.
  • Descartar a luz para ocultar e redefinir o timer.

Seguimento do cursor

Esta demonstração mostra como um pop-up pode seguir um cursor de entrada.

  • Mostre o pop-up com base na seleção, no evento de tecla ou na entrada de caracteres especiais.
  • Use o JavaScript para atualizar a posição do pop-up com propriedades personalizadas com escopo.
  • Esse padrão exige uma reflexão cuidadosa sobre o conteúdo mostrado e a acessibilidade.
  • Ele é usado com frequência na interface de edição de texto e em apps em que é possível adicionar tags.

Menu do botão de ação flutuante

Esta demonstração mostra como usar o pop-up para implementar um menu de botão de ação flutuante sem JavaScript.

  • Promova um pop-up do tipo manual com o método showPopover. Esse é o botão principal.
  • O menu é outro pop-up que é o destino do botão principal.
  • O menu é aberto com popovertoggletarget.
  • Use autofocus para focar o primeiro item de menu em exibição.
  • A dispensa leve fecha o menu.
  • A torção do ícone usa :has(). Leia mais sobre :has() neste artigo.

Pronto!

Essa é uma introdução ao pop-up, que será lançado em breve como parte da iniciativa de interface aberta. Se usado de forma sensata, será uma adição fantástica para a plataforma da Web.

Confira a interface aberta. O explicador de pop-up é atualizado conforme a API evolui. Confira a coleção de todas as demonstrações.

Agradecemos sua visita.


Foto de Madison Oren no Unsplash