Popup: stanno rinascendo!

L'obiettivo dell'iniziativa di interfaccia utente aperta è consentire agli sviluppatori di creare più facilmente esperienze utente ottimali. A questo scopo, stiamo cercando di affrontare gli schemi più problematici che gli sviluppatori devono affrontare. Possiamo farlo fornendo API e componenti migliori integrati per la piattaforma.

Una di queste aree con problemi sono i popup, descritti nell'interfaccia utente aperta come "Popover".

I popover hanno una reputazione piuttosto polarizzante da molto tempo. Ciò è in parte dovuto alle modalità di creazione e deployment. Non sono uno schema semplice da realizzare bene, ma possono generare molto valore indirizzando gli utenti a determinati elementi o rendendoli consapevoli dei contenuti del tuo sito, soprattutto se utilizzati in modo elegante.

Quando si creano popover, esistono spesso due preoccupazioni:

  • Come assicurarti che venga posizionato sopra il resto dei contenuti in una posizione appropriata.
  • Come renderlo accessibile (adatto alla tastiera, attivabile e così via).

L'API Popover integrata ha una serie di obiettivi, tutti con lo stesso obiettivo generale di semplificare la creazione di questo pattern per gli sviluppatori. Tra gli obiettivi degni di nota ci sono:

  • Semplifica la visualizzazione di un elemento e dei relativi discendenti sopra il resto del documento.
  • Rendili accessibili.
  • Non richiede JavaScript per i comportamenti più comuni (ignoramento leggero, singleton, stack e così via).

Puoi consultare le specifiche complete per i popup sul sito di OpenUI.

Compatibilità del browser

Dove puoi usare ora l'API Popover integrata? È supportata in Chrome Canary contrassegnata dal flag "Funzionalità sperimentali della piattaforma web" al momento della scrittura.

Per attivare il flag, apri Chrome Canary e visita chrome://flags. Attiva poi il flag "Funzionalità sperimentali della piattaforma web".

È disponibile anche una prova dell'origine per gli sviluppatori che vogliono testarla in un ambiente di produzione.

Infine, c'è un polyfill in fase di sviluppo per l'API. Assicurati di controllare il repository all'indirizzo github.com/oddbird/popup-polyfill.

Puoi verificare il supporto dei popup con:

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

Soluzioni attuali

Che cosa puoi fare al momento per promuovere i tuoi contenuti al di sopra di qualsiasi altra cosa? Se è supportato nel tuo browser, puoi utilizzare l'elemento HTML Dialog. Dovresti utilizzarlo in formato "Modal". Ciò richiede l'utilizzo di JavaScript.

Dialog.showModal();

Ci sono alcune considerazioni sull'accessibilità. Ad esempio, ti consigliamo di utilizzare a11y-dialog per gli utenti di Safari precedenti alla versione 15.4.

Puoi anche utilizzare una delle tante librerie basate su popover, avvisi o descrizioni comando. Molti di questi tendono a funzionare in modo simile.

  • Aggiungi un contenitore al corpo per mostrare i popover.
  • Scegli lo stile che preferisci in modo che si trovi al di sopra di tutto.
  • Crea un elemento e aggiungilo al contenitore per mostrare un popover.
  • Nascondilo rimuovendo l'elemento popover dal DOM.

Ciò richiede un'ulteriore dipendenza e più decisioni per gli sviluppatori. Richiede anche ricerche per trovare un'offerta che fornisca tutto ciò di cui hai bisogno. L'API Popover mira a soddisfare molti scenari, tra cui le descrizioni comando. L'obiettivo è coprire tutti questi scenari comuni, evitando agli sviluppatori di dover prendere un'altra decisione in modo da potersi concentrare sulla creazione delle loro esperienze.

Il tuo primo popup

Non ti serve altro.

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

Ma che cosa sta succedendo qui?

  • Non è necessario inserire l'elemento popover in un contenitore o altro: è nascosto per impostazione predefinita.
  • Non occorre scrivere codice JavaScript per farla apparire. Questo viene gestito dall'attributo popovertoggletarget.
  • Quando viene visualizzato, viene portato al livello superiore. Ciò significa che viene promosso sopra document nell'area visibile. Non è necessario gestire z-index né preoccuparti della posizione del popover nel DOM. Potrebbe essere nidificato in basso nel DOM, con i predecessori di ritaglio. Puoi anche vedere quali elementi si trovano attualmente nel livello superiore tramite DevTools. Per ulteriori informazioni per il livello superiore, consulta questo articolo.

