Tworzenie komponentu toastu

Podstawowe omówienie tworzenia adaptacyjnego i przystępnego komponentu toast.

W tym poście przedstawię sposoby tworzenia komponentu toast. Zobacz prezentację.

Prezentacja

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

Omówienie

Toasty to nieinteraktywne, pasywne i asynchroniczne krótkie wiadomości dla użytkowników. Zwykle są one używane jako wzór informacji zwrotnej interfejsu, aby poinformować użytkownika o wyniku działania.

Interakcje

Komunikaty różnią się od powiadomień, alertówpromptów, ponieważ nie są interaktywne. Nie można ich zamknąć ani pozostawić. Powiadomienia służą do przekazywania ważnych informacji, przesyłania synchronicznego, które wymagają interakcji, lub komunikatów na poziomie systemu (w przeciwieństwie do treści na poziomie strony). Powiadomienia typu toast są bardziej pasywne niż inne strategie powiadomień.

Znacznik

Element <output> jest dobrym wyborem do wyświetlania powiadomień, ponieważ jest odczytywany przez czytniki ekranu. Prawidłowy kod HTML stanowi bezpieczną podstawę, którą możemy wzbogacić za pomocą JavaScriptu i CSS.

Toast

<output class="gui-toast">Item added to cart</output>

Jeśli chcesz, możesz dodać role="status". Jest to rozwiązanie zastępcze, jeśli przeglądarka nie przypisuje elementom <output> rola implicit zgodnie ze specyfikacją.

<output role="status" class="gui-toast">Item added to cart</output>

Tosta

Może być wyświetlanych jednocześnie więcej niż 1 toast. Do administrowania wieloma powiadomieniami wykorzystany jest kontener. Ten kontener określa też położenie powiadomień na ekranie.

<section class="gui-toast-group">
  <output role="status">Wizard Rose added to cart</output>
  <output role="status">Self Watering Pot added to cart</output>
</section>

Układy

Chcę przypiąć powiadomienia do obszaru inset-block-end widocznego obszaru. Gdy dodasz więcej powiadomień, będą one układane w stos od krawędzi ekranu.

Kontener GUI

Kontener toastów odpowiada za cały układ toastów. Jest ona fixed do widoku i korzysta z właściwości logicznej inset, aby określić, do których krawędzi ma być przypięta, oraz trochę padding z tej samej krawędzi block-end.

.gui-toast-group {
  position: fixed;
  z-index: 1;
  inset-block-end: 0;
  inset-inline: 0;
  padding-block-end: 5vh;
}

Zrzut ekranu z rozmiarem pola w Narzędziach deweloperskich i dopełnieniem nałożonym na element .gui-toast-container.

Ustawianie się w widocznym obszarze jest też siatką, która może wyrównywać i rozpowszechniać powiadomienia. Elementy są wyśrodkowane jako grupa z atrybutem justify-content i indywidualnie wyśrodkowane znakiem justify-items. Dodaj trochę gap, aby grzanki się nie stykały.

.gui-toast-group {
  display: grid;
  justify-items: center;
  justify-content: center;
  gap: 1vh;
}

Zrzut ekranu z nakładką siatki CSS w grupie powiadomień, tym razem z wyróżnionym obszarem i lukami między wyświetlanymi elementami podrzędnymi.

Tosty GUI

Pojedynczy toast ma padding, zaokrąglone rogi z border-radius oraz funkcję min(), która ułatwia dostosowanie rozmiaru do urządzeń mobilnych i komputerów. Rozmiar elastyczny w tym kodzie CSS zapobiega powiększaniu się powiadomień do szerokości większej niż 90% obszaru widoku lub 25ch.

.gui-toast {
  max-inline-size: min(25ch, 90vw);
  padding-block: .5ch;
  padding-inline: 1ch;
  border-radius: 3px;
  font-size: 1rem;
}

Zrzut ekranu przedstawiający pojedynczy element .gui-toast z wyświetlonym marginesem i promieniem obramowania

Style

Gdy ustawisz układ i pozycjonowanie, dodaj kod CSS, który pomoże Ci dostosować się do ustawień i interakcji użytkownika.

Kontener z tostem

