Tworzenie komponentu okna

Podstawowe informacje o tworzeniu mini i megamodalnych mini i megamodalnych reklam adaptacyjnych kolorystycznych za pomocą elementu <dialog>.

W tym poście chcę opowiedzieć o tym, jak stworzyć elastyczne, łatwo dostępne mini i megamodalne panele z elementem <dialog>. Wypróbuj wersję demonstracyjną i zobacz !

. Prezentacja wielkich i minimalistycznych dialogów w jasnych i ciemnych motywach.

Jeśli wolisz film, oto wersja tego posta w YouTube:

Omówienie

<dialog> doskonale nadaje się do przekazywania informacji kontekstowych lub dotyczących działań w treści strony. Zastanów się, kiedy użytkownicy mogą skorzystać na tym samym działaniu, a nie na wielu stronach działania: na przykład dlatego, że formularz jest mały lub jedynie działanie wymagane przez potwierdź lub anuluj.

Element <dialog> jest ostatnio stabilny w różnych przeglądarkach:

Obsługa przeglądarek

  • Chrome: 37.
  • Krawędź: 79.
  • Firefox: 98.
  • Safari: 15.4

Źródło

Zauważyłem, że w elemencie brakuje kilku elementów, więc w tym GUI Wyzwanie Dodaję doświadczenia programisty oczekiwanych przez Ciebie elementów: dodatkowych zdarzeń, lekkiego zamknięcia, niestandardowych animacji i dużych typów.

Markup

Podstawowe elementy elementu <dialog> są skromne. Element będzie automatycznie ukryty i ma wbudowane style, które nakładają się na treść.

<dialog>
  …
</dialog>

Możemy poprawić tę wartość bazową.

Tradycyjnie elementy dialogowe mają wiele wspólnego z elementem modalnym i często ich nazwy są wymienne. Tutaj mogę wykorzystać element dialogowy zarówno niewielkie wyskakujące okienka (mini), jak i duże okna dialogowe. Nazwałem(-am) nazwę w wersji mega i mini, a oba okna nieznacznie się przystosowały do różnych zastosowań. Dodałem atrybut modal-mode, aby umożliwić określenie typu:

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

Zrzut ekranu przedstawiający okna miniatury i duże okna w motywie jasnym i ciemnym.

Nie zawsze, ale zwykle do zbierania określonych danych informacje o interakcji. Formularze w elementach dialogowych są tworzone z myślą o użytkownikach razem. Dobrze jest użyć elementu formularza, który otacza treść okna, JavaScript ma dostęp do danych wpisanych przez użytkownika. Ponadto przyciski wewnątrz formularz korzystający z method="dialog" może zamknąć okno bez JavaScriptu i przekazać i skalowalnych danych.

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

Mega okno

Duże okno ma 3 elementy wewnątrz formularza: <header> <article>, oraz <footer>. Pełnią one funkcję kontenerów semantycznych, a także docelowych stylów dla elementów prezentację danego okna. Nagłówek zawiera tytuł modalu i zawiera podsumowanie Przycisk Ten artykuł dotyczy wprowadzania danych i informacji w formularzu. Stopka zawiera <menu> z przyciski poleceń.

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

Pierwszy przycisk menu zawiera autofocus i wbudowany moduł obsługi zdarzeń onclick. Atrybut autofocus otrzyma po otwarciu okna dialogowego. użyj przycisku anulowania, a nie przycisku potwierdzenia. Dzięki temu potwierdzenie celowe, a nie przypadkowe.

Miniokno

Małe okno jest bardzo podobne do megaokna, ale brakuje w nim <header> element. Dzięki temu będą mniejsze i lepiej będą umieszczone w tekście.

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

Okno to solidny fundament dla pełnego widocznego obszaru, aby zbierać dane i informacje o interakcjach użytkowników. Te podstawowe narzędzia mogą sprawić, do ciekawych i skutecznych interakcji w witrynie lub aplikacji.