GIF del supporto del livello superiore DevTools in fase di dimostrazione

  • La funzionalità "Spegnimento/Luce" è disponibile fin da subito. Questo vuol dire che puoi chiudere il popover con un segnale di chiusura, ad esempio facendo clic all'esterno del popover, passando da tastiera a un altro elemento o premendo il tasto Esc. Aprila di nuovo e prova.

Cos'altro ti serve con i popover? Vediamo più in dettaglio l'esempio. Considera questa demo con alcuni contenuti della pagina.

Questo pulsante di azione mobile ha un posizionamento fisso con un valore z-index elevato.

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

I contenuti popover sono nidificati nel DOM, ma quando lo apri, vengono promossi sopra l'elemento di posizione fissa. Non è necessario impostare alcuno stile.

Potresti anche notare che il popover ora ha uno pseudo elemento ::backdrop. Tutti gli elementi che si trovano nel livello superiore ricevono uno pseudo elemento ::backdrop con stile. Questo esempio applica uno stile a ::backdrop con un colore di sfondo alpha ridotto e un filtro dello sfondo che sfoca i contenuti sottostanti.

Stilizzare un popover

Cerchiamo di attirare l'attenzione sullo stile del popover. Per impostazione predefinita, un popover ha una posizione fissa e alcune spaziatura interna applicata. Ha anche display: none. Potresti ignorare questa impostazione per mostrare un popover. Tuttavia, ciò non lo promuoverebbe al livello superiore.

[popover] { display: block; }

Indipendentemente da come promuovi il popover, dopo averlo promosso nello strato superiore, potrebbe essere necessario impaginarlo o posizionarlo. Non puoi scegliere come target il livello superiore e

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

Per impostazione predefinita, utilizzando margin: auto verrà visualizzato un popover al centro dell'area visibile. In alcuni casi, però, potresti voler essere esplicito riguardo al posizionamento. Ad esempio:

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

Se vuoi disporre i contenuti all'interno del popover utilizzando la griglia CSS o l'flexbox, potrebbe essere opportuno aggregarli all'interno di un elemento. In caso contrario, dovrai dichiarare una regola separata che modifica display una volta che il popover si trova nel livello superiore. Se questa opzione è impostata per impostazione predefinita, per impostazione predefinita viene visualizzata l'opzione display: none.

[popover]:open {
 display: flex;
}

Se hai provato la demo, noterai che il popover si sta muovendo dentro e fuori. Puoi spostare i popover all'interno e all'esterno usando lo pseudo-selettore :open. Lo pseudo-selettore :open associa i popover che vengono visualizzati (e quindi nel livello superiore).

Questo esempio utilizza una proprietà personalizzata per promuovere la transizione. Puoi anche applicare una transizione al ::backdrop del popover.

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

Un suggerimento qui è quello di raggruppare transizioni e animazioni in una query multimediale per il movimento. Anche questo può aiutarti a rispettare i tempi. Questo perché non puoi condividere valori tra popover e ::backdrop tramite la proprietà personalizzata.

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

Finora hai utilizzato popovertoggletarget per mostrare un popover. Utilizziamo "Ignora leggera". Tuttavia, puoi utilizzare anche gli attributi popovershowtarget e popoverhidetarget. Aggiungiamo un pulsante a un popover per nasconderlo e cambiamo il pulsante di attivazione/disattivazione in modo da usare popovershowtarget.

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

Come accennato in precedenza, l'API Popover non si limita solo alla nostra nozione storica di popup. Puoi creare applicazioni per tutti i tipi di scenari, come notifiche, menu, descrizioni comando e così via.

Alcuni di questi scenari richiedono modelli di interazione diversi. Interazioni come il passaggio del mouse. L'utilizzo dell'attributo popoverhovertarget è stato sperimentato, ma al momento non è implementato.

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

