Tworzenie komponentu toastu

Podstawowy opis tworzenia dostosowalnego i dostępnego komponentu toast.

W tym poście chcę podzielić się z Wami swoimi przemyśleniami na temat tworzenia elementu toast. Wypróbuj wersję demonstracyjną.

Demonstracja

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ą przeznaczone do przekazywania ważnych informacji, wiadomości synchronicznych wymagających interakcji lub wiadomości na poziomie systemu (a nie na poziomie strony). Powiadomienia wyskakujące 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>

Możesz zwiększyć role="status". Jest to rozwiązanie alternatywne, jeśli przeglądarka nie przypisuje elementom <output> rola implicit zgodnie ze specyfikacją.

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

Toast

Może być wyświetlanych jednocześnie więcej niż 1 toast. Aby zorganizować wiele toastów, używany jest kontener. Ten kontener odpowiada też za pozycję wyskakujących okienek 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

Postanowiłem przypiąć powiadomienia do inset-block-end widoku, a jeśli dodano więcej powiadomień, są one układane od tej krawędzi ekranu.

Kontener GUI

Kontener toastów odpowiada za cały układ toastów. Jest 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 tego samego block-end brzegu.

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

Zrzut ekranu z wymiarami pola i wypełnieniem w Narzędziech deweloperskich nałożonymi na element .gui-toast-container.

Oprócz pozycjonowania w widocznym obszarze kontener powiadomienia jest też kontenerem siatki, który może wyrównywać i rozmieszczać powiadomienia. Elementy są wyśrodkowane jako grupa za pomocą funkcji justify-content i indywidualnie za pomocą funkcji justify-items. Dodaj odrobinę gap, aby grzanki się nie stykały.

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

Zrzut ekranu z siatką CSS nałożoną na grupę toastów, tym razem z wyróżnieniem przestrzeni i luk między elementami podrzędnymi toastów.

Toast w interfejsie użytkownika

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

Po ustawieniu układu i pozycji dodaj kod CSS, który pomoże dostosować element do ustawień i interakcji użytkownika.

Kontener z tostem

Toasty nie są interaktywne, ich klikanie lub przesuwanie nie powoduje żadnych działań, ale obecnie wykorzystują one zdarzenia związane z wskaźnikiem. Aby zapobiec kradzieży kliknięć przez toasty, użyj tego kodu CSS.

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

Toast w interfejsie użytkownika

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 z animacją. Dostosowanie do ograniczonego ruchu polega na ustawieniu wartości translate na 0, ale zaktualizowaniu wartości ruchu na długość w zapytaniu o preferencje dotyczące ruchu . Każdy widzi animację, ale tylko niektórzy użytkownicy widzą toast.

Oto klatki kluczowe użyte w animacji powiadomienia. 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ł powiadomienia wczytuje się z JavaScriptu, musi utworzyć kontener powiadomienia i dodać go do strony. Wybrałem dodanie elementu przed elementem body, ponieważ w ten sposób problemy ze sterowaniem elementem z-index są mało prawdopodobne, 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ę toastów między tagami head i body

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

const Toaster = init()

Tworzenie elementu HTML komunikatu okienka odbywa się za pomocą funkcji 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, gdy istnieją już komunikaty typu toast. Wykorzystuje ona technikę FLIP opracowaną przez Paula Lewisa. Chodzi o obliczenie różnicy w pozycjach kontenera przed dodaniem nowego komunikatu i po jego dodaniu. 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 zajmuje się układem. Gdy dodasz nowy komunikat, zostanie on umieszczony na początku siatki i oddzielony od pozostałych. W tym czasie do animacji kontenera ze starej pozycji używa się animacji internetowej.

Łą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(). W przypadku toastów użyłam wielu animacji kluczowych klatek, więc aby mieć pewność, że wszystkie zostały ukończone, każda z nich musi być żądana z JavaScriptu, a wszystkiefinished obietnice muszą być spełnione. allSettled działa to w naszym przypadku, ponieważ kończy się, gdy wszystkie obietnice zostaną spełnione. Użycie await Promise.allSettled() oznacza, że następny wiersz kodu może bezpiecznie usunąć element i zakładać, że toast ukończył swój cykl życia. Na koniec wywołanie funkcji resolve() spełnia obietnicę dotyczącą toastów, dzięki czemu deweloperzy mogą wyczyścić pamięć lub wykonać inne zadania po wyświetleniu toastu.

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 użyć toastu lub funkcji toastu dla deweloperów, zaimportuj funkcję Toast i wywołaj 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

Teraz, gdy już wiesz, jak to zrobić, jak Ty to zrobisz? 🙂

Zróżnicujemy nasze podejścia i poznamy wszystkie sposoby tworzenia stron internetowych. Utwórz wersję demonstracyjną, wyślij mi linki, a ja dodam je do sekcji z remiksami społeczności.

Remiksy społeczności