Ułatwienia dostępu

Okno ma wbudowane bardzo dobre ułatwienia dostępu. Zamiast dodawać te elementy jak zwykle, ale wiele jest już dostępnych.

Przywracam fokus

Podobnie jak w ćwiczeniu Tworzenie bocznej nawigacji , ważne jest, Odpowiednie otwarcie i zamykanie czegoś skupia się na tym, co ważne, otwarte i zamknięte przyciskami. Po otwarciu panelu bocznego zaznaczenie zostanie umieszczone na przycisku zamykania. Gdy przycisk zamknięcia, zaznaczenie zostanie przywrócone na przycisk, który go otworzył.

W przypadku elementu okna wbudowane jest domyślne działanie:

Jeśli chcesz animować okno dialogowe i oddalać się od niego, ta funkcja przepada. Przywrócę go w sekcji JavaScript funkcji.

Traktowanie fokusu

Okno zarządza inert za Ciebie. Przed inert do sprawdzania ostrości używano JavaScriptu opuszczając element, w którym przechwytuje on i wsuwa go z powrotem.

Obsługa przeglądarek

  • Chrome: 102.
  • Edge: 102.
  • Firefox: 112.
  • Safari: 15.5

Źródło

Po inert dowolne części dokumentu mogą zostać „zablokowane” nie są aż tak bardzo, że nie są już skoncentrowane na celach ani nie są interaktywne po wskazaniu myszą. Zamiast łapać zaznaczenie jest wyświetlane w jedynej interaktywnej części dokumentu.

Otwieranie i automatyczne ustawianie ostrości elementu

Domyślnie element okna aktywuje pierwszy element, który można zaznaczyć. w znacznikach dialogu. Jeśli nie jest to najlepszy element, który użytkownik powinien wybrać, użyj atrybutu autofocus. Jak napisałem wcześniej, najlepszą metodą jest to, żeby umieścić go na przycisku anulowania, a nie na przycisku potwierdzenia. Dzięki temu jest celowe i nie przypadkowe.

Zamykanie klawisza Escape

Ważne jest, by ułatwić zamknięcie tego potencjalnie zakłócającego elementu. Na szczęście element okna zajmie się klawiszem Escape, z pracy administracyjnego.

Style

Istnieje prosty sposób na określenie stylu elementu dialogowego i sztywnej ścieżki. Łatwe trzeba wprowadzić zmiany, nie zmieniając właściwości wyświetlania okna, z jego ograniczeniami. Przygotowuję animacje niestandardowe dla: otwieranie i zamykanie okna, przejmując między innymi właściwość display.

Stylizacja z otwartymi rekwizytami

Bez wstydu, aby przyspieszyć dostosowywanie kolorów i ogólną spójność projektu, co udało mi się znaleźć w bibliotece zmiennych CSS Open Props. W oprócz bezpłatnych zmiennych, importuję również znormalizować plik i niektóre z nich. przyciski – oba te formaty są przyciskami Open Props. udostępnia jako opcjonalne importy. Ten import pomaga mi skupić się na dostosowywaniu okna i demonstracja, nie wymagają przy tym wielu stylów dobrze.

Określanie stylu elementu <dialog>

Posiadanie usługi displayowej

Domyślny sposób pokazywania i ukrywania elementu okna przełącza widok. od block do none. Niestety, ten film nie może być animowany. i wylotu, tylko do wewnątrz. Chciałbym włączyć animację na początku i na końcu, ustaw własny właściwość display:

dialog {
  display: grid;
}

Zmieniając i tym samym będąc właścicielem wartości usługi displayowej, jak widać w tabeli powyżej fragmentu kodu CSS, znaczna liczba stylów wymaga zarządzania, aby i dbać o wygodę użytkowników. Po pierwsze, domyślny stan okna to zamknięto. Możesz przedstawić ten stan wizualnie i uniemożliwić wyświetlanie otrzymywanie interakcji z następującymi stylami:

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