L'idea è che devi passare il mouse sopra un elemento per visualizzare il target. Questo comportamento potrebbe essere configurato tramite le proprietà CSS. Queste proprietà CSS definiscono la finestra di tempo per il passaggio del mouse sopra un elemento a cui reagisce un popover. Il comportamento predefinito oggetto dell'esperimento aveva un popover dopo un'esplicita 0.5s di :hover. Quindi, avrebbe bisogno di una leggera chiusura o dell'apertura di un altro popover per chiudersi (ulteriori informazioni a breve). Ciò è dovuto all'impostazione della durata dell'occultamento del popover su Infinity.

Nel frattempo, puoi utilizzare JavaScript per il polyfill di questa funzionalità.

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

Il vantaggio di impostare una finestra di passaggio esplicita è che garantisce che l'azione dell'utente sia intenzionale (ad esempio, un utente passa il puntatore del mouse sopra un obiettivo). Non vogliamo mostrare il popup a meno che non sia loro intenzione.

Prova questa demo in cui puoi passare il mouse sopra la destinazione con la finestra impostata su 0.5s.


Prima di esplorare alcuni casi d'uso comuni ed esempi, vediamo alcuni aspetti.


Tipi di popover

Abbiamo parlato del comportamento delle interazioni non JavaScript. E per quanto riguarda il comportamento dei popover nel complesso? Cosa succede se non vuoi che l'opzione "Ignora leggera"? O vuoi applicare un motivo singleton ai tuoi popover?

L'API Popover consente di specificare tre tipi di popover con comportamenti diversi.

[popover=auto]/[popover]:

  • Assistenza per la nidificazione. Questo non significa solo nidificato nel DOM. La definizione di popover ancestrale è:
    • in base alla posizione DOM (figlio).
    • correlato tramite l'attivazione di attributi sugli elementi secondari come popovertoggletarget, popovershowtarget e così via.
    • correlata dall'attributo anchor (API Anchoring CSS in fase di sviluppo).
  • Spegnimento luminoso.
  • Se apri, altri popover che non sono popover ancestrali vengono ignorati. Gioca con la demo qui sotto, che mette in evidenza il funzionamento della nidificazione con i popover ancestrali. Guarda come cambiare alcune delle istanze popoverhidetarget/popovershowtarget in popovertoggletarget cambia le cose.
  • Se scegli la modalità Spia, se scegli una delle due opzioni, vengono ignorate tutte le istanze, mentre se ne ignori uno nella serie vengono ignorate solo quelle che si trovano al di sopra della pila.

[popover=manual]:

  • Non chiude altri popover.
  • Nessuna spia spenta.
  • Richiede la chiusura esplicita tramite elemento di attivazione o JavaScript.

API JavaScript

Quando hai bisogno di un maggiore controllo sui popover, puoi affrontare la questione con JavaScript. Hai a disposizione entrambi i metodi, showPopover e hidePopover. Hai anche popovershow e popoverhide eventi da ascoltare:

Mostrare un popover js popoverElement.showPopover() Nascondere un popover:

popoverElement.hidePopover()

Ascolta il popover mostrato:

popoverElement.addEventListener('popovershow', doSomethingWhenPopoverShows)

Ascolta il popover mostrato e annulla la visualizzazione:

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

Ascoltare un popover nascosto:

popoverElement.addEventListener('popoverhide', doSomethingWhenPopoverHides)

Non puoi annullare l'occultamento di un popover:

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

Controlla se un popover si trova nel livello superiore:

popoverElement.matches(':open')

Questo fornisce una maggiore potenza in alcuni scenari meno comuni. Ad esempio, puoi mostrare un popover dopo un periodo di inattività.

Questa demo contiene popover con popover udibili, quindi avremo bisogno di JavaScript per riprodurre l'audio. Al clic, nascondiamo il popover, riproduciamo l'audio e lo mostriamo di nuovo.

Accessibilità

L'accessibilità è all'avanguardia con l'API Popover. Le mappature di accessibilità associano il popover al relativo elemento di attivazione, in base alle esigenze. Questo significa che non devi dichiarare gli attributi aria-* come aria-haspopup, supponendo che utilizzi uno degli attributi di attivazione come popovertoggletarget.

