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 fazer isso, estamos tentando lidar com os padrões mais problemáticos que os desenvolvedores enfrentam. Podemos fazer isso fornecendo APIs e componentes integrados à plataforma melhores.

Um desses problemas são os pop-ups, descritos em IU aberta como "Popovers".

Popovers têm uma reputação bastante polarizada há muito tempo. Isso se deve, em parte, à maneira como eles são criados e implantados. Eles não são um padrão fácil de criar, mas podem produzir muito valor direcionando os usuários a determinadas coisas ou fazendo com que eles saibam sobre o conteúdo do seu site, especialmente quando usados de maneira agradável.

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

  • Como garantir que ele seja colocado em um local adequado acima do restante do seu conteúdo.
  • Como torná-la acessível (amigável ao teclado, focalizável etc.).

A API Popover integrada tem diversas metas, todas com o mesmo objetivo geral de facilitar a criação desse padrão pelos desenvolvedores. Alguns desses objetivos são:

  • Facilitar a exibição de um elemento e dos descendentes dele acima do restante do documento.
  • Torne-o acessível.
  • Não exigem JavaScript para os comportamentos mais comuns (dispensar luz, singleton, empilhamento etc.).

Confira as especificações completas para pop-ups no site do OpenUI.

Compatibilidade com navegadores

Onde você pode usar a API Popover integrada agora? Ela tem suporte no Chrome Canary com a flag "Recursos experimentais da plataforma da Web" até o momento.

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

Também há um teste de origem para desenvolvedores que querem testar esse recurso 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.

Verifique se há suporte a pop-ups usando:

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

Soluções atuais

O que você pode fazer atualmente para promover seu conteúdo acima de tudo? Se ele for compatível com seu navegador, use o elemento da caixa de diálogo HTML. É preciso usá-lo na forma "modal". Para isso, é necessário usar o JavaScript.

Dialog.showModal();

Há algumas considerações sobre acessibilidade. É aconselhável usar o a11y-dialog, por exemplo, ao atender usuários do Safari em versões anteriores à 15.4.

Você também pode usar uma das muitas bibliotecas baseadas em pop-ups, alertas ou dicas disponíveis. Muitos deles tendem a funcionar de maneira semelhante.

  • Anexe um contêiner ao corpo para mostrar pop-ups.
  • Defina o estilo para que ele fique acima de todo o resto.
  • Crie um elemento e o anexe ao contêiner para mostrar um pop-up.
  • Oculte-o removendo o elemento de pop-up do DOM.

Isso exige uma dependência extra e mais decisões para os desenvolvedores. Também é necessário fazer pesquisas para encontrar uma oferta que ofereça tudo o que você precisa. O objetivo da API Popover é atender a muitos cenários, incluindo dicas. O objetivo é abordar todos esses cenários comuns, evitando que os desenvolvedores precisem tomar outra decisão para se concentrarem na criação de experiências.

Seu primeiro pop-up

Isso é tudo de 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?

  • Você não precisa colocar o elemento de pop-up em um contêiner ou qualquer coisa, já que ele fica oculto por padrão.
  • Você não precisa escrever nenhum JavaScript para que ela apareça. Isso é processado pelo atributo popovertoggletarget.
  • Quando aparecer, ele será 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 posição do pop-up no DOM. Ele poderia estar aninhado profundamente no DOM, com ancestrais de recorte. Também é possível ver quais elementos estão atualmente na camada superior pelo DevTools. Para mais informações sobre a camada superior, confira este artigo.

GIF do suporte da camada superior do DevTools sendo demonstrado

  • Você tem a opção "Dispensa de luz" pronta para uso. Isso significa que você pode fechar a janela pop-up com um sinal de fechamento, como clicar fora dela, navegar até outro elemento ou pressionar a tecla Esc. Abra de novo e experimente!

O que mais você ganha 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 um posicionamento fixo com uma z-index alta.

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

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

Além disso, o pop-up agora tem um pseudoelemento ::backdrop. Todos os elementos na camada superior recebem um pseudoelemento ::backdrop estilizado. Este exemplo aplica o estilo ::backdrop com uma cor de plano de fundo Alfa reduzida e um filtro de pano de fundo, que desfoca o conteúdo subjacente.

Como definir o estilo de um pop-up

Vamos focar no estilo do pop-up. Por padrão, um pop-over tem uma posição fixa e um pouco de padding aplicado. Ela também tem display: none. Você pode substituir essa configuração para mostrar um pop-up. No entanto, isso não o promoveria para a camada superior.

[popover] { display: block; }
.

