Ćwiczenia z programowania: tworzenie komponentu Relacje

Dzięki tym ćwiczeniom w Codelabs dowiesz się, jak tworzyć treści takie jak relacje na Instagramie w internecie. Będziemy tworzyć komponent na bieżąco, zaczynając od HTML, potem CSS, a potem JavaScript.

Przeczytaj mojego posta na blogu o tworzeniu komponentu Relacje. .

Konfiguracja

  1. Aby udostępnić projekt do edycji, kliknij Remiksuj, aby edytować.
  2. Otwórz pokój app/index.html.

HTML

Zawsze staram się używać semantycznego kodu HTML. Każdy znajomy może mieć dowolną liczbę historii, więc uznałem, że warto stworzyć <section> dla każdego znajomego i element <article> w przypadku każdej historii. Zacznijmy jednak od początku. Najpierw potrzebny będzie kontener na opowiadania

Dodaj element <div> do elementu <body>:

<div class="stories">

</div>

Dodaj elementy <section>, które będą reprezentować znajomych:

<div class="stories">
  <section class="user"></section>
  <section class="user"></section>
  <section class="user"></section>
  <section class="user"></section>
</div>

Dodaj elementy <article>, które będą reprezentować artykuły:

<div class="stories">
  <section class="user">
    <article class="story" style="--bg: url(https://picsum.photos/480/840);"></article>
    <article class="story" style="--bg: url(https://picsum.photos/480/841);"></article>
  </section>
  <section class="user">
    <article class="story" style="--bg: url(https://picsum.photos/481/840);"></article>
  </section>
  <section class="user">
    <article class="story" style="--bg: url(https://picsum.photos/481/841);"></article>
  </section>
  <section class="user">
    <article class="story" style="--bg: url(https://picsum.photos/482/840);"></article>
    <article class="story" style="--bg: url(https://picsum.photos/482/843);"></article>
    <article class="story" style="--bg: url(https://picsum.photos/482/844);"></article>
  </section>
</div>
  • Używamy usługi do tworzenia obrazów (picsum.com), aby pomóc w tworzeniu prototypów.
  • Atrybut style w każdym elemencie <article> jest częścią wczytywania zastępczego omówię dokładniej w następnej sekcji.

CSS

Nasze treści są zawsze w dobrym stylu. Zamieńmy te kości w coś, co ludzie będą robić z którymi chcesz wchodzić w interakcję. Już dziś skupimy się na urządzeniach mobilnych.

.stories

Dla naszego kontenera <div class="stories"> potrzebujesz kontenera przewijanego w poziomie. Jest to możliwe dzięki:

  • zmieniając kontener w siatkę.
  • Skonfigurowanie każdego elementu podrzędnego w celu wypełnienia ścieżki wiersza
  • Dostosowanie szerokości każdego elementu podrzędnego do szerokości widocznego obszaru na urządzeniu mobilnym

W siatce nowe kolumny o szerokości 100vw będą nadal umieszczane po prawej stronie poprzedniej do momentu umieszczenia wszystkich elementów HTML w znacznikach.

Chrome i Narzędzia deweloperskie otwierają się z wizualną siatką przedstawiającą układ o pełnej szerokości
W Narzędziach deweloperskich w Chrome widać przepełnienie kolumny siatki i przewijanie w poziomie.

Dodaj ten kod CSS na końcu app/css/index.css:

.stories {
  display: grid;
  grid: 1fr / auto-flow 100%;
  gap: 1ch;
}

Zawartość wykracza poza widoczny obszar, więc musimy pokazać, jak go obsłużyć. Dodaj zaznaczone wiersze kodu do zestawu reguł .stories:

.stories {
  display: grid;
  grid: 1fr / auto-flow 100%;
  gap: 1ch;
  overflow-x: auto;
  scroll-snap-type: x mandatory;
  overscroll-behavior: contain;
  touch-action: pan-x;
}

Chcemy przewijać w poziomie, więc ustawimy parametr overflow-x na auto Gdy użytkownik przewija stronę, chcemy, by komponent delikatnie przylegał do następnego artykułu, więc użyjemy scroll-snap-type: x mandatory. Przeczytaj więcej na ten temat Fragmenty CSS w punktach przyciągania przewijania CSS i nadmierne przewijanie. mojego posta na blogu.