Per la gestione della messa a fuoco, puoi utilizzare l'attributo di messa a fuoco automatica per spostare lo stato attivo su un elemento all'interno di un popover. Questa procedura è analoga a quanto avviene per una finestra di dialogo, ma la differenza si verifica quando viene ripristinata la messa a fuoco e questo è dovuto all'eliminazione della luce. Nella maggior parte dei casi, la chiusura di un popover ripristina lo stato attivo sull'elemento precedentemente attivo. Tuttavia, l'elemento attivo viene spostato su un elemento su cui è stato fatto clic quando il pulsante è chiuso, se può essere messo a fuoco. Consulta la sezione sulla gestione del focus nel video esplicativo.

Per vedere come funziona, devi aprire la "versione a schermo intero" di questa demo.

In questa demo, l'elemento attivo riceve un contorno verde. Prova a spostarti nell'interfaccia con la tastiera. Nota il punto in cui l'elemento attivo viene restituito quando un popover viene chiuso. Potresti anche notare che se hai premuto un po' il popover, il popover si è chiuso. Questo è tutto. Anche se i popover hanno la gestione del focus, non catturano questa attenzione. Inoltre, la navigazione da tastiera identifica il segnale di chiusura quando lo stato attivo si sposta all'esterno del popover.

Ancoraggio (in fase di sviluppo)

Per quanto riguarda i popover, uno schema insidioso è quello di ancorare l'elemento all'attivatore. Ad esempio, se una descrizione comando è impostata per essere visualizzata sopra il relativo attivatore, ma il documento viene fatto scorrere. La descrizione comando potrebbe essere tagliata dall'area visibile. Attualmente esistono offerte JavaScript per gestire questo problema, ad esempio "UI mobile". che riposizionano la descrizione comando per evitare che ciò accada e utilizzare l'ordine di posizione desiderato.

Tuttavia, vogliamo che tu sia in grado di definirlo con i tuoi stili. C'è un'API complementare in fase di sviluppo insieme all'API Popover per far fronte a questo problema. L'API "CSS Anchor Positioning" ti consente di eseguire il tethering degli elementi con altri elementi e lo fa in modo da riposizionare gli elementi in modo che non vengano tagliati dall'area visibile.

Questa demo utilizza l'API Anchoring nel suo stato attuale. La posizione della barca risponde a quella dell'ancora nell'area visibile.

Di seguito è riportato uno snippet del CSS che sta utilizzando questa demo. Non è richiesto 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);
  }
}

Puoi consultare le specifiche qui. Sarà disponibile anche un polyfill per questa API.

Esempi

Ora che sai cosa offrono i popover e come, vediamo alcuni esempi.

Notifiche

Questa demo mostra una notifica "Copia negli appunti".

  • Usa [popover=manual].
  • Popover del programma d'azione con showPopover.
  • Dopo un timeout di 2000ms, nascondilo con hidePopover.

Toast

Questa demo utilizza il livello superiore per mostrare le notifiche relative allo stile dei messaggi popup.

  • Un popover di tipo manual funge da contenitore.
  • Le nuove notifiche vengono aggiunte al popover, che verrà visualizzato.
  • Vengono rimosse con l'API Web Animazioni al clic e rimosse dal DOM.
  • Se non ci sono toast da mostrare, il popover è nascosto.

Menu nidificato

Questa demo mostra come funziona un menu di navigazione nidificato.

  • Usa [popover=auto] perché consente i popover nidificati.
  • Usa autofocus sul primo link di ogni menu a discesa per navigare con la tastiera.
  • È un candidato perfetto per l'API CSS Anchoring. Tuttavia, per questa demo puoi utilizzare una piccola quantità di JavaScript per aggiornare le posizioni utilizzando le proprietà personalizzate.
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));

Ricorda che, poiché questa demo utilizza autofocus, dovrà essere aperta in "visualizzazione a schermo intero" per la navigazione da tastiera.

Popover multimediale

Questa demo mostra come potresti visualizzare dei contenuti multimediali.

  • Utilizza [popover=auto] per ignorare la sveglia.
  • JavaScript rimane in ascolto dell'evento play del video e apre il video.
  • L'evento popover popoverhide mette in pausa il video.

Popover in stile Wiki