Tosty nie są interaktywne. Klikanie czy przesuwanie palcem po nich nic nie da, ale obecnie wykorzystują zdarzenia wskaźnika. Aby zapobiec kradzieży kliknięć przez toasty, użyj tego kodu CSS.

.gui-toast-group {
  pointer-events: none;
}

Tosty GUI

Nadaj toastom jasny lub ciemny motyw za pomocą właściwości niestandardowych, HSL i zapytania o multimedia z preferencjami.

.gui-toast {
  --_bg-lightness: 90%;

  color: black;
  background: hsl(0 0% var(--_bg-lightness) / 90%);
}

@media (prefers-color-scheme: dark) {
  .gui-toast {
    color: white;
    --_bg-lightness: 20%;
  }
}

Animacja

Nowy komunikat powinien pojawiać się na ekranie w ramach animacji. Zmniejszony ruch można dostosować, ustawiając domyślne wartości translate na 0, ale zmieniając wartość animacji na odpowiednią długość w zapytaniu o multimedia w preferencji ruchu . Każdy widzi animację, ale tylko niektórzy użytkownicy widzą toast.

Tutaj są klatki kluczowe używane w animacji wyświetlanej w wyskakującym okienku. CSS będzie kontrolować wejście, oczekiwanie i wyjście toastu w ramach jednej animacji.

@keyframes fade-in {
  from { opacity: 0 }
}

@keyframes fade-out {
  to { opacity: 0 }
}

@keyframes slide-in {
  from { transform: translateY(var(--_travel-distance, 10px)) }
}

Następnie element toast konfiguruje zmienne i zarządza klatkami kluczowymi.

.gui-toast {
  --_duration: 3s;
  --_travel-distance: 0;

  will-change: transform;
  animation: 
    fade-in .3s ease,
    slide-in .3s ease,
    fade-out .3s ease var(--_duration);
}

@media (prefers-reduced-motion: no-preference) {
  .gui-toast {
    --_travel-distance: 5vh;
  }
}

JavaScript

Gdy masz już gotowe style i HTML z dostępnością dla czytników ekranu, potrzebujesz JavaScriptu, aby zarządzać wyświetlaniem, dodawaniem i usuwaniem powiadomień na podstawie zdarzeń użytkownika. Interfejs programisty dla komponentu toast powinien być minimalny i łatwy w użyciu, na przykład:

import Toast from './toast.js'

Toast('My first toast')

Tworzenie grupy toastów i samych toastów

Gdy moduł toast jest wczytywany z JavaScriptu, musi utworzyć kontener toastów i dodać go do strony. Wybrałem dodanie elementu przed elementem body, co powinno wyeliminować problemy z układaniem elementów z-index, ponieważ kontener znajduje się nad kontenerem dla wszystkich elementów treści.

const init = () => {
  const node = document.createElement('section')
  node.classList.add('gui-toast-group')

  document.firstElementChild.insertBefore(node, document.body)
  return node
}

Zrzut ekranu pokazujący grupę powiadomień między tagami nagłówka i treści.

Funkcja init() jest wywoływana wewnętrznie w module, a element jest przechowywany jako Toaster:

const Toaster = init()

Tworzenie elementu HTML komunikatu powoduje funkcja createToast(). Funkcja wymaga tekstu dla komunikatu toastowego, tworzy element <output>, ozdabia go za pomocą niektórych klas i atrybutów, ustawia tekst i zwraca węzeł.

const createToast = text => {
  const node = document.createElement('output')
  
  node.innerText = text
  node.classList.add('gui-toast')
  node.setAttribute('role', 'status')

  return node
}

Zarządzanie jednym lub wieloma komunikatami toast

JavaScript dodaje teraz do dokumentu kontener na toasty i jest gotowy do dodania utworzonych toastów. Funkcja addToast() zarządza wyświetlaniem jednego lub wielu komunikatów. Najpierw sprawdza liczbę toastów i czy ruch jest prawidłowy, a potem używa tych informacji, aby dołączyć toast lub wykonać animowanie, aby inne tosty „zrobiły miejsce” dla nowego.

