Tworzenie komponentu toastu

Podstawowe informacje o tworzeniu adaptacyjnego i łatwo dostępnego komponentu toastu.

W tym poście chcę przedstawić pomysł na utworzenie elementu powiadomienia toastu. Zobacz prezentację.

Demonstracja

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

Przegląd

Tosty to nieinteraktywne, pasywne i asynchroniczne krótkie komunikaty przeznaczone dla użytkowników. Zwykle pełnią funkcję wzorca informacji zwrotnych, które informuje użytkownika o wynikach działania.

Interactions

Komunikaty różnią się od powiadomień, alertów i promptów, ponieważ nie są interaktywne i nie należy ich zamykać ani utrzymywać. Powiadomienia dotyczą ważniejszych informacji, komunikatów synchronicznych, które wymagają interakcji, lub komunikatów na poziomie systemu (a nie na poziomie strony). Komunikaty są bardziej bierne niż inne strategie powiadamiania.

Markup

Dobrym wyborem jest element <output>, ponieważ informuje o nim czytniki ekranu. Prawidłowy kod HTML to bezpieczna podstawa do ulepszania kodu JavaScript i CSS oraz dużej ilości kodu JavaScript.

Tost

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

Dodanie elementu role="status" może zwiększyć integrację społeczną. Spowoduje to ustawienie kreacji zastępczej, jeśli przeglądarka nie przyzna elementom <output> niejawnej roli zgodnie ze specyfikacją.

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

Pojemnik na tosty

Jednocześnie można wyświetlić więcej niż jeden tost. Do administrowania wieloma komunikatami używany jest kontener. Służy on również do kontrolowania pozycji tostów 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

Przypięłam tosty do inset-block-end widocznego obszaru, a jeśli pojawi się ich więcej, będą się one grupować od tej krawędzi ekranu.

kontener GUI

Kontener tosty odpowiada za układ wszystkich elementów wyświetlanych podczas prezentacji. Jest to wartość fixed względem widocznego obszaru i używa właściwości logicznej inset do określenia, do których krawędzi mają zostać przypięte, oraz nieco padding od 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 z Narzędziami deweloperskimi i dopełnieniem nałożonym na element .gui-toast-container.

Kontener reklam nie tylko określa swoją pozycję w widocznym obszarze, lecz także jest kontenerem siatki, który może wyrównywać i wyświetlać tosty. Elementy są wyśrodkowane jako grupa z oznaczeniem justify-content, a osobno – przy użyciu znacznika justify-items. Wrzuć trochę gap, żeby tosty się nie dotykały.

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

Zrzut ekranu z nakładką siatki CSS na grupie powiadomień, tym razem z zaznaczoną spacją i lukami między elementami podrzędnymi CSS.

Toast GUI

Pojedynczy tost ma padding, delikatnie rogi border-radius oraz funkcję min(), która ułatwia dopasowanie rozmiaru do komórek i komputerów. Rozmiar elastyczny w poniższym kodzie CSS zapobiega rozszerzeniom wyświetlania tostów przekraczających 90% widocznego obszaru 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 widocznym dopełnieniem i promieniem obramowania.

Style

Przy ustawionym układzie i pozycjonowaniu dodaj kod CSS, który pomoże Ci dostosować się do ustawień i interakcji użytkownika.

Pojemnik na tosty

Tosty nie są interaktywne. Dotykanie ich czy przesuwanie nie działa, ale obecnie odbierają zdarzenia wskaźnika. Skorzystaj z poniższego kodu CSS, aby nie dopuścić do kradzieży kliknięć.

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

Toast GUI

Nadaj tostom jasny lub ciemny motyw adaptacyjny z niestandardowymi właściwościami, HSL i preferowanym zapytaniem o multimedia.

.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

Podczas wyświetlania na ekranie nowego powiadomienia powinna towarzyszyć animacja. Dostosowanie zmniejszonego ruchu odbywa się przez domyślne ustawienie wartości translate na 0, ale zmodyfikowanie wartości ruchu do określonej długości w zapytaniu o multimedia w ramach preferencji ruchu . Każdy widzi animację, ale tylko niektórzy użytkownicy pokonują dany dystans.

Oto klatki kluczowe użyte w animacji tostru. CSS będzie kontrolować wejście, czas oczekiwania i wyjście toastu, a wszystko to 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 administruje 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 gotowe style i kod HTML dostępny w czytniku ekranu, potrzebny jest JavaScript do obsługi tworzenia, dodawania i niszczenia tostów na podstawie zdarzeń użytkownika. Interfejs tostów powinien być jak najprostszy i minimalny na początku. Na przykład:

import Toast from './toast.js'

Toast('My first toast')

Tworzę tosty i tosty

Gdy moduł toastu wczytuje się z JavaScriptu, musi utworzyć kontener toast i dodać go do strony. Element został dodany przed body, przez co z-index problemy z układaniem są mało prawdopodobne, ponieważ kontener znajduje się nad kontenerem w przypadku 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 przedstawiający grupę tostów między tagami nagłówka i ciała.

Funkcja init() jest wywoływana wewnętrznie do modułu i przechowuje element jako Toaster:

const Toaster = init()

Tworzenie elementu HTML typu Toast odbywa się za pomocą funkcji createToast(). Ta funkcja wymaga tekstu dla toastu, tworzy element <output>, dodaje do niego klasy i atrybuty, 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 tostami

JavaScript dodaje teraz do dokumentu kontener, w którym mogą wyświetlać się tosty. Możesz też dodawać utworzone tosty. Funkcja addToast() administruje obsługą jednego lub wielu tostów. Najpierw sprawdzam liczbę tostów i czy ruch jest w porządku, a potem wykorzystuję te informacje, by dołączyć ten tost lub przygotować ciekawą animację, by pozostałe tosty wyglądały na „zrobienie miejsca” na nowy tosty.

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

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

Po dodaniu pierwszego powiadomienia Toaster.appendChild(toast) dodaje tosty do strony, która uruchamia animacje CSS: animuj się, poczekaj 3s, animuj się. Funkcja flipToast() jest wywoływana, gdy pojawiają się tosty, z użyciem techniki o nazwie FLIP (Paul Lewis). Chodzi o to, aby obliczyć różnicę położeniu pojemnika przed dodaniem nowego powiadomienia i po nim. Pomyśl o tym, jak oznaczyć toster, gdzie będzie w tej chwili, i animować go z miejsca, w którym się znajduje.

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 wykonuje podnoszenie układu. Po dodaniu nowego powiadomienia siatka umieszcza go na początku i umieszcza między nim a innymi. Tymczasem do animacji kontenera ze starej pozycji służy animacja internetowa.

Jak połączyć wszystkie skrypty JavaScript w jednym miejscu

Po wywołaniu funkcji Toast('my first toast') wywoływany jest toast dodawany do strony (być może nawet kontener jest animowany w celu uwzględnienia nowego komunikatu), zwracana jest obietnica, a utworzony toast jest oglądany w celu ukończenia animacji CSS (3 animacje klatek kluczowych) w celu rozwiązania problemu.

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

Myślę, że myląca część tego kodu znajduje się w funkcji Promise.allSettled() i mapowaniu toast.getAnimations(). Ponieważ na potrzeby wyświetlania powiadomienia użyto wielu animacji klatki kluczowej, aby mieć pewność, że wszystkie z nich się skończyły, kod JavaScript musi mieć żądanie każdej z nich, a każdą z nich należy zrealizować w ciągu finished. allSettled sprawdza się w naszym przypadku, po spełnieniu wszystkich obietnic. Użycie polecenia await Promise.allSettled() oznacza, że kolejny wiersz kodu może bezpiecznie usunąć element i założyć, że wyświetlanie powiadomienia zakończyło się jego cyklem życia. Wywołanie resolve() spełnia obietnicę zaklęć, więc po pojawieniu się toastu deweloperzy mogą posprzątać lub wykonać inne czynności.

export default Toast

Na koniec funkcja Toast jest eksportowana z modułu, aby umożliwić importowanie i używanie innych skryptów.

Korzystanie z komponentu Toast

Aby korzystać z komunikatu dla programistów, zaimportuj funkcję Toast i wywołaj ją z ciągiem komunikatu.

import Toast from './toast.js'

Toast('Wizard Rose added to cart')

Jeśli po pojawieniu się komunikatu deweloper chce wyczyścić czystość, może użyć trybu asynchronicznego i 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, więc jak to zrobisz 🙂

Stwórzmy różne metody i nauczmy się wszystkiego, jak rozwijać się w internecie. Utwórz demonstrację i udostępnię mi linki na Twitterze, a dodam ją do sekcji remiksów w ramach społeczności poniżej.

Remiksy społeczności