Zarówno kontener nadrzędny, jak i elementy podrzędne muszą wyrazić zgodę na przyciąganie przewijania. zajmijmy się tym teraz. Dodaj ten kod na końcu app/css/index.css:

.user {
  scroll-snap-align: start;
  scroll-snap-stop: always;
}

Twoja aplikacja jeszcze nie działa, ale na filmie poniżej dowiesz się, co się stanie, gdy Funkcja scroll-snap-type jest włączona i wyłączona. Po włączeniu każdy poziomo przewijają elementy do następnego artykułu. Gdy jest wyłączona, przeglądarka używa zachowanie domyślne.

Pozwoli Ci to przejrzeć listę znajomych, ale nadal mamy problem z historią do rozwiązania.

.user

Utwórzmy w sekcji .user układ, który przekształci historię dziecka . Aby rozwiązać ten problem, użyjemy przydatnej sztuczki. Zasadniczo tworzymy siatkę 1 x 1, w której wiersz i kolumna mają tę samą siatkę. alias domeny [story], a każdy element siatki relacji będzie próbował przejąć to miejsce, w wyniku czego powstaje stos.

Dodaj zaznaczony kod do zestawu reguł .user:

.user {
  scroll-snap-align: start;
  scroll-snap-stop: always;
  display: grid;
  grid: [story] 1fr / [story] 1fr;
}

Dodaj ten zestaw reguł na dole sekcji app/css/index.css:

.story {
  grid-area: story;
}

Bez pozycjonowania bezwzględnego, zmiennoprzecinkowego i innych dyrektyw układu, i elementy wciąż pozostają w płynie. Poza tym to prawie jak kod, spójrz na to! Informacje te są szczegółowo opisane w filmie i poście na blogu.

.story

Teraz trzeba tylko określić styl samego elementu historii.

Wcześniej wspomnieliśmy, że atrybut style w każdym elemencie <article> jest częścią technika wczytywania zastępczego:

<article class="story" style="--bg: url(https://picsum.photos/480/840);"></article>

Wykorzystamy właściwość background-image CSS, która pozwala określić więcej niż jednego obrazu tła. Możemy je ułożyć w takiej kolejności, aby użytkownik jest na górze. Po załadowaniu pojawi się automatycznie. Do zapiszemy URL obrazu w usłudze niestandardowej (--bg) i użyjemy go w naszym CSS, aby umieścić w warstwie z obiektem zastępczym wczytywania.

Najpierw zaktualizujmy zestaw reguł .story, aby zastąpić gradient obrazem tła. po zakończeniu wczytywania. Dodaj zaznaczony kod do zestawu reguł .story:

.story {
  grid-area: story;

  background-size: cover;
  background-image:
    var(--bg),
    linear-gradient(to top, lch(98 0 0), lch(90 0 0));
}

