Dialogkomponente erstellen

Ein grundlegender Überblick darüber, wie du mit dem <dialog>-Element farbadaptive, responsive und barrierefreie Mini- und Mega-Modals erstellen kannst.

In diesem Beitrag möchte ich meine Gedanken zur Entwicklung von Farbanpassungs-, reaktionsschnelle und barrierefreie Mini- und Mega-Modale mit dem <dialog>-Element. Testen Sie die Demo und sehen Sie sich die Quelle.

<ph type="x-smartling-placeholder">
</ph> Darstellung der Mega- und Mini-Dialoge in ihren hellen und dunklen Designs.

Falls Sie Videos bevorzugen, finden Sie hier eine YouTube-Version dieses Beitrags:

Übersicht

Die <dialog> eignet sich hervorragend für In-Page-Kontextinformationen oder Aktionen. Überlegen Sie, wann die Die Nutzererfahrung profitiert von einer Aktion auf derselben Seite statt einer mehrseitigen Aktion. Aktion: z. B. weil das Formular klein ist oder die einzige vom „Bestätigen“ oder „Abbrechen“ des Nutzers.

Das <dialog>-Element ist seit Kurzem browserübergreifend stabil:

Unterstützte Browser

  • Chrome: 37. <ph type="x-smartling-placeholder">
  • Edge: 79. <ph type="x-smartling-placeholder">
  • Firefox: 98 <ph type="x-smartling-placeholder">
  • Safari: 15.4 <ph type="x-smartling-placeholder">

Quelle

Bei dem Element fehlte ein paar Dinge. In diesem GUI Herausforderung: Ich füge die Entwicklererfahrung hinzu. zu erwartende Elemente: zusätzliche Termine, leichtes Schließen, benutzerdefinierte Animationen und eine Miniversion und den Mega-Typ aus.

Markup

Die wesentlichen Elemente eines <dialog>-Elements sind bescheiden. Das Element wird automatisch ausgeblendet und es sind Stile integriert, die Ihre Inhalte überlagern.

<dialog>
  …
</dialog>

Wir können diese Baseline verbessern.

Üblicherweise hat ein Dialogelement ähnlich wie ein modales Element sind austauschbar. Ich habe mir hier frei gelassen, das Dialogelement für sowohl kleine Pop-ups (Mini- als auch Vollbild-Dialogfelder) (Mega). Von mir benannt Mega- und Mini-Dialogfelder, wobei beide Dialoge leicht an verschiedene Anwendungsfälle angepasst wurden. Ich habe ein modal-mode-Attribut hinzugefügt, mit dem Sie den Typ angeben können:

<dialog id="MegaDialog" modal-mode="mega"></dialog>
<dialog id="MiniDialog" modal-mode="mini"></dialog>

Screenshot der Mini- und der Mega-Dialoge in hellem und dunklem Design.

Nicht immer, aber generell werden Dialogelemente verwendet, um einige Interaktionsinformationen. Formulare in Dialogelementen sind wie gemacht zusammen. Es empfiehlt sich, den Inhalt des Dialogs mit einem Formularelement zu umschließen, JavaScript kann auf die vom Benutzer eingegebenen Daten zugreifen. Außerdem können Schaltflächen in den ein Formular, das method="dialog" verwendet, kann ein Dialogfeld ohne JavaScript schließen und Daten.

<dialog id="MegaDialog" modal-mode="mega">
  <form method="dialog">
    …
    <button value="cancel">Cancel</button>
    <button value="confirm">Confirm</button>
  </form>
</dialog>

Mega-Dialogfeld

Ein Mega-Dialogfeld hat drei Elemente innerhalb des Formulars: <header>, <article>, und <footer> Diese dienen als semantische Container und Stilziele für die Präsentation des Dialogfelds. Der Header betitelt das Dialogfenster und bietet einen Abschluss Schaltfläche. Der Artikel bezieht sich auf Formulareingaben und -informationen. Die Fußzeile enthält <menu> von Aktionsschaltflächen.

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