Teraz okno jest niewidoczne i nie można z nim korzystać, gdy nie jest otwarte. Później Dodam JavaScript do zarządzania atrybutem inert w oknie, dzięki czemu użytkownicy korzystający z klawiatury i czytników ekranu nie mogą otworzyć ukrytego okna.

Nadanie dialogowi adaptacyjnego motywu kolorystycznego

Mega okno z jasnym i ciemnym motywem prezentujące kolory powierzchni.

Mimo że color-scheme włącza dokument jako udostępniany przez przeglądarkę z adaptacyjnym motywem kolorystycznym do preferencji systemu jasnego i ciemnego, chciałem dostosować niż ten element okna. Otwarte rekwizyty zapewniają kilka powierzchni kolorów, które automatycznie dostosowują się do ustawienia systemu jasnego i ciemnego, podobnie jak w przypadku color-scheme. Te świetnie nadają się do tworzenia warstw. Uwielbiam używać kolorów, są zgodne z wyglądem powierzchni warstw. Kolor tła to var(--surface-1); aby umieścić ją na tej warstwie, użyj narzędzia 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);
  }
}

Później dodamy więcej kolorów adaptacyjnych do elementów podrzędnych, takich jak nagłówek. i stopkę. Traktuję je jako dodatkowe elementy dialogów, ale niezwykle ważne zaprojektowanie atrakcyjnego i dobrze zaprojektowanego dialogu.

Elastyczny rozmiar okien

Domyślnie okno dialogowe przekazuje rozmiar do zawartości, co jest świetnie. Moim celem jest ograniczenie max-inline-size do czytelnych rozmiarów (--size-content-3 = 60ch) lub do 90% szerokości widocznego obszaru. Ten Zapewnia, że okno dialogowe nie będzie miało na ekranie komputera, który trudno jest odczytać. Następnie dodaję max-block-size aby okno nie przekraczało wysokości strony. Oznacza to również, że będziemy Określ, gdzie znajduje się obszar, który można przewijać (jeśli jest wysoki) .

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

Czy widzisz, że aplikacja max-block-size jest wyświetlana 2 razy? W pierwszym użyto 80vh, jednostki widocznego obszaru. Naprawdę chcę, aby dialog był względny. dla użytkowników międzynarodowych, więc używam logicznego, nowszego i tylko częściowo obsługuje jednostkę dvb w drugiej deklaracji, gdy stanie się bardziej stabilna.

Pozycjonowanie megaokna

Aby ułatwić określenie pozycji elementu okna, warto podzielić jego dwa aspekty: tło pełnoekranowe i kontener okna. Tło musi: wszystkie elementy, dodając efekt cieniowania, który pomaga wzmocnić efekt cieniowania. a treść jest niedostępna. Kontener dialogowy może wyśrodkowuje się na tym tle i nabiera dowolne kształty, jakich wymaga jego zawartość.

Te style naprawiają element okna, rozciągając go do każdego i użyje elementu margin: auto do wyśrodkowania treści:

dialog {
  
  margin: auto;
  padding: 0;
  position: fixed;
  inset: 0;
  z-index: var(--layer-important);
}
Style okien Mega na komórki

W małych widocznych obszarach trochę inaczej określam styl tego megamodalnego całej strony. Ja ustaw dolny margines na 0, co umieszcza zawartość okna na dole w widocznym obszarze. Dzięki kilku korektom stylu mogę zmienić okno arkusz działań, bliżej kciuków użytkownika:

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

Zrzut ekranu z narzędziami deweloperskimi z nakładaniem odstępów między marginesami 
  w megaoknie na komputerze i komórce, gdy jest otwarte.

Pozycjonowanie miniokna

Gdy za pomocą większego widocznego obszaru, np. na komputerze, ustawiam miniokna dialogowe element, który je wywołał. Potrzebuję do tego JavaScriptu. Znajdziesz tu wykorzystywana przeze mnie technika tutaj, ale moim zdaniem wykraczają one poza zakres tego artykułu. Bez JavaScriptu wyświetla się na środku ekranu, tak jak duże okno dialogowe.

