Pop-ups: eles estão ressurgindo!

O objetivo da iniciativa Open UI é 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 oferecendo melhores APIs e componentes integrados à plataforma.

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

Os popovers têm uma reputação bastante polarizada há muito tempo. Em parte, isso se deve à maneira 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 para certas coisas ou informá-los sobre o conteúdo do site, especialmente quando usados com bom gosto.

Muitas vezes, há duas grandes preocupações ao criar popovers:

  • Como garantir que ele seja colocado acima do restante do seu conteúdo em um local adequado.
  • Como torná-la acessível (fácil de usar para teclado, focalizável e assim por diante).

A API Popover integrada tem diversos objetivos (em inglês), todos com o mesmo objetivo geral de facilitar a criação desse padrão pelos desenvolvedores. Alguns desses objetivos são:

  • Facilite 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 (dispensamento leve, singleton, empilhamento etc.).

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

Compatibilidade com navegadores

Onde você pode usar a API Popover integrada agora? Ela é compatível com o Chrome Canary com os "Recursos experimentais da plataforma da Web" no momento da gravação.

Para ativar essa sinalização, abra o Chrome Canary e acesse chrome://flags. Depois, ative "Recursos experimentais da plataforma da Web". .

Também existe um teste de origem para desenvolvedores que gostariam de fazer 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.

Verifique o suporte via pop-up com:

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

Soluções atuais

O que você pode fazer atualmente para promover seu conteúdo acima de tudo? Se for compatível com seu navegador, você poderá usar o elemento da caixa de diálogo HTML. Use-o em "Modal". forma E isso requer JavaScript para ser usado.

Dialog.showModal();

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

Você também pode usar uma das muitas bibliotecas baseadas em pop-up, alerta ou dica por aí. Muitos deles tendem a funcionar de maneira semelhante.

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

Isso exige uma dependência extra e mais decisões para os desenvolvedores. Ela também exige pesquisa para encontrar uma oferta que forneç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 tenham que tomar outras decisões para se concentrarem na criação das experiências.

Seu primeiro pop-up

Isso é tudo 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 pop-over em um contêiner ou qualquer outro item. Ele está oculto por padrão.
  • Você não precisa escrever código JavaScript para que eles apareçam. Isso é processado pelo atributo popovertoggletarget.
  • Quando aparece, ela é promovida para a camada superior. Isso significa que ela é promovida acima de document na janela de visualização. Você não precisa gerenciar z-index nem se preocupar com onde seu pop-over está no DOM. Ela pode estar profundamente aninhada no DOM, com recortes de ancestrais. Também é possível conferir quais elementos estão na camada superior usando o DevTools. Para mais informações sobre a camada superior, consulte este artigo.

GIF do suporte à camada superior do DevTools demonstrado

  • Você recebe a opção "Dispensa de luz" prontos para uso. Com isso, queremos dizer que você pode fechar o pop-up com um sinal fechado, como clicar fora do pop-up, navegar para outro elemento usando o teclado ou pressionar a tecla Esc. Abra-o novamente e experimente!

O que mais o pop-up oferece? Vamos conferir 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 um z-index alto.

.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 estilos.

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

Estilizar um pop-over

Vamos voltar nossa atenção para o estilo do pop-over. Por padrão, um pop-over tem uma posição fixa e algum padding é aplicado. Ele também tem display: none. Você pode substituí-lo para mostrar um pop-over. No entanto, isso não o promoveria para a camada superior.

[popover] { display: block; }

Independentemente de como você promove o pop-up, depois de promovê-lo na camada superior, pode ser 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 será posicionado no centro da janela de visualização usando margin: auto. Mas, em alguns casos, convém ser explícito sobre o posicionamento. Exemplo:

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

Se você deseja posicionar o conteúdo dentro do pop-over usando grade CSS ou flexbox, pode ser aconselhável envolvê-lo em um elemento. Caso contrário, será necessário declarar uma regra separada que mude a display quando o pop-up estiver na camada de cima. Se ela for definida por padrão, ela será mostrada, substituindo display: none.

[popover]:open {
 display: flex;
}