Die erste Menüschaltfläche enthält autofocus und einen onclick-Inline-Event-Handler. Das Attribut autofocus erhält wenn das Dialogfeld geöffnet wird. Ich denke, es hat sich bewährt, die Schaltfläche „Abbrechen“, nicht die Schaltfläche „Bestätigen“. So wird sichergestellt, dass die Bestätigung bewusst und nicht versehentlich.

Mini-Dialogfeld

Das Mini-Dialogfeld ist dem Mega-Dialogfeld sehr ähnlich, es fehlt nur ein <header>-Element. Dadurch kann es kleiner und inline angezeigt werden.

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

Das Dialogelement bietet eine solide Grundlage für ein vollständiges Darstellungsbereichselement, das Daten und Nutzerinteraktionen sammeln. Diese Grundlagen können interessante und effektive Interaktionen auf Ihrer Website oder in Ihrer App.

Bedienungshilfen

Das Dialogelement verfügt über eine sehr gute integrierte Zugänglichkeit. Anstatt diese hinzuzufügen, wie ich es normalerweise tue, viele sind bereits verfügbar.

Fokus wird wiederhergestellt

So wie Sie es im Schritt Nebennavigation erstellen Komponente enthält, ist es wichtig, Beim Öffnen und Schließen liegt der Fokus auf dem jeweils relevanten Öffnen und Schließen. Schaltflächen. Wenn diese seitliche Navigationsleiste geöffnet wird, liegt der Fokus auf der Schließen-Schaltfläche. Wenn der Parameter wenn die Schließen-Schaltfläche gedrückt wird, wird wieder der Fokus auf die Schaltfläche gesetzt, über die sie geöffnet wurde.

Mit dem Dialogelement ist dies in das Standardverhalten integriert:

Wenn ihr den Dialog animieren möchtet, geht verloren. Im JavaScript-Bereich stelle ich ihn wieder her. Funktionalität.

Überfangfokus

Das Dialogelement verwaltet inert für Sie. Vor dem inert wurde JavaScript verwendet, um den Fokus zu überwachen sodass ein Element zurückbleibt, an dem es es abfängt und zurücksetzt.

Unterstützte Browser

  • Chrome: 102. <ph type="x-smartling-placeholder">
  • Edge: 102. <ph type="x-smartling-placeholder">
  • Firefox: 112 <ph type="x-smartling-placeholder">
  • Safari: 15.5 <ph type="x-smartling-placeholder">

Quelle

Nach dem inert können Teile des Dokuments „eingefroren“ sein so sehr, dass sie nicht mehr fokussieren oder mit der Maus interaktiv sind. Anstelle von Fallen, Fokus wird auf den einzigen interaktiven Teil des Dokuments gelenkt.

Element öffnen und automatisch fokussieren

Standardmäßig weist das Dialogelement dem ersten fokussierbaren Element den Fokus zu im Dialogfeld-Markup ein. Wenn dies nicht das beste Element ist, das Nutzende standardmäßig verwenden können, Verwenden Sie das Attribut autofocus. Wie bereits erwähnt, empfiehlt es sich, , um dies auf die Schaltfläche „Abbrechen“ und nicht auf die Schaltfläche „Bestätigen“ zu setzen. Dadurch wird sichergestellt, dass die Bestätigung absichtlich und nicht versehentlich erfolgt ist.

Wird mit der Escape-Taste geschlossen

Es ist wichtig, dieses potenziell störende Element einfach zu schließen. Glücklicherweise verarbeitet das Dialogelement die Escape-Taste für Sie, sodass Sie vor der Orchestrierungslast.

Stile

Es gibt eine einfache Möglichkeit, das Dialogelement zu gestalten, und einen harten Pfad. Die einfache wird erreicht, indem die Anzeigeeigenschaft des Dialogfelds nicht geändert wird und mit seinen Einschränkungen. benutzerdefinierte Animationen für Öffnen und Schließen des Dialogfelds, wodurch das Attribut display und mehr übernommen werden.

Stile mit offenen Requisiten