Wyróżnij się

Na koniec dodajmy trochę blasku do okna, aby wyglądało jak miękka, daleka powierzchnia nad stroną. Zmniejsza to miękkość obrazu za pomocą zaokrąglania rogów okna. Głębia jest tworzona dzięki starannie dopracowanym cieniu rekwizytów w otwartej przestrzeni rekwizyty:

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

Dostosowywanie pseudoelementu tła

Postanowiłam pracować bardzo delikatnie z tłem, dodając tylko efekt rozmycia backdrop-filter to wielkie okno:

Obsługa przeglądarek

  • Chrome: 76.
  • Krawędź: 79.
  • Firefox: 103.
  • Safari: 18.

Źródło

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

Ustawiam też przejście na backdrop-filter, mam nadzieję, że przeglądarki umożliwia zmianę elementu tła w przyszłości:

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

Zrzut ekranu przedstawiający megaokna z rozmytym tłem z kolorowymi awatarami.

Dodatki do stylu

Określam tę sekcję jako „dodatki”. ponieważ ma to więcej wspólnego z elementem okna niż w przypadku okna dialogowego.

Ograniczenia dotyczące przewijania

Po wyświetleniu okna użytkownika użytkownik wciąż może przewinąć stronę za nim, które są niepotrzebne:

Zwykle overscroll-behavior to standardowe rozwiązanie, ale zgodnie z , Nie ma to wpływu na okno dialogowe, ponieważ nie jest to port do przewijania, tzn. czy przewijanie. Mogę użyć JavaScriptu, by monitorować nowe zdarzenia z tego przewodnika, np. „zamknięte”; i „otwarty” oraz przełącz overflow: hidden na dokumencie, lub poczekaj, aż :has() będzie stabilna w wszystkie przeglądarki:

Obsługa przeglądarek

  • Chrome: 105.
  • Edge: 105.
  • Firefox: 121.
  • Safari: 15.4

Źródło

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

Teraz po otwarciu megaokna dokument HTML zawiera overflow: hidden.

Układ <form>

Będąc bardzo istotnym elementem przy zbieraniu interakcji na podstawie informacji od użytkownika, używam ich tutaj do rozmieszczenia nagłówka, stopki elementów artykułu. Przy tym układzie zamierzam przedstawić artykuł podrzędny jako obszar do przewijania. Robię to dzięki grid-template-rows Elementowi artykułu przypisano atrybut 1fr, a formularz ma taką samą wartość maksymalną wysokość jako element okna. Ustawienie tej wysokości i rozmiaru wiersza firmy umożliwia ograniczenie elementu artykułu i przewijanie go, gdy wykracza poza kontener:

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

Zrzut ekranu przedstawiający narzędzia deweloperskie nakładające informacje o układzie siatki na wiersze.

Określanie stylu okna dialogowego <header>

Rola tego elementu to nadanie tytułu zawartości okna i oferty łatwo dostępny przycisk zamykania. Ma również określony kolor powierzchni, za treść artykułu. Te wymagania prowadzą do stworzenia Flexbox kontener, elementy wyrównane w pionie i odstępy od krawędzi, dopełnienie i luki, aby zwolnić trochę miejsca dla tytułu i przycisków zamykania:

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

Zrzut ekranu przedstawiający narzędzia deweloperskie w Chrome z nałożonymi informacjami o układzie flexbox w nagłówku okna.

Określanie stylu przycisku zamykania nagłówka

Ponieważ w wersji demonstracyjnej są używane przyciski Otwórz rekwizyty, przycisk zamykania jest dostosowany do w okrągły, środkowy przycisk:

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

Zrzut ekranu przedstawiający narzędzia deweloperskie w Chrome z nałożonymi informacjami o rozmiarze i dopełnieniu przycisku zamykania nagłówka.