Questa demo mostra come creare descrizioni comando per i contenuti incorporati che includono contenuti multimediali.

  • Usa [popover=auto]. Mostrarne uno nasconde le altre perché non sono ancestrali.
  • Mostrato su pointerenter con JavaScript.
  • Un altro candidato perfetto per l'API CSS Anchoring.

Questa demo crea un riquadro di navigazione a scomparsa utilizzando un popover.

  • Utilizza [popover=auto] per ignorare la sveglia.
  • Utilizza autofocus per impostare lo stato attivo sul primo elemento di navigazione.

Gestione degli sfondi

Questa demo mostra come gestire gli sfondi per più popover in cui vuoi che sia visibile un solo elemento ::backdrop.

  • Utilizza JavaScript per gestire un elenco dei popover visibili.
  • Applica un nome di classe al popover più basso nel livello superiore.

Popover cursore personalizzato

Questa demo mostra come utilizzare popover per promuovere canvas al livello superiore e utilizzarlo per mostrare un cursore personalizzato.

  • Promuovi canvas al livello superiore con showPopover e [popover=manual].
  • Quando vengono aperti altri popover, nascondi e mostra il popover canvas per assicurarti che sia in cima.

Popover del foglio azioni

Questa demo mostra come utilizzare un popover come foglio per le azioni.

  • Per impostazione predefinita, il popover esegue l'override di display.
  • Il foglio delle azioni viene aperto con l'attivatore popover.
  • Quando viene visualizzato, il popover viene portato al livello superiore e convertito in visualizzazione.
  • Questa opzione può essere utilizzata per restituire il dispositivo.

Popover attivato dalla tastiera

Questa demo mostra come utilizzare popover per l'interfaccia utente dello stile della tavolozza dei comandi.

  • Utilizza Cmd + J per visualizzare il popover.
  • L'obiettivo input è quello di autofocus.
  • La casella combinata è un secondo popover posizionato sotto l'input principale.
  • Ignora chiaro consente di chiudere la tavolozza se il menu a discesa non è presente.
  • Un altro candidato per l'API Anchoring

Popover a tempo

Questa demo mostra un popover di inattività dopo quattro secondi. Un pattern UI spesso usato nelle app che contengono informazioni sicure su un utente per mostrare una finestra modale di disconnessione.

  • Utilizza JavaScript per visualizzare il popover dopo un periodo di inattività.
  • Quando visualizzi i popover, reimposta il timer.

Screensaver

Come per la demo precedente, potresti aggiungere un pizzico di creatività al tuo sito e aggiungere un salvaschermo.

  • Utilizza JavaScript per visualizzare il popover dopo un periodo di inattività.
  • La spia si spegne per nascondere e reimpostare il timer.

Segui il cursore del testo

Questa demo mostra come fare in modo che un popover segua un cursore di input.

  • Mostra il popover in base alla selezione, all'evento chiave o all'immissione di un carattere speciale.
  • Utilizza JavaScript per aggiornare la posizione del popover con le proprietà personalizzate con ambito.
  • Questo schema richiede un attento esame dei contenuti mostrati e dell'accessibilità.
  • È spesso presente nell'interfaccia utente e nelle app di modifica del testo in cui è possibile taggare.

Menu del pulsante di azione mobile

Questa demo mostra come utilizzare i popover per implementare un menu di pulsanti di azione mobile senza JavaScript.

  • Promuovi un popover di tipo manual con il metodo showPopover. Questo è il pulsante principale.
  • Il menu è un altro popover target del pulsante principale.
  • Il menu si apre con popovertoggletarget.
  • Usa autofocus per impostare lo stato attivo sulla prima voce di menu mostrata.
  • La spia per ignorare chiude il menu.
  • La rotazione dell'icona utilizza :has(). Puoi scoprire di più su :has() in questo articolo.

È tutto.

Questa è un'introduzione ai popover, che verranno introdotti nell'ambito dell'iniziativa Open UI. Usato in modo ragionevole, sarà un'aggiunta fantastica alla piattaforma web.

Assicurati di dare un'occhiata all'interfaccia utente aperta. La spiegazione popover viene sempre aggiornata man mano che l'API si evolve. Ed ecco la raccolta per tutte le demo.

Grazie per aver "potato"!


Foto di Madison Oren su Unsplash