Um adaptive Farben und Einheitlichkeit des Designs insgesamt zu beschleunigen, habe ich CSS-Variablenbibliothek Open Props ein. In zusätzlich zu den bereitgestellten Variablen, importiere ich auch eine normalize und einige Schaltflächen, mit denen Sie Props öffnen. als optionale Importe zur Verfügung stellt. Diese Importe helfen mir dabei, Dialog und Demo, ohne dass viele Stile erforderlich sind, um es zu unterstützen und gut.

<dialog>-Element gestalten

Inhaber der Property „display“

Durch das Standardverhalten zum Ein- und Ausblenden eines Dialogfeldelements wird die Anzeige umgeschaltet. Property von block in none geändert. Das bedeutet leider, dass sie nicht animiert werden kann. nach innen und außen, nur nach innen. Ich möchte eine Animation ein- und ausblenden. Der erste Schritt eigene festlegen display:

dialog {
  display: grid;
}

Durch Ändern des Wertes der display-Eigenschaft und damit der Inhaberin, wie in den über dem CSS-Snippet müssen einige Stile verwaltet werden, User Experience zu gewährleisten. Der Standardstatus eines Dialogfelds geschlossen. Sie können diesen Status visuell darstellen und verhindern, dass das Dialogfeld Interaktionen mit den folgenden Stilen erhalten:

dialog:not([open]) {
  pointer-events: none;
  opacity: 0;
}

Jetzt ist das Dialogfeld unsichtbar und es kann nicht mit ihm interagiert werden, solange es nicht geöffnet ist. Später Ich werde JavaScript hinzufügen, um das Attribut inert im Dialogfeld zu verwalten dass Nutzer, die eine Tastatur oder Screenreader verwenden, das ausgeblendete Dialogfeld nicht erreichen können.

Dem Dialogfeld ein adaptives Farbdesign geben

Mega-Dialogfeld mit dem hellen und dunklen Design, das die Farben der Oberfläche zeigt.

Während color-scheme Ihr Dokument in einem vom Browser bereitgestellten adaptives Farbschema an die Einstellungen des hellen und dunklen Systems angepasst wird. das Dialogelement. Open Props bietet einige Oberflächen Farben, die sich automatisch an die hellen und dunklen Systemeinstellungen, ähnlich wie bei der Verwendung von color-scheme. Diese sind ideal, um Ebenen in einem Design zu erstellen, und ich liebe es, Farben zu verwenden, unterstreichen diese Darstellung von Ebenenoberflächen. Die Hintergrundfarbe ist var(--surface-1); Um auf diesen Layer zu setzen, verwenden Sie 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);
  }
}

Für untergeordnete Elemente, z. B. die Kopfzeile, werden später weitere adaptive Farben hinzugefügt. und in der Fußzeile. Ich betrachte sie als zusätzlichen Dialogelement, aber sehr wichtig für ein überzeugendes und schön gestaltetes Dialogdesign.

Größe des responsiven Dialogfelds anpassen

Die Größe des Dialogfelds wird standardmäßig an seinen Inhalt delegiert, was in der Regel super. Mein Ziel ist es, die Reichweite max-inline-size auf eine lesbare Größe (--size-content-3 = 60ch) oder 90% der Breite des Darstellungsbereichs. Dieses sorgt dafür, dass das Dialogfeld auf einem Mobilgerät nicht randlos erscheint breit auf einem Desktop-Bildschirm, der schwer lesbar ist. Dann füge ich eine max-block-size damit das Dialogfeld die Höhe der Seite nicht überschreitet. Das bedeutet auch, dass wir muss angegeben werden, wo sich der scrollbare Bereich des Dialogfelds befindet. Dialogelement.

dialog {
  
  max-inline-size: min(90vw, var(--size-content-3));
  max-block-size: min(80vh, 100%);
  max-block-size: min(80dvb, 100%);
  overflow: hidden;
}