Określanie stylu okna dialogowego <article>

Element artykułu ma w tym oknie specjalną rolę: przewijane w przypadku długiego lub długiego okna.

W tym celu nadrzędny element formularza określił maksymalne wartości dla , która określa ograniczenia, jakie dany element artykułu ma do osiągnięcia, jeśli otrzyma są za wysokie. Ustaw overflow-y: auto tak, aby paski przewijania były widoczne tylko wtedy, gdy są potrzebne. zawiera przewijanie za pomocą overscroll-behavior: contain, a pozostałe zostaną użyte niestandardowe style prezentacji:

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

Stopka ma zawierać menu z przyciskami polecenia. Flexbox jest używany do: wyrównaj treść do końca wbudowanej osi stopki, a następnie ustaw odstępy zostawmy przyciskom trochę wolnego miejsca.

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

Zrzut ekranu przedstawiający narzędzia deweloperskie w Chrome z nałożonymi informacjami o układzie flexbox na elemencie stopki.

menu zawiera przyciski poleceń, które można wykorzystać w oknie. Wykorzystuje zawijanie układ flexbox z elementem gap zapewniający odstęp między przyciskami. Elementy menu zawiera dopełnienie, takie jak <ul>. Usuwam też ten styl, ponieważ nie jest mi potrzebny.

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

Zrzut ekranu przedstawiający narzędzia deweloperskie w Chrome z nałożonymi informacjami dotyczącymi Flexbox na elementy menu stopki.

Animacja

Elementy okien są często animowane, ponieważ otwierają i zamykają okno. Zachęcanie widzów do ruchu w ramach ruchu przychodzącego i wyjścia pomaga użytkownikom nie orientują się w przepływie treści.

Zwykle element okna może być animowany tylko wewnątrz, ale nie na zewnątrz. Dzieje się tak, ponieważ przeglądarka przełącza właściwość display w elemencie. Wcześniej w przewodniku ustawia wyświetlanie na siatkę, ale nigdy nie ustawia go na brak. Dzięki temu możesz: pojawia się i znika.

Otwarte rekwizyty obejmują wiele klatek kluczowych animacji do użycia, co sprawia, a ich administracja jest łatwa i czytelna. Oto cele animacji i nałożone na nią warstwy podejście wybrane przeze mnie:

  1. Zmniejszony ruch to domyślne przejście, proste przejście od nieprzezroczystości, które pojawia się lub znika.
  2. Jeśli ruch jest prawidłowy, zostaną dodane animacje przesuwania i skalowania.
  3. Elastyczny układ megaokna na urządzenia mobilne jest dostosowany do wysuwania.

bezpieczne i wartościowe przeniesienie domyślne;

Choć otwarte rekwizyty są wyposażone w klatki kluczowe do zanikania i znikania, wolę ten warstwowego podejścia do przejść jako domyślnego, przy czym animacje klatek kluczowych potencjalnych aktualizacji. Wcześniej określiliśmy styl widoczności okna przezroczystość, administrowanie 1 lub 0 w zależności od atrybutu [open]. Do między 0% a 100%, poinformuj przeglądarkę, jak długo i jakiego rodzaju wygładzanie, które chcesz zmienić:

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

Dodawanie ruchu do przejścia

Jeśli użytkownikowi nie przeszkadza ruch, powinny się przesuwać zarówno duże okna, jak i miniatury jako wejście i zwiększać skalę jako wyjście. Możesz to osiągnąć za pomocą prefers-reduced-motion zapytanie o multimedia i kilka otwartych rekwizytów:

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

Dostosowanie animacji wyjściowej do wyświetlania na urządzeniach mobilnych

Wcześniej w sekcji dotyczącej stylów został przystosowany styl megaokna do wyświetlania na urządzeniach mobilnych. gdy urządzenia przypominają arkusz działań, jak kartka papieru od dołu do góry i nadal jest przymocowana u dołu. Skala na zewnątrz nie pasuje dobrze do nowego układu. Możemy ją dostosować w taki sposób, kilka zapytań o media i kilka otwartych rekwizytów:

@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

W JavaScripcie jest jeszcze kilka rzeczy, które można dodać:

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

Te dodatki mają związek z chęcią lekkiego zamknięcia okna (kliknięcie okna dialogowego w tle), animację i kilka dodatkowych zdarzeń, aby szybciej wyświetlać reklamy dane formularza.

Dodaję oświetlenie zamknięte

To proste zadanie i świetne uzupełnienie elementu dialogowego, nie były animowane. Interakcja jest rejestrowana przez obserwowanie kliknięć okna. i wykorzystanie zdarzeń bąbelki ocenia kliknięcia. Bierzemy przy tym pod uwagę tylko close(). jeśli jest to element znajdujący się najwyżej:

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

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

Powiadomienie: dialog.close('dismiss'). Zdarzenie jest wywoływane i podano ciąg znaków. Ten ciąg może być pobierany przez inny kod JavaScript, aby uzyskać informacje o tym, jak zostało zamknięte. Za każdym razem, gdy dzwonię, funkcji za pomocą różnych przycisków, aby zapewnić kontekst dotyczący aplikacji interakcji użytkownika.

Dodaję zdarzenia zamykające i zamknięte

Z elementem dialogu towarzyszy zdarzenie zamknięcia, które pojawia się natychmiast, gdy funkcja okna close() jest wywoływana. Trzeba animować ten element, dobrze jest mieć zdarzenia przed animacją i po niej, by zmienić lub zresetować formularz. Używam go do zarządzania dodawaniem inert w zamkniętym oknie i w wersji demonstracyjnej używam ich do modyfikowania listę awatarów, jeśli użytkownik przesłał nowe zdjęcie.

W tym celu utwórz 2 nowe zdarzenia o nazwach closing i closed. Potem nasłuchuje wbudowanego zdarzenia zamknięcia w oknie. W tym miejscu ustaw okno dialogowe na inert i wysyłaj zdarzenie closing. Następne zadanie to poczekać na animacji i przejść, by zakończyć ich działanie w oknie, a następnie wysłać closed zdarzenie.

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

funkcja animationsComplete, która jest też wykorzystywana podczas tworzenia toastu. komponent, zwraca obietnicę na podstawie do końca animacji i przejścia. Dlatego dialogClose jest asynchroniczny funkcji; może następnie await obietnica została zwrócona i z większą pewnością przejdziemy do etapu zamkniętego.

Dodawanie otwieranych i otwartych zdarzeń

Dodanie tych zdarzeń nie jest łatwe, ponieważ wbudowany element okna zdarzenia otwartego, tak jak w przypadku zamknięcia. Korzystam z MutationObserver aby uzyskać wgląd w zmiany atrybutów okna. W tym obserwatorze Będę śledzić zmiany w atrybucie otwieranie i zarządzać zdarzeniami niestandardowymi odpowiednio się zmienia.

Podobnie jak w przypadku wydarzeń zamykających i zakończonych, utwórz 2 nowe wydarzenia. o nazwach opening i opened. w miejscach, w których wcześniej nasłuchiwaliśmy zamknięcia okna. tym razem za pomocą utworzonego obserwatora mutacji .


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

Funkcja wywołania zwrotnego obserwatora mutacji jest wywoływana, gdy okno dialogowe atrybuty zmieniają się, co powoduje wyświetlenie listy zmian w postaci tablicy. Przeprowadź iterację atrybut się zmienia, szukając elementu attributeName. Następnie czy element ma atrybut czy nie: to ustawienie informuje, czy okno została otwarta. Jeśli została otwarta, usuń atrybut inert i ustaw fokus do elementu żądającego autofocus. lub pierwszy element button znaleziony w oknie. Na koniec, podobnie jak w przypadku zamknięcia i zdarzenia zamkniętego, wyślij od razu zdarzenie otwierające, poczekaj na animacje aby zakończyć, a następnie wysłać otwarte zdarzenie.

