Una panoramica di base su come creare modelli mini e megamodali adatti al colore, adattabili e accessibili con l'elemento <dialog>
.
In questo post voglio condividere i miei pareri su come creare
adattabili al colore,
modelli mini e megamodali adattabili e accessibili con l'elemento <dialog>
.
Prova la demo e guarda il
Fonte.
Se preferisci i video, ecco una versione di questo post su YouTube:
Panoramica
La
<dialog>
è ottimo per le informazioni contestuali o le azioni in-page. Pensa a quando
l'esperienza utente può trarre vantaggio da un'azione sulla stessa pagina anziché su più pagine
azione: forse perché il modulo è piccolo o è l'unica azione richiesta dal
conferma o annulla.
L'elemento <dialog>
è recentemente diventato stabile nei vari browser:
Ho notato che nell'elemento mancano alcune cose, quindi in questa GUI Sfida Aggiungo l'esperienza di sviluppo elementi previsti: eventi aggiuntivi, chiusura di luci, animazioni personalizzate e un mini e mega-type.
Aumento
Gli elementi di base di un elemento <dialog>
sono modesti. L'elemento
vengono nascosti automaticamente e presentano stili integrati per sovrapporsi ai contenuti.
<dialog>
…
</dialog>
Possiamo migliorare questo valore di riferimento.
Tradizionalmente, un elemento di dialogo condivide molto con un modale e spesso i nomi
sono intercambiabili. Mi sono presa la libertà di usare l'elemento finestra di dialogo per
sia piccole finestre di dialogo (mini) sia finestre di dialogo a pagina intera (mega). Ho denominato
grandi e mini, con entrambe le finestre di dialogo leggermente adattate ai diversi casi d'uso.
Ho aggiunto un attributo modal-mode
per consentirti di specificare il tipo:
<dialog id="MegaDialog" modal-mode="mega"></dialog>
<dialog id="MiniDialog" modal-mode="mini"></dialog>
Non sempre, ma in genere gli elementi delle finestre di dialogo verranno utilizzati per raccogliere alcuni
informazioni sull'interazione. I moduli all'interno di elementi di finestre di dialogo sono pensati per essere subito
insieme.
È consigliabile che un elemento modulo racchiuda i contenuti delle finestre di dialogo in modo che
JavaScript può accedere ai dati inseriti dall'utente. Inoltre, i pulsanti all'interno
un modulo che utilizza method="dialog"
può chiudere una finestra di dialogo senza JavaScript e passare
e i dati di Google Cloud.
<dialog id="MegaDialog" modal-mode="mega">
<form method="dialog">
…
<button value="cancel">Cancel</button>
<button value="confirm">Confirm</button>
</form>
</dialog>
Finestra di dialogo Mega
Una mega finestra di dialogo contiene tre elementi all'interno del modulo:
<header>
,
<article>
,
e
<footer>
Questi fungono da container semantici, oltre a target di stile per
presentazione della finestra di dialogo. L'intestazione dà un titolo alla modale e offre una
. L'articolo serve per input e informazioni sui moduli. Il piè di pagina contiene un
<menu>
di
pulsanti di azione.
<dialog id="MegaDialog" modal-mode="mega">
<form method="dialog">
<header>
<h3>Dialog title</h3>
<button onclick="this.closest('dialog').close('close')"></button>
</header>
<article>...</article>
<footer>
<menu>
<button autofocus type="reset" onclick="this.closest('dialog').close('cancel')">Cancel</button>
<button type="submit" value="confirm">Confirm</button>
</menu>
</footer>
</form>
</dialog>
Il primo pulsante di menu ha
autofocus
e un gestore di eventi in linea onclick
. L'attributo autofocus
riceverà
lo stato attivo quando la finestra di dialogo è aperta. Secondo me è una best practice
sul pulsante Annulla, non sul pulsante di conferma. In questo modo ti assicuri che la conferma
intenzionale e non accidentale.
Mini finestra di dialogo
La mini finestra di dialogo è molto simile alla mega finestra di dialogo, manca un
Elemento <header>
. In questo modo è più piccolo e più in linea.
<dialog id="MiniDialog" modal-mode="mini">
<form method="dialog">
<article>
<p>Are you sure you want to remove this user?</p>
</article>
<footer>
<menu>
<button autofocus type="reset" onclick="this.closest('dialog').close('cancel')">Cancel</button>
<button type="submit" value="confirm">Confirm</button>
</menu>
</footer>
</form>
</dialog>
L'elemento finestra di dialogo fornisce una solida base per un elemento dell'area visibile completa che possono raccogliere dati e interazioni degli utenti. Questi elementi essenziali possono rendere molto interazioni più interessanti ed efficaci sul tuo sito o nella tua app.
Accessibilità
L'elemento della finestra di dialogo ha un'accessibilità integrata molto buona. Invece di aggiungerli come al solito, molte sono già presenti.
Ripristino dello stato attivo in corso...
Come abbiamo fatto manualmente in Creazione di una navigazione laterale , è importante che l'apertura e la chiusura di qualcosa pone correttamente l'attenzione sull'apertura e la chiusura pertinenti pulsanti. Quando si apre la barra di navigazione laterale, lo stato attivo viene impostato sul pulsante di chiusura. Quando premuto il pulsante di chiusura, viene ripristinato lo stato attivo sul pulsante che l'ha aperto.
Nell'elemento finestra di dialogo, il comportamento predefinito è integrato:
Sfortunatamente, se vuoi animare la finestra di dialogo dentro e fuori, questa funzionalità è perduto. Ripristinerò lo stesso file nella sezione JavaScript. funzionalità.
Messa a fuoco in primo piano
L'elemento finestra di dialogo gestisce
inert
per te sul documento. Prima del giorno inert
, veniva utilizzato JavaScript per controllare lo stato attivo
lasciando un elemento, a quel punto lo intercetta e lo restituisce.
Dopo il giorno inert
, qualsiasi parte del documento potrebbe essere "bloccata" in quanto sono
non sono più incentrati sui target o sono interattivi con il mouse. Invece di intrappolare
lo stato attivo viene guidato nell'unica parte interattiva del documento.
Aprire e impostare lo stato attivo su un elemento automaticamente
Per impostazione predefinita, l'elemento della finestra di dialogo assegna lo stato attivo al primo elemento attivabile
nel markup delle finestre di dialogo. Se questo non è l'elemento migliore
per l'utente per impostazione predefinita,
utilizza l'attributo autofocus
. Come spiegato in precedenza, è una best practice
per metterlo sul pulsante Annulla e non sul pulsante di conferma. Ciò garantisce che
la conferma è intenzionale e non accidentale.
Chiusura con il tasto Esc
È importante facilitare la chiusura di questo elemento potenzialmente invasivo. Fortunatamente, l'elemento della finestra di dialogo gestirà il tasto Esc per te, liberandoti dall'onere dell'orchestrazione.
Stili
È disponibile un percorso semplice per lo stile dell'elemento della finestra di dialogo e un percorso difficile. Il modo più semplice
si ottiene non modificando la proprietà di visualizzazione della finestra di dialogo
con i suoi limiti. Seguiamo il percorso difficile per fornire animazioni personalizzate
l'apertura e la chiusura della finestra di dialogo, prendendo il controllo della proprietà display
e altre ancora.
Stili con oggetti di scena aperti
Per accelerare i colori adattivi e l'uniformità del design generale, ho spudoratamente trasferito nella libreria di variabili CSS Open Props. Nella oltre alle variabili fornite senza costi, importo anche un normalizza il file e alcuni pulsanti, entrambi aperti fornisce come importazioni facoltative. Queste importazioni mi permettono di concentrarmi sulla personalizzazione una finestra di dialogo e una demo pur non avendo bisogno di molti stili per supportarla bene.
Stile dell'elemento <dialog>
Possedere la proprietà display
Il comportamento predefinito di visualizzazione e occultamento di un elemento di finestra di dialogo attiva/disattiva la visualizzazione
proprietà da block
a none
. Ciò significa che non è possibile animare
dentro e fuori, solo dentro. Vorrei eseguire l'animazione sia in entrata che in uscita e il primo passaggio è
per impostare il mio
proprietà display:
dialog {
display: grid;
}
Modificando, e quindi possedendo, il valore della proprietà display, come mostrato nell' precedente snippet CSS, è necessario gestire una notevole quantità di stili per per creare un'esperienza utente corretta. Innanzitutto, lo stato predefinito di una finestra di dialogo chiuso. Puoi rappresentare visivamente questo stato e impedire che la finestra di dialogo ricevere interazioni con i seguenti stili:
dialog:not([open]) {
pointer-events: none;
opacity: 0;
}
Ora la finestra di dialogo è invisibile e non può essere interagito se non è aperta. Più tardi
Aggiungerò codice JavaScript per gestire l'attributo inert
nella finestra di dialogo, assicurando che
gli utenti di tastiera e screen reader non riescono a raggiungere la finestra di dialogo nascosta.
Impostare un tema cromatico adattivo alla finestra di dialogo
Mentre color-scheme
attiva il tuo documento in un servizio fornito dal browser
il tema cromatico adattivo alle preferenze di sistema chiare e scure, volevo personalizzare
l'elemento della finestra di dialogo. Gli oggetti aperti offrono una certa superficie
colori che si adattano automaticamente
preferenze di sistema chiare e scure, in modo simile all'uso di color-scheme
. Questi
sono ideali per creare strati in un progetto e mi piace usare i colori per
supportano visivamente l'aspetto delle superfici degli strati. Il colore di sfondo è
var(--surface-1)
; per posizionare il documento su quel livello, utilizza var(--surface-2)
:
dialog {
…
background: var(--surface-2);
color: var(--text-1);
}
@media (prefers-color-scheme: dark) {
dialog {
border-block-start: var(--border-size-1) solid var(--surface-3);
}
}
In seguito verranno aggiunti altri colori adattivi per gli elementi secondari, come l'intestazione e piè di pagina. Li ritengo un elemento extra come elemento di dialogo, ma molto importante realizzare finestre di dialogo convincenti e ben progettate.
Dimensionamento dei dialoghi adattabili
Per impostazione predefinita, la finestra di dialogo delega le proprie dimensioni ai contenuti, il che in genere
bene. Il mio obiettivo qui è limitare
max-inline-size
a una dimensione leggibile (--size-content-3
= 60ch
) o al 90% della larghezza dell'area visibile. Questo
assicura che la finestra di dialogo non si spinga bordo a bordo su un dispositivo mobile e non accadrà
largo su uno schermo di computer
di difficile lettura. Quindi aggiungo un
max-block-size
in modo che la finestra di dialogo non superi l'altezza della pagina. Ciò significa anche che
devi specificare dove si trova l'area scorrevole della finestra di dialogo, nel caso sia alta
finestra di dialogo.
dialog {
…
max-inline-size: min(90vw, var(--size-content-3));
max-block-size: min(80vh, 100%);
max-block-size: min(80dvb, 100%);
overflow: hidden;
}
Noti che max-block-size
fa due volte? Il primo utilizza 80vh
, un indirizzo
area visibile. Quello che voglio davvero è mantenere il dialogo all'interno
del flusso relativo,
per gli utenti internazionali, quindi uso la logica, la più recente e solo parzialmente
dvb
unità supportata nella seconda dichiarazione, che indica quando diventa più stabile.
Posizionamento di finestre di dialogo mega
Per posizionare più facilmente un elemento di finestra di dialogo, è consigliabile scomporre i due parti: lo sfondo a schermo intero e il contenitore delle finestre di dialogo. Lo sfondo deve coprire tutto, creando un effetto sfumatura per contribuire a rendere questa finestra di dialogo davanti e i contenuti dietro sono inaccessibili. Il contenitore di dialogo è libero posizionarsi al centro dello sfondo e assumere la forma richiesta dai contenuti.
I seguenti stili fissano l'elemento della finestra di dialogo alla finestra, estendendolo a ogni
angolo e utilizza margin: auto
per centrare i contenuti:
dialog {
…
margin: auto;
padding: 0;
position: fixed;
inset: 0;
z-index: var(--layer-important);
}
Mega stili di finestre di dialogo mobile
Nelle aree visibili di piccole dimensioni, lo stile della megamodale a pagina intera è leggermente diverso. IO
imposta il margine inferiore su 0
, in modo da portare i contenuti della finestra di dialogo in fondo
nell'area visibile. Con un paio di modifiche allo stile, posso trasformare il dialogo in un
foglio d'azione, più vicino ai pollici dell'utente:
@media (max-width: 768px) {
dialog[modal-mode="mega"] {
margin-block-end: 0;
border-end-end-radius: 0;
border-end-start-radius: 0;
}
}
Posizionamento di mini finestre di dialogo
Quando utilizzo un'area visibile più grande, ad esempio su un computer desktop, ho scelto di posizionare le mini finestre di dialogo l'elemento che li ha chiamati. Per farlo, mi serve JavaScript. Puoi trovare le tecnica che uso qui, ma ritengo che non rientri nell'ambito di questo articolo. Senza il codice JavaScript, una mini finestra di dialogo viene visualizzata al centro dello schermo, proprio come una mega finestra di dialogo.
Metti in risalto
Infine, aggiungi un tocco di originalità alla finestra di dialogo in modo che appaia come una superficie morbida lontana sopra la pagina. La morbidezza si ottiene arrotondando gli angoli della finestra di dialogo. La profondità si ottiene con l'ombra di uno degli oggetti di scena aperti oggetti di scena:
dialog {
…
border-radius: var(--radius-3);
box-shadow: var(--shadow-6);
}
Personalizzazione dello pseudoelemento di sfondo
Ho scelto di lavorare con lo sfondo molto poco, aggiungendo solo un effetto sfocato con
backdrop-filter
alla mega finestra di dialogo:
dialog[modal-mode="mega"]::backdrop {
backdrop-filter: blur(25px);
}
Ho anche scelto di eseguire una transizione su backdrop-filter
, nella speranza che i browser
consentirà la transizione dell'elemento di sfondo in futuro:
dialog::backdrop {
transition: backdrop-filter .5s ease;
}
Stili extra
Chiamo questa sezione "extra" perché ha più a che fare con l'elemento della finestra di dialogo rispetto all'elemento di dialogo in generale.
Contenimento della pergamena
Quando la finestra di dialogo viene visualizzata, l'utente può comunque scorrere la pagina sottostante. cosa che non voglio:
Generalmente,
overscroll-behavior
sarebbe la mia solita soluzione, ma secondo la
specifiche,
non ha alcun effetto sulla finestra di dialogo perché non è una porta di scorrimento, ovvero
a scorrimento, quindi non c'è nulla da impedire. potrei usare JavaScript per controllare
i nuovi eventi di questa guida, ad esempio "chiuso" e "aperto" e attiva/disattiva
overflow: hidden
sul documento oppure potrei attendere che :has()
si stabilizzi in modo stabile
tutti i browser:
html:has(dialog[open][modal-mode="mega"]) {
overflow: hidden;
}
Ora, quando si apre una mega finestra di dialogo, il documento HTML ha overflow: hidden
.
Layout <form>
Oltre a essere un elemento molto importante per la raccolta delle interazioni
informazioni da parte dell'utente, lo uso qui per impaginare intestazioni, piè di pagina
elementi dell'articolo. Con questo layout intendo articolare l'articolo figlio come un
in cui è possibile scorrere. Ottengo questo risultato
grid-template-rows
Per l'elemento dell'articolo è assegnato un valore 1fr
e il modulo stesso ha lo stesso valore massimo
l'altezza dell'elemento della finestra di dialogo. L'impostazione di un'altezza fissa e di una dimensione di riga stabili è
consente di vincolare l'elemento articolo e di scorrere quando ne straripa:
dialog > form {
display: grid;
grid-template-rows: auto 1fr auto;
align-items: start;
max-block-size: 80vh;
max-block-size: 80dvb;
}
Definizione dello stile della finestra di dialogo <header>
Il ruolo di questo elemento è fornire un titolo per i contenuti della finestra di dialogo e offrire un pulsante di chiusura facile da trovare. Viene anche assegnato un colore alla superficie per farlo apparire dietro i contenuti dell'articolo di dialogo. Questi requisiti generano un flexbox contenitore, elementi allineati verticalmente e distanziati rispetto ai bordi e alcuni spaziatura interna e spazi vuoti per dare spazio al titolo e ai pulsanti di chiusura:
dialog > form > header {
display: flex;
gap: var(--size-3);
justify-content: space-between;
align-items: flex-start;
background: var(--surface-2);
padding-block: var(--size-3);
padding-inline: var(--size-5);
}
@media (prefers-color-scheme: dark) {
dialog > form > header {
background: var(--surface-1);
}
}
Definizione dello stile del pulsante di chiusura dell'intestazione
Poiché la demo utilizza i pulsanti Open Props, il pulsante di chiusura è personalizzato in un pulsante rotondo con un'icona al centro, come questo:
dialog > form > header > button {
border-radius: var(--radius-round);
padding: .75ch;
aspect-ratio: 1;
flex-shrink: 0;
place-items: center;
stroke: currentColor;
stroke-width: 3px;
}
Definizione dello stile della finestra di dialogo <article>
L'elemento articolo ha un ruolo speciale in questa finestra di dialogo: è uno spazio destinato a essere fatto scorrere nel caso di una finestra di dialogo lunga o lunga.
A questo scopo, l'elemento modulo principale ha stabilito alcuni valori massimi per
che forniscono vincoli che questo elemento dell'articolo deve raggiungere se ottiene
troppo alto. Imposta overflow-y: auto
in modo che le barre di scorrimento vengano mostrate solo quando necessario.
contiene lo scorrimento al suo interno con overscroll-behavior: contain
e il resto
saranno stili di presentazione personalizzati:
dialog > form > article {
overflow-y: auto;
max-block-size: 100%; /* safari */
overscroll-behavior-y: contain;
display: grid;
justify-items: flex-start;
gap: var(--size-3);
box-shadow: var(--shadow-2);
z-index: var(--layer-1);
padding-inline: var(--size-5);
padding-block: var(--size-3);
}
@media (prefers-color-scheme: light) {
dialog > form > article {
background: var(--surface-1);
}
}
Definizione dello stile della finestra di dialogo <footer>
Il piè di pagina deve contenere menu di pulsanti di azione. Flexbox viene utilizzato allinea i contenuti alla fine dell'asse in linea del piè di pagina, quindi una certa spaziatura per di lasciare spazio ai pulsanti.
dialog > form > footer {
background: var(--surface-2);
display: flex;
flex-wrap: wrap;
gap: var(--size-3);
justify-content: space-between;
align-items: flex-start;
padding-inline: var(--size-5);
padding-block: var(--size-3);
}
@media (prefers-color-scheme: dark) {
dialog > form > footer {
background: var(--surface-1);
}
}
Definizione dello stile del menu a piè di pagina della finestra di dialogo
La menu
viene utilizzato per contenere i pulsanti di azione per la finestra di dialogo. Utilizza un wrapping
layout flexbox con gap
per lasciare spazio tra i pulsanti. Elementi del menu
presentano una spaziatura interna, ad esempio <ul>
. Inoltre lo rimuovo perché non mi serve.
dialog > form > footer > menu {
display: flex;
flex-wrap: wrap;
gap: var(--size-3);
padding-inline-start: 0;
}
dialog > form > footer > menu:only-child {
margin-inline-start: auto;
}
Animazione
Gli elementi della finestra di dialogo sono spesso animati perché entrano ed escono dalla finestra. L'assegnazione di un movimento di supporto per l'entrata e l'uscita ai dialoghi aiuta gli utenti orientarsi nel flusso.
Normalmente l'elemento della finestra di dialogo può essere solo animato all'interno, non all'esterno. Questo perché
il browser attiva/disattiva la proprietà display
dell'elemento. In precedenza, la guida
imposta la visualizzazione su griglia e non la imposta mai su nessuno. In questo modo, è possibile
si animano dentro e fuori.
Gli oggetti Open Props sono dotati di molti fotogrammi chiave animazioni, il che rende orchestrazione semplice e leggibile. Ecco gli obiettivi delle animazioni l'approccio che ho adottato:
- Il movimento ridotto è la transizione predefinita, con una semplice dissolvenza in entrata e in uscita dall'opacità.
- Se il movimento è corretto, vengono aggiunte animazioni di scorrimento e scala.
- Il layout mobile adattabile della mega finestra di dialogo viene modificato in modo da scorrere verso l'esterno.
Una transizione predefinita sicura e significativa
Anche se gli oggetti aperti sono dotati di fotogrammi chiave per la dissolvenza in entrata e in uscita, preferisco questo
approccio a più livelli delle transizioni come impostazione predefinita, mentre le animazioni dei fotogrammi chiave
potenziali upgrade. In precedenza abbiamo già assegnato uno stile alla visibilità della finestra di dialogo
opacità, orchestrando 1
o 0
in base all'attributo [open]
. A
transizione tra 0% e 100%, comunicare al browser la durata e il tipo
dell'output desiderato:
dialog {
transition: opacity .5s var(--ease-3);
}
Aggiunta di movimento alla transizione
Se l'utente è a suo agio con il movimento, entrambe le finestre di dialogo mega e mini dovrebbero scorrere
come ingresso e lo scale out come uscita. Puoi farlo con
prefers-reduced-motion
query multimediale e alcuni oggetti aperti:
@media (prefers-reduced-motion: no-preference) {
dialog {
animation: var(--animation-scale-down) forwards;
animation-timing-function: var(--ease-squish-3);
}
dialog[open] {
animation: var(--animation-slide-in-up) forwards;
}
}
Adattare l'animazione di uscita per il mobile
In precedenza, nella sezione Stili, lo stile delle finestre di dialogo mega è stato adattato per i dispositivi mobili. i dispositivi in modo che siano più simili a un foglio azioni, come se un pezzo di carta verso l'alto dalla parte inferiore dello schermo ed è ancora attaccata alla parte inferiore. La scala l'animazione di uscita non si adatta bene a questo nuovo design e possiamo adattarla un paio di query multimediali e alcuni oggetti di scena aperti:
@media (prefers-reduced-motion: no-preference) and @media (max-width: 768px) {
dialog[modal-mode="mega"] {
animation: var(--animation-slide-out-down) forwards;
animation-timing-function: var(--ease-squish-2);
}
}
JavaScript
Ci sono diversi elementi da aggiungere con JavaScript:
// dialog.js
export default async function (dialog) {
// add light dismiss
// add closing and closed events
// add opening and opened events
// add removed event
// removing loading attribute
}
Queste aggiunte nascono dal desiderio di ignorare la luce (facendo clic sulla finestra di dialogo sfondo), l'animazione e alcuni eventi aggiuntivi per ottimizzare la tempistica della visualizzazione i dati del modulo.
Aggiunta della funzione Ignora luce
Questa attività è semplice e rappresenta un'ottima aggiunta a un elemento di dialogo che non
essere animati. L'interazione si ottiene osservando i clic sulla finestra di dialogo
e sfruttare gli eventi
gorgogliamento
per valutare gli elementi sui quali è stato fatto clic e
close()
:
se è l'elemento più in alto:
export default async function (dialog) {
dialog.addEventListener('click', lightDismiss)
}
const lightDismiss = ({target:dialog}) => {
if (dialog.nodeName === 'DIALOG')
dialog.close('dismiss')
}
Nota dialog.close('dismiss')
. L'evento viene richiamato e viene fornita una stringa.
Questa stringa può essere recuperata da altri elementi JavaScript per ottenere informazioni su come la stringa
la finestra di dialogo è stata chiusa. Scoprirai che ho fornito anche stringhe di chiusura ogni volta che chiamo
la funzione da vari pulsanti, per fornire un contesto alla mia applicazione
l'interazione dell'utente.
Aggiunta di eventi di chiusura e chiusura
L'elemento della finestra di dialogo viene fornito con un evento di chiusura: viene emesso immediatamente quando
viene richiamata la funzione di dialogo close()
. Dato che stiamo animando questo elemento,
è utile avere eventi prima e dopo l'animazione, per cambiare
o reimpostare il modulo della finestra di dialogo. Lo utilizzo qui per gestire l'aggiunta
Attributo inert
nella finestra di dialogo chiusa e nella demo li uso per modificarlo
l'elenco di avatar se l'utente ha inviato una nuova immagine.
A questo scopo, crea due nuovi eventi denominati closing
e closed
. Poi
rimanere in ascolto dell'evento di chiusura integrato nella finestra di dialogo. Da qui, imposta la finestra di dialogo su
inert
e invia l'evento closing
. L'attività successiva è attendere che
animazioni e transizioni per terminare l'esecuzione sulla finestra di dialogo, quindi invia
closed
evento.
const dialogClosingEvent = new Event('closing')
const dialogClosedEvent = new Event('closed')
export default async function (dialog) {
…
dialog.addEventListener('close', dialogClose)
}
const dialogClose = async ({target:dialog}) => {
dialog.setAttribute('inert', '')
dialog.dispatchEvent(dialogClosingEvent)
await animationsComplete(dialog)
dialog.dispatchEvent(dialogClosedEvent)
}
const animationsComplete = element =>
Promise.allSettled(
element.getAnimations().map(animation =>
animation.finished))
La funzione animationsComplete
, utilizzata anche nella sezione Creazione di un avviso popup
, restituisce una promessa basata sul
il completamento dell'animazione e le promesse di transizione. Ecco perché dialogClose
è una funzione asincrona
funzione;
può quindi
await
la promessa è tornata e si procede con fiducia all'evento chiuso.
Aggiunta di eventi di apertura e apertura in corso...
Aggiungere questi eventi non è facile perché l'elemento di dialogo integrato fornisce un evento aperto come fa con la chiusura. Uso un MutationObserver per fornire insight sul cambiamento degli attributi della finestra di dialogo. In questo osservatore, Cercherò le modifiche all'attributo aperto e gestirò gli eventi personalizzati di conseguenza.
Analogamente a come abbiamo iniziato gli eventi di chiusura e chiusi, crea due nuovi eventi
chiamati opening
e opened
. Dove in precedenza abbiamo ascoltato la chiusura della finestra di dialogo
, questa volta usa un osservatore delle mutazioni creato per controllare la finestra di dialogo
attributi.
…
const dialogOpeningEvent = new Event('opening')
const dialogOpenedEvent = new Event('opened')
export default async function (dialog) {
…
dialogAttrObserver.observe(dialog, {
attributes: true,
})
}
const dialogAttrObserver = new MutationObserver((mutations, observer) => {
mutations.forEach(async mutation => {
if (mutation.attributeName === 'open') {
const dialog = mutation.target
const isOpen = dialog.hasAttribute('open')
if (!isOpen) return
dialog.removeAttribute('inert')
// set focus
const focusTarget = dialog.querySelector('[autofocus]')
focusTarget
? focusTarget.focus()
: dialog.querySelector('button').focus()
dialog.dispatchEvent(dialogOpeningEvent)
await animationsComplete(dialog)
dialog.dispatchEvent(dialogOpenedEvent)
}
})
})
La funzione di callback dell'osservatore mutazioni verrà chiamata quando la finestra di dialogo
vengono modificati, fornendo l'elenco delle modifiche sotto forma di array. Ripetizione oltre
cambia l'attributo, cercando che attributeName
sia aperto. Poi controlla
se l'elemento ha o meno l'attributo: indica se la finestra di dialogo
è diventata aperta. Se è stato aperto, rimuovi l'attributo inert
e imposta lo stato attivo
a un elemento che richiede
autofocus
o il primo elemento button
trovato nella finestra di dialogo. Infine, in modo simile alla chiusura
e chiuso, invia subito l'evento di apertura, attendi le animazioni
per terminare, quindi invia l'evento aperto.
Aggiunta di un evento rimosso
Nelle applicazioni a pagina singola, le finestre di dialogo vengono spesso aggiunte e rimosse in base alle route o altre esigenze e stato dell'applicazione. Può essere utile ripulire gli eventi quando viene rimossa una finestra di dialogo.
Puoi farlo con un altro osservatore delle mutazioni. Questa volta, invece di osservando gli attributi su un elemento di dialogo, osserveremo i figli del corpo e verifica che gli elementi delle finestre di dialogo vengano rimossi.
…
const dialogRemovedEvent = new Event('removed')
export default async function (dialog) {
…
dialogDeleteObserver.observe(document.body, {
attributes: false,
subtree: false,
childList: true,
})
}
const dialogDeleteObserver = new MutationObserver((mutations, observer) => {
mutations.forEach(mutation => {
mutation.removedNodes.forEach(removedNode => {
if (removedNode.nodeName === 'DIALOG') {
removedNode.removeEventListener('click', lightDismiss)
removedNode.removeEventListener('close', dialogClose)
removedNode.dispatchEvent(dialogRemovedEvent)
}
})
})
})
Il callback di osservatore delle mutazioni viene chiamato ogni volta che vengono aggiunti o rimossi bambini
dal corpo del documento. Le mutazioni specifiche osservate sono per
removedNodes
con
nodeName
di
una finestra di dialogo. Se è stata rimossa una finestra di dialogo, gli eventi di clic e chiusura vengono rimossi a
liberare memoria e inviare l'evento personalizzato rimosso.
Rimozione dell'attributo di caricamento
Per impedire che l'animazione della finestra di dialogo riproduca l'animazione di uscita quando viene aggiunta a la pagina o al caricamento della pagina, nella finestra di dialogo è stato aggiunto un attributo di caricamento. La lo script seguente attende il termine dell'esecuzione delle animazioni della finestra di dialogo, quindi rimuove l'attributo. Ora la finestra di dialogo può essere animata in entrata e in uscita e abbiamo per nascondere in modo efficace un'animazione altrimenti distraente.
export default async function (dialog) {
…
await animationsComplete(dialog)
dialog.removeAttribute('loading')
}
Scopri di più sul problema di impedire le animazioni dei fotogrammi chiave durante il caricamento della pagina qui.
Tutti insieme
Ora che abbiamo spiegato ogni sezione, ecco dialog.js
nella sua interezza.
singolarmente:
// custom events to be added to <dialog>
const dialogClosingEvent = new Event('closing')
const dialogClosedEvent = new Event('closed')
const dialogOpeningEvent = new Event('opening')
const dialogOpenedEvent = new Event('opened')
const dialogRemovedEvent = new Event('removed')
// track opening
const dialogAttrObserver = new MutationObserver((mutations, observer) => {
mutations.forEach(async mutation => {
if (mutation.attributeName === 'open') {
const dialog = mutation.target
const isOpen = dialog.hasAttribute('open')
if (!isOpen) return
dialog.removeAttribute('inert')
// set focus
const focusTarget = dialog.querySelector('[autofocus]')
focusTarget
? focusTarget.focus()
: dialog.querySelector('button').focus()
dialog.dispatchEvent(dialogOpeningEvent)
await animationsComplete(dialog)
dialog.dispatchEvent(dialogOpenedEvent)
}
})
})
// track deletion
const dialogDeleteObserver = new MutationObserver((mutations, observer) => {
mutations.forEach(mutation => {
mutation.removedNodes.forEach(removedNode => {
if (removedNode.nodeName === 'DIALOG') {
removedNode.removeEventListener('click', lightDismiss)
removedNode.removeEventListener('close', dialogClose)
removedNode.dispatchEvent(dialogRemovedEvent)
}
})
})
})
// wait for all dialog animations to complete their promises
const animationsComplete = element =>
Promise.allSettled(
element.getAnimations().map(animation =>
animation.finished))
// click outside the dialog handler
const lightDismiss = ({target:dialog}) => {
if (dialog.nodeName === 'DIALOG')
dialog.close('dismiss')
}
const dialogClose = async ({target:dialog}) => {
dialog.setAttribute('inert', '')
dialog.dispatchEvent(dialogClosingEvent)
await animationsComplete(dialog)
dialog.dispatchEvent(dialogClosedEvent)
}
// page load dialogs setup
export default async function (dialog) {
dialog.addEventListener('click', lightDismiss)
dialog.addEventListener('close', dialogClose)
dialogAttrObserver.observe(dialog, {
attributes: true,
})
dialogDeleteObserver.observe(document.body, {
attributes: false,
subtree: false,
childList: true,
})
// remove loading attribute
// prevent page load @keyframes playing
await animationsComplete(dialog)
dialog.removeAttribute('loading')
}
Utilizzo del modulo dialog.js
La funzione esportata dal modulo prevede di essere chiamata e passata una finestra di dialogo che richiede l'aggiunta di questi nuovi eventi e funzionalità:
import GuiDialog from './dialog.js'
const MegaDialog = document.querySelector('#MegaDialog')
const MiniDialog = document.querySelector('#MiniDialog')
GuiDialog(MegaDialog)
GuiDialog(MiniDialog)
In breve, abbiamo eseguito l'upgrade delle due finestre di dialogo caricamento di correzioni e altri eventi con cui lavorare.
Ascoltare i nuovi eventi personalizzati
Ora ogni elemento di finestra di dialogo aggiornato può rimanere in ascolto di cinque nuovi eventi, come questo:
MegaDialog.addEventListener('closing', dialogClosing)
MegaDialog.addEventListener('closed', dialogClosed)
MegaDialog.addEventListener('opening', dialogOpening)
MegaDialog.addEventListener('opened', dialogOpened)
MegaDialog.addEventListener('removed', dialogRemoved)
Ecco due esempi di come gestire questi eventi:
const dialogOpening = ({target:dialog}) => {
console.log('Dialog opening', dialog)
}
const dialogClosed = ({target:dialog}) => {
console.log('Dialog closed', dialog)
console.info('Dialog user action:', dialog.returnValue)
if (dialog.returnValue === 'confirm') {
// do stuff with the form values
const dialogFormData = new FormData(dialog.querySelector('form'))
console.info('Dialog form data', Object.fromEntries(dialogFormData.entries()))
// then reset the form
dialog.querySelector('form')?.reset()
}
}
Nella demo che ho creato con l'elemento finestra di dialogo, uso l'evento chiuso e i dati del modulo per aggiungere un nuovo elemento avatar all'elenco. Il tempismo è buono che la finestra di dialogo abbia completato l'animazione di uscita e che alcuni script si animano nel nuovo avatar. Grazie ai nuovi eventi, l'orchestrazione dell'esperienza utente può essere più fluido.
Nota dialog.returnValue
: contiene la stringa di chiusura passata quando l'elemento
viene chiamato l'evento della finestra di dialogo close()
. Nell'evento dialogClosed
è fondamentale
per sapere se la finestra di dialogo è stata chiusa, annullata o confermata. Se viene confermato,
quindi recupera i valori del modulo e reimpostalo. Il ripristino è utile in modo che
quando la finestra di dialogo viene visualizzata di nuovo, è vuota e pronta per un nuovo invio.
Conclusione
Ora che sai come ci ho fatto, come faresti‽ 🙂
Diversificaamo i nostri approcci e impariamo tutti i modi per creare sul web.
Crea una demo, twittami con i link e la aggiungerò alla sezione dei remix della community qui sotto.
Remix della community
- @GrimLink con un 3-in-1 Finestra di dialogo.
- @mikemai2awesome con una bella
remix che non modifica
display
proprietà. - @geoffrich_ con Intenso e carino FLIP girevole smaltata.
Risorse
- Codice sorgente su GitHub
- Avatar dei doodle