Siehst du, dass ich max-block-size zweimal habe? Im ersten Beispiel wird 80vh verwendet, ein physischer Darstellungsbereich-Einheit. Ich möchte, dass der Dialog innerhalb eines relativen Ablaufs bleibt, für internationale Nutzer. Daher verwende ich die logische, neuere und nur teilweise unterstützte dvb-Einheit in der zweiten Deklaration, wenn sie stabiler wird.

Positionierung des Mega-Dialogfelds

Um die Positionierung eines Dialogelements zu erleichtern, empfiehlt es sich, seine beiden Teilen: den Vollbildhintergrund und den Dialogcontainer. Der Hintergrund muss decken Sie alles ab und fügen einen Schattierungseffekt hinzu, um zu verdeutlichen, dass dieses Dialogfeld und der Inhalt dahinter ist unzugänglich. Der Dialogcontainer kann über diesem Hintergrund zentrieren und die Form annehmen, die der Inhalt erfordert.

Mit den folgenden Stilen wird das Dialogelement am Fenster fixiert, sodass es jeweils und margin: auto verwendet, um den Inhalt zu zentrieren:

dialog {
  
  margin: auto;
  padding: 0;
  position: fixed;
  inset: 0;
  z-index: var(--layer-important);
}
Stile des Mega-Dialogfelds für Mobilgeräte

Bei kleinen Darstellungsbereichen gestalte ich dieses Vollbild-Modaldialogfeld etwas anders. Ich den unteren Rand auf 0 festlegen, wodurch sich der Inhalt des Dialogfelds am Ende des Dialogfelds befindet. Darstellungsbereich. Mit ein paar Stilanpassungen kann ich das Dialogfeld Aktionsblatt näher an den Daumen der Nutzenden:

@media (max-width: 768px) {
  dialog[modal-mode="mega"] {
    margin-block-end: 0;
    border-end-end-radius: 0;
    border-end-start-radius: 0;
  }
}

Screenshot des überlagernden Randabstands der Entwicklertools 
  im Mega-Dialogfeld für Computer und Mobilgeräte.

Mini-Dialogpositionierung

In einem größeren Darstellungsbereich, z. B. auf einem Desktop-Computer, platziere ich die Mini-Dialogfelder über das Element, das sie aufgerufen hat. Dazu benötige ich JavaScript. Sie finden die Technik, die ich verwende hier, aber ich glaube, dass dies den Rahmen dieses Artikels sprengen würde. Ohne JavaScript Mini-Dialogfeld wird genau wie ein Mega-Dialog in der Mitte des Bildschirms angezeigt.

Mach es knackig

Verleihen Sie dem Dialog auch etwas Flair, damit er wie eine weiche Oberfläche aussieht, die weit weg sitzt. oberhalb der Seite platzieren. Diese wird durch Abrunden der Ecken des Dialogfelds erreicht. Die Tiefe wird mit einem der sorgfältig gefertigten Schatten von Open Props erreicht. Ausstattung:

dialog {
  
  border-radius: var(--radius-3);
  box-shadow: var(--shadow-6);
}

Hintergrund-Pseudoelement anpassen

Ich entschied mich, ganz leicht im Hintergrund zu arbeiten, nur einen Weichzeichnereffekt backdrop-filter zum Mega-Dialogfeld hinzu:

Unterstützte Browser

  • Chrome: 76 <ph type="x-smartling-placeholder">
  • Edge: 79. <ph type="x-smartling-placeholder">
  • Firefox: 103 <ph type="x-smartling-placeholder">
  • Safari: 18. <ph type="x-smartling-placeholder">

Quelle

dialog[modal-mode="mega"]::backdrop {
  backdrop-filter: blur(25px);
}

Ich habe mich auch für eine Umstellung auf backdrop-filter entschieden, da ich hoffe, dass Browser wird ein Wechsel des Hintergrundelements möglich sein:

dialog::backdrop {
  transition: backdrop-filter .5s ease;
}

Screenshot des Mega-Dialogfelds mit weichgezeichnetem Hintergrund von bunten Avataren.

Stile für Extras

Ich nenne diesen Bereich "Extras". Es hat mehr mit meinem Dialogelement zu tun als das Dialogelement im Allgemeinen.