Independentemente de como você promove seu pop-up, depois de promover um pop-up para a camada superior, talvez seja necessário colocá-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-over é posicionado no centro da janela de visualização usando margin: auto. Mas, em alguns casos, você pode querer ser explícito sobre o posicionamento. Exemplo:

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

Se você quiser posicionar o conteúdo dentro do pop-up usando a grade CSS ou o flexbox, é recomendável envolvê-lo em um elemento. Caso contrário, será necessário declarar uma regra separada que mude o display quando o pop-up estiver na camada superior. Se for definido por padrão, ele será mostrado, substituindo display: none por padrão.

[popover]:open {
 display: flex;
}

Se você já testou essa demonstração, vai perceber que o pop-up está fazendo a transição para dentro e para fora. Você pode fazer a transição dos pop-ups para dentro e para fora usando o pseudosseletor :open. O pseudosseletor :open faz a correspondência dos pop-ups que estão sendo mostrados (e, portanto, na camada superior).

Neste exemplo, usamos uma propriedade personalizada para orientar 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 pode ajudar a manter o ritmo também. 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, vimos o uso de popovertoggletarget para mostrar um pop-up. Para dispensar, use a opção "Dispensar luz". No entanto, você também tem os atributos popovershowtarget e popoverhidetarget que podem ser usados. Vamos adicionar um botão a um pop-up que o oculta e mudar o botão 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 testado, mas não foi implementado no momento.

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

A ideia é passar o cursor sobre um elemento para mostrar o alvo. Esse comportamento pode ser configurado usando propriedades CSS. Essas propriedades CSS definiriam o período para se passar o cursor sobre um elemento ao qual um pop-over reage. O comportamento padrão testado apresentou um pop-up após um 0.5s explícito de :hover. Então, será necessário dispensar uma luz ou abrir outro pop-up para dispensar (mais sobre isso em breve). Isso ocorreu porque a duração da ocultação do pop-up foi definida como Infinity.

Enquanto isso, é possível usar o JavaScript para aplicar polyfill a essa 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);
  });
});

A vantagem de definir uma janela de passagem de cursor explícita é que isso garante que a ação do usuário seja intencional (por exemplo, um usuário passa o ponteiro sobre um alvo). 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 explorar alguns casos de uso e exemplos comuns, vamos analisar alguns itens.


Tipos de pop-up

Abordamos o comportamento de interação não JavaScript. Mas e o comportamento de pop-ups como um todo? E se você não quiser "Dispensar iluminação"? Ou quer aplicar um padrão singleton aos seus popovers?

A API Popover permite especificar três tipos de popover que diferem no comportamento.

[popover=auto]/[popover]:

  • Suporte para a transição. Isso não significa apenas estar aninhado no DOM. A definição de um pop-up ancestral é:
    • relacionadas pela posição do DOM (filho).
    • relacionadas, acionando atributos em elementos filhos, como popovertoggletarget, popovershowtarget e assim por diante.
    • relacionadas pelo atributo anchor (em desenvolvimento na API CSS Anchoring).
  • Iluminação dispensada.
  • A abertura dispensa outros pop-ups que não são popovers de ancestral. Teste a demonstração abaixo que destaca como funciona o aninhamento com popovers ancestrais. Confira como mudar algumas das instâncias de popoverhidetarget/popovershowtarget para popovertoggletarget muda as coisas.
  • A ação de ignorar um deles descarta todos, mas dispensar um na pilha apenas dispensa aqueles que estão acima dele na pilha.
.

[popover=manual]:

  • Não fecha outros pop-ups.
  • Nenhuma luz dispensada.
  • Requer uma dispensa explícita por meio de elemento de gatilho ou JavaScript.

JavaScript API

Quando precisar de mais controle sobre seus popovers, você pode realizar ações com JavaScript. Você recebe os métodos 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()

Ouça um pop-up sendo mostrado:

popoverElement.addEventListener('popovershow', doSomethingWhenPopoverShows)

Ouça e cancele a exibição de um pop-up:

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

Escute um pop-up sendo escondido:

popoverElement.addEventListener('popoverhide', doSomethingWhenPopoverHides)

Não é possível cancelar a ocultação de um pop-up:

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

Verifique se um pop-up está na camada superior:

popoverElement.matches(':open')

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

Esta demonstração tem pop-ups com efeitos audíveis. Por isso, precisaremos do JavaScript para reproduzir o áudio. Quando o usuário clica, escondemos o pop-up, reproduzimos o áudio e o mostramos novamente.

Acessibilidade

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

Para gerenciar o foco, é possível usar o atributo autofocus para mover o foco para um elemento dentro de um popover. Isso é o mesmo que para uma caixa de diálogo, mas a diferença ocorre ao retornar o foco, devido à dispensa da luz. Na maioria dos casos, fechar um pop-up retorna o foco para o elemento anteriormente focado. No entanto, o foco será movido para um elemento clicado ao dispensar a luz, se puder receber foco. Confira a seção sobre gerenciamento de foco na explicação.