Dodawanie usuniętego wydarzenia

W aplikacjach jednostronicowych okna dialogowe są często dodawane i usuwane na podstawie tras lub inne potrzeby związane z aplikacją. Przydaje się, jeśli wyczyścisz zdarzenia lub dane po usunięciu okna.

Możesz to osiągnąć, korzystając z innego obserwatora mutacji. Tym razem zamiast obserwując atrybuty w elemencie dialogowym, obserwujemy elementy podrzędne i sprawdź, czy elementy dialogowe zostaną usunięte.


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

Wywołanie zwrotne obserwatora mutacji jest wywoływane za każdym razem, gdy elementy podrzędne są dodawane lub usuwane z treści dokumentu. Konkretne mutacje, które oglądasz, służą removedNodes, które zawierają nodeName z okno. Jeśli okno zostało usunięte, zdarzenia kliknięcia i zamknięcia są usuwane z zwolnienie pamięci i wysłanie niestandardowego zdarzenia usuniętego.

Usunięcie atrybutu wczytywania

Aby uniemożliwić odtwarzanie animacji wyjściowej okna po dodaniu do lub podczas wczytywania strony, do okna został dodany atrybut wczytywania. następujący skrypt czeka na zakończenie działania animacji dialogów, a następnie usuwa atrybut. Okno dialogowe można teraz swobodnie animować. ukrycie animacji, która w przeciwnym razie może rozpraszać uwagę.

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

Dowiedz się więcej o zapobieganiu pojawianiu się animacji klatek kluczowych podczas wczytywania strony. tutaj.

Wszystko razem

Oto dialog.js w całości, skoro objaśniliśmy już każdą sekcję pojedynczo:

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

Korzystanie z modułu dialog.js

Wyeksportowana funkcja z modułu oczekuje, że zostanie wywołana i przekazana okno element, do którego chce dodać te nowe zdarzenia i funkcje:

import GuiDialog from './dialog.js'

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

GuiDialog(MegaDialog)
GuiDialog(MiniDialog)

Te 2 okna dialogowe zostały udoskonalone – lekkie zamknięcie, animacja, poprawek błędów i innych zdarzeń, nad którymi trzeba pracować.

Nasłuchiwanie nowych zdarzeń niestandardowych

Każdy uaktualniony element okna może nasłuchiwać 5 nowych zdarzeń, na przykład:

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

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

MegaDialog.addEventListener('removed', dialogRemoved)

Oto 2 przykłady obsługi tych zdarzeń:

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

W utworzonej przeze mnie wersji demonstracyjnej z elementem dialogu użyję zdarzenia zamkniętego, dane formularza, aby dodać do listy nowy element awatara. Odpowiednia pora jest że okno dialogowe zakończyło animację wyjścia, a potem były animowane niektóre skrypty w nowym awatarze. Dzięki nowym wydarzeniom administrowanie wrażeniami użytkowników może działać płynniej.

Uwaga dialog.returnValue: zawiera ciąg zamykający przekazywany, gdy zdarzenie w oknie dialogowym close(). W przypadku zdarzenia dialogClosed bardzo ważne jest, czy okno zostało zamknięte, anulowane lub potwierdzone. Jeśli dane te są potwierdzone, pobiera wartości formularza i resetuje formularz. Zresetowanie jest przydatne, więc aby po ponownym wyświetleniu okno było puste i gotowe do przesłania nowego pliku.

Podsumowanie

Wiesz już, jak to zrobiłem. Jak Ty? 🙂

Stosujmy różne podejścia i poznajmy sposoby budowania obecności w internecie.

Utwórz wersję demonstracyjną, a potem dodaj linki do funkcji tweetuj mi. znajdziesz poniżej w sekcji z remiksami na karcie Społeczność.

Remiksy utworzone przez społeczność

Zasoby