Begrenzungen scrollen

Wenn das Dialogfeld angezeigt wird, können Nutzende die Seite dahinter scrollen, Das möchte ich nicht:

Normalerweise overscroll-behavior wäre meine übliche Lösung, aber entsprechend dem Spezifikation hat das keine Auswirkungen auf das Dialogfeld, da es sich nicht um einen Scroll-Port handelt, sondern um damit nichts verhindert werden kann. Ich könnte mithilfe von JavaScript nach die neuen Ereignisse aus diesem Leitfaden, wie z. B. „geschlossen“, und "Geöffnet" und wechseln Sie overflow: hidden für das Dokument oder ich könnte warten, bis :has() stabil ist in alle Browser:

Unterstützte Browser

  • Chrome: 105 <ph type="x-smartling-placeholder">
  • Edge: 105. <ph type="x-smartling-placeholder">
  • Firefox: 121. <ph type="x-smartling-placeholder">
  • Safari: 15.4 <ph type="x-smartling-placeholder">

Quelle

html:has(dialog[open][modal-mode="mega"]) {
  overflow: hidden;
}

Wenn jetzt ein Mega-Dialogfeld geöffnet ist, enthält das HTML-Dokument overflow: hidden.

Das <form>-Layout

Sie ist nicht nur ein sehr wichtiges Element, um Interaktionen zu erfassen, von den Nutzenden erhalten, verwende ich sie hier, um die Kopf-, Fußzeile und Artikel-Elementen enthalten. Mit diesem Layout möchte ich das untergeordnete Artikelelement als scrollen können. Das erreiche ich mit grid-template-rows Das Artikelelement erhält den Wert 1fr und das Formular selbst hat das gleiche Maximum "height" als Dialogelement festlegen. Das Festlegen dieser festen Höhe und der festen Zeilengröße kann das Artikelelement fixiert werden und scrollt, wenn es überläuft:

dialog > form {
  display: grid;
  grid-template-rows: auto 1fr auto;
  align-items: start;
  max-block-size: 80vh;
  max-block-size: 80dvb;
}

Screenshot von Entwicklertools, die die Rasterlayoutinformationen über die Zeilen einblenden.

Stil des Dialogfelds <header> festlegen

Mit diesem Element geben Sie einen Titel für den Inhalt und das Angebot des Dialogs an. einfach zu findende Schließen-Schaltfläche. Außerdem erhält er eine Oberflächenfarbe, hinter dem Inhalt des Dialogartikels. Diese Anforderungen führen zu einer Flexbox Behälter, vertikal ausgerichtete Elemente mit Abstand zu ihren Kanten sowie einige Abstand und Lücken, damit die Titel- und Schließen-Schaltflächen mehr Platz haben:

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

Screenshot der Chrome-Entwicklertools mit Flexbox-Layoutinformationen in der Kopfzeile des Dialogfelds.

Stil für die Schließen-Schaltfläche in der Kopfzeile festlegen

Da in der Demo die Schaltflächen "Props öffnen" verwendet werden, ist die Schaltfläche "Schließen" angepasst. in eine runde Symbol-Schaltfläche in der Mitte:

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

Screenshot der Chrome-Entwicklertools mit eingeblendeten Größen- und Padding-Informationen für die Schließen-Schaltfläche der Kopfzeile.

Stil des Dialogfelds <article> festlegen

Das Artikelelement hat in diesem Dialogfeld eine besondere Rolle: Es ist ein Leerzeichen, bei einem langen oder langen Dialogfeld gescrollt werden kann.

Um dies zu erreichen, hat das übergeordnete Formularelement einige Höchstwerte für die Einschränkungen für dieses Artikelelement vorgeben, die erreicht werden können, wenn es zu hoch. Legen Sie overflow-y: auto so fest, dass Bildlaufleisten nur bei Bedarf angezeigt werden. mit overscroll-behavior: contain Scrollen darin enthalten, und der Rest sind benutzerdefinierte Präsentationsstile:

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