.

Será preciso 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 navegar pela interface com o teclado. Observe onde o foco é retornado quando um pop-up é fechado. Você também pode notar que, se você consultar, o pop-up fechou. Isso é padrão. Embora os pop-ups tenham gerenciamento de foco, eles não prendem o foco. A navegação pelo teclado identifica um sinal de fechamento quando o foco sai da janela pop-up.

Ancoragem (em desenvolvimento)

Quando se trata de pop-ups, um padrão complicado é ancorar o elemento no gatilho. Por exemplo, se uma dica estiver definida para aparecer acima do gatilho, mas o documento for rolado. A dica pode ser cortada pela janela de visualização. Atualmente, existem ofertas de JavaScript para lidar com isso, como "interface flutuante". Eles reposicionarão a dica para que você pare que isso aconteça e se baseie em uma ordem de posição desejada.

No entanto, queremos que você seja capaz de definir isso com seus estilos. Para isso, existe uma API complementar em desenvolvimento, além da API Popover. A API "CSS Anchor Positioning" permite vincular elementos a outros elementos e faz isso de uma maneira que reposiciona os elementos 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. Nenhum JavaScript é necessário.

.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á sabe o que o pop-up tem a oferecer e como, vamos nos aprofundar em alguns exemplos.

Notificações

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

  • Usa [popover=manual]
  • Pop-up de ação com showPopover.
  • Depois de um tempo limite de 2000ms, oculte-o com hidePopover.

Avisos

Esta demonstração usa a camada superior para mostrar notificações de estilo de aviso.

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

Menu aninhado

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

  • Use [popover=auto], porque ele permite pop-ups aninhados.
  • Use autofocus no primeiro link de cada menu suspenso para navegar pelo teclado.
  • É um candidato perfeito para a API CSS Anchoring. Mas, para esta demonstração, você pode usar uma pequena quantidade 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, será necessário abri-la na visualização em tela cheia para navegar pelo teclado.

Pop-up de mídia

Esta demonstração mostra como exibir uma mídia pop-up.

  • Usa [popover=auto] para dispensar a luz
  • O JavaScript detecta o evento play do vídeo e abre o vídeo.
  • O evento de pop-up popoverhide pausa o vídeo.
.

Pop-overs de estilo Wiki

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

  • Usa [popover=auto] Mostrar um esconde os demais, porque eles não são ancestrais.
  • Exibido no 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

Gerenciamento de cenários

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

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

Pop-up de cursor personalizado

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

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

Pop-up do Actionsheet

Nesta demonstração, mostramos como usar um pop-up como uma folha de ações.

  • Mostre o pop-up por padrão, substituindo display.
  • A Actionsheet é aberta com o acionador de pop-up.
  • Quando o pop-up é exibido, ele é promovido para a camada superior e é traduzido para a visualização.
  • É possível usar "dispensar" para retornar essa ação.

Pop-up ativado pelo teclado

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

  • Use cmd + j para mostrar o pop-up.
  • O input é focado com autofocus.
  • A caixa de combinação é uma segunda popover posicionada abaixo da entrada principal.
  • A opção "Dispensar" fecha a paleta se o menu suspenso não estiver presente.
  • Outro candidato para a API Anchoring
.

Pop-up cronometrado

Esta demonstração mostra um pop-up de inatividade após quatro segundos. Um padrão de IU frequentemente usado em apps que contêm informações seguras sobre um usuário para mostrar um modal de logout.

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

Protetor de tela

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

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

Acompanhamento com acento circunflexo

Esta demonstração mostra como fazer com que um pop-over siga um acento circunflexo 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 no escopo.
  • Para isso, é preciso pensar bem no conteúdo mostrado e na acessibilidade.
  • Ele costuma ser visto na interface de edição de texto e em apps em que é possível marcar.
.

Menu do botão de ação flutuante

Esta demonstração mostra como você pode 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. Este é 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 do menu em exibição.
  • "Dispensar a luz" fecha o menu.
  • A giro do ícone usa :has(). Você pode ler mais sobre o :has() neste artigo.

Pronto!

Esta é uma introdução ao pop-up, que está chegando como parte da iniciativa de interface aberta. Se usado de maneira coerente, será uma adição fantástica à plataforma da web.

Confira a página Open UI. A explicação em pop-up é atualizada à medida que a API evolui. Esta é a coleção de todas as demonstrações.

Valeu por aparecer!


Foto de Madison Oren no Unsplash