Se você testou essa demonstração, perceberá que o pop-up agora está fazendo a transição para dentro e para fora. Você pode fazer a transição de pop-ups para dentro e para fora usando o pseudosseletor :open. O pseudosseletor :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. Você também pode aplicar uma transição ao ::backdrop do pop-over.

[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 aqui é agrupar transições e animações em uma consulta de mídia para movimento. Isso também pode ajudar a controlar o tempo. 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ê viu o uso de popovertoggletarget para mostrar um pop-up. Para isso, usamos a opção "Dispensa leve". No entanto, você também recebe os atributos popovershowtarget e popoverhidetarget que podem ser usados. Vamos adicionar um botão a um pop-over 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 diferentes padrões de interação. Interações como passar o cursor. O uso de um atributo popoverhovertarget foi testado, mas não está 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 pelas propriedades do CSS. Essas propriedades de CSS definiriam a janela de tempo para passar o cursor sobre um elemento ao qual um pop-over reage. O comportamento padrão testado apresentou um pop-over após um 0.5s explícito de :hover. Em seguida, seria necessário dispensar uma luz ou abrir outro pop-up (mais sobre isso em breve). Isso aconteceu porque a duração para ocultar o pop-over foi definida como Infinity.

Enquanto isso, use JavaScript para criar um polyfill para 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 algo explícito como uma janela ao passar o cursor é que isso garante que a ação do usuário seja intencional (por exemplo, o usuário passa o ponteiro sobre um alvo). Não queremos mostrar o pop-up, a menos que essa seja a intenção da pessoa.

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


Antes de explorar alguns casos de uso e exemplos comuns, vamos ver algumas coisas.


Tipos de pop-over

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

A API Popover permite especificar três tipos de pop-over, que diferem em comportamento.

[popover=auto]/[popover]:

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

[popover=manual]:

  • Não fecha outros pop-ups.
  • Nenhuma luz dispensada.
  • Exige a dispensa explícita por meio de um elemento acionador ou do JavaScript.
(link em inglês)

JavaScript API

Quando precisar de mais controle sobre os pop-ups, use 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 ser exibido:

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

Ouça um pop-over ser oculto:

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-over está na camada superior:

popoverElement.matches(':open')

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

Esta demonstração tem popovers com pop-ups audíveis, portanto, precisaremos do JavaScript para reproduzir o áudio. Ao clicar, ocultamos o pop-over, reproduzimos o áudio e o exibimos novamente.

Acessibilidade

A acessibilidade está na vanguarda 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, supondo que você use um dos atributos de acionamento, como popovertoggletarget.

No gerenciamento de foco, você pode usar o atributo autofocus para mover o foco para um elemento dentro de um pop-over. É o mesmo que em 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-over retorna o foco para o elemento anteriormente em foco. No entanto, o foco é movido para um elemento clicado na dispensa de luz, se ele puder receber o foco. Confira a seção sobre gerenciamento de foco na explicação.

Será necessário abrir a versão em tela cheia desta demonstração para ver como ele funciona.

Nesta demonstração, o elemento em foco recebe um contorno verde. Navegue pela interface com o teclado. Observe onde o foco é retornado quando um pop-over é fechado. Você também deve notar que, ao percorrer as guias, o pop-up fechou. Isso é feito por projeto. Embora os popovers tenham gerenciamento de foco, eles não prendem o foco. A navegação pelo teclado identifica um sinal de fechamento quando o foco sai do pop-up.

Ancoragem (em desenvolvimento)

Quando se trata de popovers, um padrão complicado a ser atendido é a ancoragem do elemento no gatilho. Por exemplo, se uma dica estiver definida para aparecer acima do acionador, mas o documento for rolado. Essa dica pode ser cortada pela janela de visualização. Existem ofertas atuais de JavaScript para lidar com isso, como a interface flutuante. Eles vão reposicionar a dica para que você impeça que isso aconteça e dependa de uma ordem de posicionamento desejada.

No entanto, queremos que você possa definir isso com seus estilos. Há uma API complementar em desenvolvimento junto com a API Popover para lidar com isso. O módulo "Posicionamento âncora do CSS" A API permitirá que você conecte elementos a outros elementos e faça 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 de ancoragem no estado atual. A posição do barco responde à posição da âncora na janela de visualização.

(link em inglês)

Veja um snippet do CSS que faz esta demonstração funcionar. Não é necessário 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 as especificações 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 nos aprofundar em alguns exemplos.

Notificações

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

  • Usa [popover=manual]
  • Na 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 de estilo de aviso.

  • Um pop-over com o tipo manual atua como contêiner.
  • As novas notificações são anexadas ao pop-up, que é exibido.
  • Elas são removidas com a API de animações da Web ao ser clicado 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 popovers aninhados.
  • Use autofocus no primeiro link de cada menu suspenso para navegar pelo teclado.
  • Ele é um candidato perfeito para a API de ancoragem CSS. Porém, 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 navegação pelo teclado.

(link em inglês)

Pop-up de mídia

Esta demonstração mostra como abrir mídias.

  • Usa [popover=auto] para dispensar luzes.
  • O JavaScript detecta o evento play do vídeo e exibe o vídeo.
  • O evento popoverhide de pop-ups pausa o vídeo.

Pop-ups no estilo Wiki

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

  • Usa [popover=auto] Mostrar um oculta os outros porque eles não são ancestral.
  • Exibido em pointerenter com JavaScript.
  • Outro candidato perfeito para a API de ancoragem do CSS.

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

  • Usa [popover=auto] para dispensar luzes.
  • Usa autofocus para focar o primeiro item de navegação.
(link em inglês)

Gerenciamento de panos de fundo

Esta demonstração mostra como gerenciar panos de fundo para vários popovers em que você quer que apenas uma ::backdrop fique visível.

  • Use JavaScript para manter uma lista dos pop-ups visíveis.
  • Aplique um nome de classe ao pop-up mais baixo na camada superior.
(link em inglês)

Pop-up personalizado do cursor

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 fique na parte de cima.
(link em inglês)

Pop-up de actionsheet

Esta demonstração mostra como usar um pop-over como uma planilha de ações.

  • Mostrar o pop-up por padrão substituindo display.
  • A planilha de ações é aberta com o acionador pop-over.
  • Quando o pop-up é exibido, ele é promovido para a camada superior e traduzido para visualização.
  • A função de dispensar a luz pode ser usada para fazer o retorno.

Pop-up ativado pelo teclado

Esta demonstração mostra como você pode usar o popover na interface de estilo da paleta de comandos.

  • Use cmd + j para mostrar o pop-up.
  • O input está em foco com autofocus.
  • A caixa de combinação é uma segunda popover posicionada abaixo da entrada principal.
  • Quando a luz dispensa, a paleta é fechada se o menu suspenso não estiver presente.
  • Outro candidato à API Anchoring

Pop-up com marcação de tempo

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 JavaScript para mostrar o pop-up após um período de inatividade.
  • Na exibição pop-over, redefina o timer.
(link em inglês)

Protetor de tela

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

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

Acento circunflexo seguido de acento circunflexo

Esta demonstração mostra como fazer com que um pop-over siga um cursor de entrada.

  • Mostre o pop-up com base na seleção, no evento principal 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 exigiria um pensamento cuidadoso em relação ao conteúdo mostrado e à acessibilidade.
  • Muitas vezes, ela é vista na interface de edição de texto e em apps em que é possível marcar.
(link em inglês)

Menu de botão de ação flutuante

Esta demonstração mostra como usar o pop-over 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 é alvo do botão principal.
  • O menu é aberto com popovertoggletarget.
  • Use autofocus para focar o primeiro item do menu na exibição.
  • "Dispensar luz" fecha o menu.
  • A rotação de ícone usa :has(). Leia mais sobre :has() neste artigo.

Pronto!

Essa é uma introdução ao pop-up que será lançada como parte da iniciativa Open UI. Se usado de maneira coerente, será uma adição fantástica à plataforma da web.

Confira a opção Abrir interface. A explicação pop-up é atualizada conforme a API evolui. Esta é a coleção de todas as demonstrações.

Valeu por aparecer!


Foto de Madison Oren no Unsplash