Die Rolle der Fußzeile besteht darin, Menüs mit Aktionsschaltflächen zu enthalten. Flexbox wird verwendet, um Richten Sie den Inhalt am Ende der Inline-Achse der Fußzeile aus. um den Tasten etwas Platz zu geben.

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

Screenshot der Chrome-Entwicklertools mit Flexbox-Layoutinformationen im Fußzeilenelement.

Die menu -Element wird die Aktionsschaltflächen für das Dialogfeld enthalten. Dabei wird ein Wrapping Flexbox-Layout mit gap, um Platz zwischen den Schaltflächen zu schaffen. Menüelemente haben einen Innenrand, z. B. <ul>. Ich entferne auch diesen Stil, da ich ihn nicht benötige.

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

Screenshot der Chrome-Entwicklertools mit Flexbox-Informationen über den Menüelementen in der Fußzeile.

Animation

Dialogfeldelemente sind häufig animiert, da sie das Fenster betreten und verlassen. Durch unterstützende Bewegung an den Ein- und Ausgang von Dialogfeldern können Nutzende sich im Fluss zu orientieren.

Normalerweise kann das Dialogelement nur inner- und nicht außen animiert werden. Das liegt daran, Der Browser schaltet die Eigenschaft display für das Element um. Die Anleitung „Rasteranzeige“ und nie auf „Keine“. Dadurch können Sie dass sie immer wieder animieren können.

Open Props enthält viele Keyframes. Animationen erstellt und so gestaltet, Orchestrierung einfach und gut lesbar ist. Hier sehen Sie die Ziele für Animationen Ansatz:

  1. Reduzierte Bewegung ist der Standardübergang, ein einfaches Ein- und Ausblenden der Deckkraft.
  2. Wenn die Bewegung in Ordnung ist, werden Folien- und Skalierungsanimationen hinzugefügt.
  3. Das responsive mobile Layout für das Mega-Dialogfeld wird so angepasst, dass es herauszoomt.

Sicherer und sinnvoller Standardwechsel

Obwohl offene Props Keyframes zum Ein- und Ausblenden bieten, ziehe ich diese standardmäßig aktiviert, wobei Keyframe-Animationen potenzielle Upgrades. Die Sichtbarkeit des Dialogfelds haben wir bereits mit Deckkraft, wobei 1 oder 0 je nach [open]-Attribut orchestriert wird. Bis zwischen 0 und 100 Prozent liegt, teilen Sie dem Browser mit, wie lange Gewünschtes Easing:

dialog {
  transition: opacity .5s var(--ease-3);
}

Dem Übergang Bewegung hinzufügen

Wenn der Nutzer kein Problem mit Bewegungen hat, sollten sich sowohl das Mega- als auch das Mini-Dialogfeld verschieben. als Eingang und hochskaliert als Ausstieg. Dies erreichen Sie mit dem prefers-reduced-motion-Medienabfrage und einige offene Props:

@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;
  }
}
<ph type="x-smartling-placeholder">
</ph>

Exit-Animation für Mobilgeräte anpassen

Im Abschnitt „Stile“ wurde der Stil des Mega-Dialogs bereits für Mobilgeräte angepasst. wie ein Blatt Papier, als ob ein Blatt Papier vom unteren Displayrand nach oben und ist immer noch unten angebracht. Die Waage die Exit-Animation nicht gut in das neue Design passt, und wir können es mit Medienabfragen und offene Props:

@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

Mit JavaScript müssen Sie einige Dinge hinzufügen:

// 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
}

Diese Ergänzungen ergeben sich aus dem Wunsch, Licht auszuschalten (durch Klicken auf das Dialogfeld Bilderrahmen), Animationen und einige zusätzliche Ereignisse, um das der Formulardaten.

Licht beim Hinzufügen ausschalten

Diese Aufgabe ist unkompliziert und eine tolle Ergänzung für ein Dialogelement, animiert sein. Die Interaktion erfolgt durch Beobachten der Klicks im Dialogfeld und die Nutzung von Ereignissen sprudelnd um zu beurteilen, auf was geklickt wurde. close() wenn es das oberste Element ist:

export default async function (dialog) {
  dialog.addEventListener('click', lightDismiss)
}

const lightDismiss = ({target:dialog}) => {
  if (dialog.nodeName === 'DIALOG')
    dialog.close('dismiss')
}

Beachten Sie dialog.close('dismiss'). Das Ereignis wird aufgerufen und ein String wird bereitgestellt. Dieser String kann von anderem JavaScript abgerufen werden, um zu ermitteln, wie der Dialogfeld wurde geschlossen. Sie werden feststellen, dass ich bei jedem Aufruf die Funktion über verschiedene Schaltflächen, um für meine Anwendung die Interaktion der Nutzenden.

Schließende und geschlossene Ereignisse hinzufügen

Das Dialogelement hat ein Schließen-Ereignis: Es wird sofort ausgelöst, wenn der Dialog close() aufgerufen. Da wir dieses Element animieren, Es ist toll, dass vor und nach der Animation Termine vorhanden sind. oder das Dialogformular zurücksetzen. Hier verwalte ich das Hinzufügen der Attribut inert im geschlossenen Dialogfeld und verwende es in der Demo, um es zu ändern in der Avatarliste, wenn der Nutzer ein neues Bild eingereicht hat.

Erstellen Sie dazu zwei neue Ereignisse mit den Namen closing und closed. Dann Warten Sie auf das integrierte Schließen-Ereignis im Dialogfeld. Legen Sie dann das Dialogfeld inert und senden das closing-Ereignis. Als Nächstes müssen Sie warten, der Animationen und Übergänge, um die Ausführung im Dialogfeld abzuschließen, closed-Ereignis.

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

Die Funktion animationsComplete, die auch im Modul Toast erstellen Komponente, gibt ein Versprechen basierend auf dem die Fertigstellung der Animation und der Übergang. Aus diesem Grund dialogClose ist ein asynchrones Funktion; kann es dann await das Versprechen zurückgegeben und selbstbewusst auf das Closed-Ereignis übergegangen werden.

Öffnende und geöffnete Ereignisse hinzufügen

Diese Ereignisse lassen sich nicht so einfach hinzufügen, da das integrierte Dialogelement die ein offenes Ereignis wie bei einem Schließen bereitstellen. Ich nutze MutationObserver um Einblicke in die sich ändernden Attribute des Dialogfelds zu erhalten. In diesem Beobachter Ich achte auf Änderungen am offenen Attribut und verwalte die benutzerdefinierten Ereignisse entsprechend anpassen.

Ähnlich wie bei den Abschluss- und Geschlossener-Ereignissen erstellen Sie zwei neue Ereignisse, namens opening und opened. Wo zuvor auf das Schließen des Dialogfelds gewartet wurde -Ereignis, verwenden Sie dieses Mal einen erstellten Mutationsbeobachter, um die Attribute.


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

Die Mutationsobserver-Callback-Funktion wird aufgerufen, wenn das Dialogfeld geändert werden und die Liste der Änderungen als Array bereitstellen. Überarbeiten Das Attribut ändert sich und attributeName muss offen sein. Als Nächstes aktivieren Sie ob das Element das Attribut hat oder nicht: Dies gibt an, ob das Dialogfeld ist offen geworden. Wenn es geöffnet wurde, entfernen Sie das Attribut inert und setzen Sie den Fokus entweder einem Element hinzu, das autofocus oder das erste button-Element im Dialogfeld. Ähnlich wie bei der abschließenden und Closed, sofort das Eröffnungsereignis auslösen, auf die Animationen warten, um den Vorgang abzuschließen, und senden Sie dann das Ereignis "open".

Entfernte Termine hinzufügen

In Single-Page-Anwendungen werden Dialogfelder häufig basierend auf Routen hinzugefügt und entfernt. oder andere Anwendungsanforderungen und -status. Es kann nützlich sein, Ereignisse oder wenn ein Dialogfeld entfernt wird.