const addToast = toast => {
  const { matches:motionOK } = window.matchMedia(
    '(prefers-reduced-motion: no-preference)'
  )

  Toaster.children.length && motionOK
    ? flipToast(toast)
    : Toaster.appendChild(toast)
}

Podczas dodawania pierwszego komunikatu Toaster.appendChild(toast) dodaję komunikat do strony, który uruchamia animacje CSS: animacja wejścia, oczekiwanie 3s, animacja wyjścia. Funkcja flipToast() jest wywoływana w przypadku istniejących powiadomień. Wykorzystuje ona technikę FLIP autorstwa Paula Lewisa. Chodzi o obliczenie różnicy w położeniu kontenera, przed i po dodaniu nowego komunikatu. Wyobraź sobie, że zaznaczasz, gdzie jest toster, gdzie ma się znaleźć, a potem animujesz jego ruch z jednego miejsca do drugiego.

const flipToast = toast => {
  // FIRST
  const first = Toaster.offsetHeight

  // add new child to change container size
  Toaster.appendChild(toast)

  // LAST
  const last = Toaster.offsetHeight

  // INVERT
  const invert = last - first

  // PLAY
  const animation = Toaster.animate([
    { transform: `translateY(${invert}px)` },
    { transform: 'translateY(0)' }
  ], {
    duration: 150,
    easing: 'ease-out',
  })
}

Siatka CSS odpowiada za układ. Po dodaniu nowego powiadomienia siatka umieszcza go na początku i odstępuje od innych. Tymczasem do animowania kontenera od poprzedniej pozycji jest używana animacja internetowa.

Łączenie wszystkich elementów kodu JavaScript

Gdy wywołana zostanie funkcja Toast('my first toast'), na stronie zostanie utworzony komunikat toastowy (być może nawet kontener zostanie animowany, aby pomieścić nowy komunikat) i zwrócona zostanie obietnica, a utworzony komunikat toastowy zostanie obserwowany w celu zakończenia animacji CSS (3 animacje kluczowych klatek) w ramach obietnicy.

const Toast = text => {
  let toast = createToast(text)
  addToast(toast)

  return new Promise(async (resolve, reject) => {
    await Promise.allSettled(
      toast.getAnimations().map(animation => 
        animation.finished
      )
    )
    Toaster.removeChild(toast)
    resolve() 
  })
}

Najbardziej myląca część tego kodu to funkcja Promise.allSettled() i mapowanie toast.getAnimations(). Użyłem wielu animacji klatek kluczowych w przypadku powiadomienia, więc aby mieć pewność, że wszystkie są gotowe, każdą z nich trzeba zażądać od JavaScriptu, a każdą z ich obietnic finished należy żądać do końca. allSettled działa to w naszym przypadku, ponieważ kończy się, gdy wszystkie obietnice zostaną spełnione. Użycie dyrektywy await Promise.allSettled() oznacza, że kolejny wiersz kodu może z łatwością usunąć element i zakładać, że wyświetlanie powiadomienia zakończyło się już cyklem życia. Wywołanie resolve() spełnia ogólną obietnicę tego, że deweloperzy będą mogli posprzątać urządzenie lub zająć się czymś innym po wyświetleniu komunikatu.

export default Toast

Na koniec funkcja Toast jest eksportowana z modułu, aby inne skrypty mogły ją importować i korzystać z niej.

Korzystanie z komponentu Toast

Aby korzystać z toasta lub funkcji dla deweloperów, należy zaimportować funkcję Toast i wywołać ją za pomocą ciągu znaków wiadomości.

import Toast from './toast.js'

Toast('Wizard Rose added to cart')

Jeśli deweloper chce wykonać czyszczenie lub inną pracę po wyświetleniu komunikatu toastowego, może użyć asynchronicznego wywołania await.

import Toast from './toast.js'

async function example() {
  await Toast('Wizard Rose added to cart')
  console.log('toast finished')
}

Podsumowanie

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

Stosujmy różne podejścia i poznajmy sposoby budowania obecności w internecie. Stwórz wersję demonstracyjną, a potem dodam linki do tweetów, a ja dodam ją do poniższej sekcji na temat remiksów na karcie Społeczność.

Remiksy społeczności