Ustawienie background-size na cover zapewnia, że w polu nie ma pustego miejsca bo wypełni go obraz. Definiowanie 2 obrazów tła pozwala nam wykorzystać atrakcyjny internetowy trik CSS nazywany wczytywaniem tombstone:

  • Obraz tła 1 (var(--bg)) to adres URL przekazany w kodzie HTML
  • Obraz tła 2 (linear-gradient(to top, lch(98 0 0), lch(90 0 0)) to gradient ma być wyświetlany podczas wczytywania adresu URL

Po pobraniu obrazu CSS automatycznie zastąpi gradient tym obrazem.

Dodamy kod CSS, by usunąć pewne zachowania i zwiększyć szybkość działania przeglądarki. Dodaj zaznaczony kod do zestawu reguł .story:

.story {
  grid-area: story;

  background-size: cover;
  background-image:
    var(--bg),
    linear-gradient(to top, lch(98 0 0), lch(90 0 0));

  user-select: none;
  touch-action: manipulation;
}
  • user-select: none zapobiega przypadkowemu zaznaczaniu tekstu przez użytkowników
  • touch-action: manipulation informuje przeglądarkę, że te interakcje powinny być traktowane jak zdarzenia dotyku, co sprawia, że przeglądarka nie będzie zdecyduj, czy klikasz adres URL,

Na koniec dodajmy trochę CSS, by animować przejście między artykułami. Dodaj parametr wyróżniony kod w zestawie reguł .story:

.story {
  grid-area: story;

  background-size: cover;
  background-image:
    var(--bg),
    linear-gradient(to top, lch(98 0 0), lch(90 0 0));

  user-select: none;
  touch-action: manipulation;

  transition: opacity .3s cubic-bezier(0.4, 0.0, 1, 1);

  &.seen {
    opacity: 0;
    pointer-events: none;
  }
}

Zajęcia .seen zostaną dodane do opowiadania, które wymaga wyjścia. Mam niestandardową funkcję wygładzania (cubic-bezier(0.4, 0.0, 1,1)) z interfejsu Easing firmy Material Design (przewiń do sekcji Przyspieszone wygładzanie).

Jeśli masz spostrzegawczość, zapewne zauważyłeś pointer-events: none deklaracja państwowa i małej teraz trochę po głowie. Według mnie to jedyny dla tej różnicy. Potrzebujemy tego, ponieważ element .seen.story będzie wyświetlać się na górze i będzie otrzymywać kliknięcia, mimo że jest niewidoczny. Poprzez ustawienie wartości pointer-events do none, zmieniamy szklaną historię w okno i nie kradniemy do większej liczby interakcji. To nie najgorszy kompromis, a zarządzanie nim nie jest zbyt trudne. w naszej usłudze porównywania cen. Nie żonglujemy urządzeniem z-index. Podoba mi się to bez ruchu.

JavaScript

Interakcje z komponentem Relacje są dość proste dla użytkownika: kliknij w prawo, aby przejść dalej, dotknij w lewo, aby wrócić. Użytkownicy mają tendencję do prostych rozwiązań być dla programistów. Zajmiemy się jednak wieloma innymi.

Konfiguracja

Na początek zapiszmy i przechowujemy jak najwięcej informacji. Dodaj do pliku app/js/index.js ten kod:

const stories = document.querySelector('.stories')
const median = stories.offsetLeft + (stories.clientWidth / 2)

Pierwszy wiersz kodu JavaScript pobiera i przechowuje odniesienie do naszego podstawowego kodu HTML pierwiastka elementu. W następnym wierszu obliczamy, gdzie znajduje się środek elementu, więc może zdecydować, czy kliknięcie ma przejść do przodu czy do tyłu.

Stan

Następnie tworzymy mały obiekt o stanie odpowiednim dla naszej logiki. W tym Interesuje nas tylko bieżąca historia. Dzięki znacznikom HTML możemy otwórz ją, chwytając pierwszego znajomego i jego najnowszą historię. Dodaj zaznaczony kod do urządzenia app/js/index.js:

const stories = document.querySelector('.stories')
const median = stories.offsetLeft + (stories.clientWidth / 2)

const state = {
  current_story: stories.firstElementChild.lastElementChild
}

Detektory

Mamy już wystarczającą logikę, aby zacząć nasłuchiwać zdarzeń użytkownika i je kierować.

Mysz

Zacznijmy od posłuchania zdarzenia 'click' w kontenerze Relacje. Dodaj zaznaczony kod do elementu app/js/index.js:

const stories = document.querySelector('.stories')
const median = stories.offsetLeft + (stories.clientWidth / 2)

const state = {
  current_story: stories.firstElementChild.lastElementChild
}

stories.addEventListener('click', e => {
  if (e.target.nodeName !== 'ARTICLE')
    return

  navigateStories(
    e.clientX > median
      ? 'next'
      : 'prev')
})

Jeśli zdarzy się kliknięcie, które nie jest związane z elementem <article>, anulujemy kaucję i nie podejmiemy żadnych działań. W przypadku artykułu chwytamy poziomą pozycję myszy lub palca clientX Nie wdrożono jeszcze funkcji navigateStories, ale argument określa kierunek, w którym powinniśmy iść. Jeśli taka pozycja użytkownika to jest większa niż mediana, wiemy, że musimy wyznaczyć trasę do next, w przeciwnym razie prev (poprzednia).

Klawiatura

Posłuchajmy naciśnięć na klawiaturze. Po naciśnięciu strzałki w dół przechodzimy do: next. Jeśli strzałka w górę jest strzałka w górę, ruszamy do obszaru prev.

Dodaj zaznaczony kod do elementu app/js/index.js:

const stories = document.querySelector('.stories')
const median = stories.offsetLeft + (stories.clientWidth / 2)

const state = {
  current_story: stories.firstElementChild.lastElementChild
}

stories.addEventListener('click', e => {
  if (e.target.nodeName !== 'ARTICLE')
    return

  navigateStories(
    e.clientX > median
      ? 'next'
      : 'prev')
})

document.addEventListener('keydown', ({key}) => {
  if (key !== 'ArrowDown' || key !== 'ArrowUp')
    navigateStories(
      key === 'ArrowDown'
        ? 'next'
        : 'prev')
})

Nawigacja po relacjach

Czas na zajęcie się wyjątkową logiką biznesową Twoich historii i wygodą użytkowników, którzy się nimi zajmują z których wiele jest znanych. Może się to wydawać grube i skomplikowane, ale jeśli spojrzysz na to w całości okaże się, że jest całkiem sensowne.

Z góry przechowujemy selektory, które pomagają nam zdecydować, czy przewinąć znajomego lub pokazać/ukryć historię. Ponieważ pracujemy nad kodem HTML, zapytań w poszukiwaniu obecności znajomych (użytkowników) lub historii (artykułów).

Te zmienne pomogą nam znaleźć odpowiedzi na pytania takie jak „podawany artykuł x, czy „następny”. czy przejść do innej historii od tego samego czy innego znajomego?” Zrobiłem to, używając drzewa do tego, jaką stworzyliśmy, i docierając do rodziców i ich dzieci.

Dodaj ten kod na końcu app/js/index.js:

const navigateStories = direction => {
  const story = state.current_story
  const lastItemInUserStory = story.parentNode.firstElementChild
  const firstItemInUserStory = story.parentNode.lastElementChild
  const hasNextUserStory = story.parentElement.nextElementSibling
  const hasPrevUserStory = story.parentElement.previousElementSibling
}

Oto nasz cel związany z logiką biznesową, maksymalnie zbliżony do języka naturalnego:

  • Zdecyduj, jak obsługiwać kliknięcie
    • Jeśli jest następny lub poprzedni artykuł: pokaż go.
    • Jeśli to ostatnia/pierwsza historia znajomego: pokaż nowemu znajomemu.
    • Jeśli nie ma historii, którą można otworzyć w tym kierunku: nie rób nic.
  • Umieść nową opowieść w schowku state

Dodaj zaznaczony kod do funkcji navigateStories:

const navigateStories = direction => {
  const story = state.current_story
  const lastItemInUserStory = story.parentNode.firstElementChild
  const firstItemInUserStory = story.parentNode.lastElementChild
  const hasNextUserStory = story.parentElement.nextElementSibling
  const hasPrevUserStory = story.parentElement.previousElementSibling

  if (direction === 'next') {
    if (lastItemInUserStory === story && !hasNextUserStory)
      return
    else if (lastItemInUserStory === story && hasNextUserStory) {
      state.current_story = story.parentElement.nextElementSibling.lastElementChild
      story.parentElement.nextElementSibling.scrollIntoView({
        behavior: 'smooth'
      })
    }
    else {
      story.classList.add('seen')
      state.current_story = story.previousElementSibling
    }
  }
  else if(direction === 'prev') {
    if (firstItemInUserStory === story && !hasPrevUserStory)
      return
    else if (firstItemInUserStory === story && hasPrevUserStory) {
      state.current_story = story.parentElement.previousElementSibling.firstElementChild
      story.parentElement.previousElementSibling.scrollIntoView({
        behavior: 'smooth'
      })
    }
    else {
      story.nextElementSibling.classList.remove('seen')
      state.current_story = story.nextElementSibling
    }
  }
}

Wypróbuj

  • Aby wyświetlić podgląd strony, kliknij Wyświetl aplikację. Następnie naciśnij Pełny ekran pełny ekran

Podsumowanie

To już podsumowanie potrzeb, jakie miałam w związku z tym komponentem. Możesz wykorzystać napędzaj go danymi i dobieraj go do swoich potrzeb.