Sie können dies mit einem anderen Mutationsbeobachter erreichen. Dieses Mal statt der Beobachtung von Attributen eines dialogelements und achten Sie darauf, dass Dialogelemente entfernt werden.


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

Der Mutationsobserver-Callback wird aufgerufen, wenn untergeordnete Elemente hinzugefügt oder entfernt werden aus dem Textkörper des Dokuments. Die beobachteten spezifischen Mutationen removedNodes mit den nodeName von ein Dialogfeld. Wenn ein Dialogfeld entfernt wurde, werden die Ereignisse vom Typ „Klick“ und „Schließen“ in freigeben, um Arbeitsspeicher freizugeben. Das benutzerdefinierte Ereignis "Entfernt" wird gesendet.

Ladeattribut wird entfernt

Um zu verhindern, dass die Dialoganimation ihre Exit-Animation wiedergibt, wenn sie zu der Seite oder beim Seitenaufbau wird dem Dialogfeld ein Ladeattribut hinzugefügt. Die das folgende Skript wartet, bis die Dialoganimationen fertig sind, und entfernt das Attribut. Der Dialog kann jetzt ein- und ausgeblendet werden. eine ansonsten ablenkende Animation effektiv verborgen.

export default async function (dialog) {
  
  await animationsComplete(dialog)
  dialog.removeAttribute('loading')
}

Weitere Informationen zum Problem, wie Keyframe-Animationen beim Seitenaufbau verhindert werden hier.

Alles zusammen

Hier sehen Sie den vollständigen Text von dialog.js, nachdem wir die einzelnen Abschnitte erklärt haben. einzeln:

// 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')
}

Modul dialog.js verwenden

Die aus dem Modul exportierte Funktion erwartet, aufgerufen zu werden und ein Dialogfeld zu übergeben. -Element das diese neuen Ereignisse und Funktionen hinzufügen möchte:

import GuiDialog from './dialog.js'

const MegaDialog = document.querySelector('#MegaDialog')
const MiniDialog = document.querySelector('#MiniDialog')

GuiDialog(MegaDialog)
GuiDialog(MiniDialog)

Jetzt werden die beiden Dialogfelder mit leichtem Schließen, Animation Fehlerbehebungen und weitere Ereignisse, mit denen Sie arbeiten können.

Neue benutzerdefinierte Ereignisse überwachen

Jedes aktualisierte Dialogelement kann jetzt auf fünf neue Ereignisse warten:

MegaDialog.addEventListener('closing', dialogClosing)
MegaDialog.addEventListener('closed', dialogClosed)

MegaDialog.addEventListener('opening', dialogOpening)
MegaDialog.addEventListener('opened', dialogOpened)

MegaDialog.addEventListener('removed', dialogRemoved)

Hier zwei Beispiele für die Verarbeitung dieser Ereignisse:

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

In der Demo, die ich mit dem Dialogelement erstellt habe, um der Liste ein neues Avatar-Element hinzuzufügen. Das Timing ist in dass das Dialogfeld seine Exit-Animation abgeschlossen hat und dass einige Skripts im neuen Avatar. Dank der neuen Ereignisse lässt sich die Nutzererfahrung flüssiger sein.

Beachten Sie dialog.returnValue: Dies enthält den String zum Schließen, der übergeben wird, wenn die Dialog close()-Ereignis wird aufgerufen. Es ist beim dialogClosed-Ereignis wichtig, ob das Dialogfeld geschlossen, abgebrochen oder bestätigt wurde. Wenn das der Fall ist, erfasst dann die Formularwerte und setzt das Formular zurück. Das Zurücksetzen ist nützlich, dass das Dialogfeld leer ist, wenn es erneut angezeigt wird.

Fazit

Jetzt, wo du weißt, wie ich es gemacht habe, wie würdest du... ‽ 🙂

Lassen Sie uns unsere Herangehensweisen diversifizieren und alle Möglichkeiten kennenlernen, wie wir das Web entwickeln können.

Erstelle eine Demo, twittere mir Links und ich füge sie hinzu im Abschnitt „Community-Remixe“ weiter unten.

Community-Remixe

Ressourcen