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
- Aby udostępnić projekt do edycji, kliknij Remiksuj, aby edytować.
- 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](https://web.dev/static/articles/codelab-building-a-stories-component/image/chrome-devtools-open-a-72aaa3c91f873.png?authuser=7&hl=pl)
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ówtouch-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
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.