In diesem Codelab lernst du, wie du Instagram Stories erstellen kannst. im Web. Wir erstellen die Komponente nach und nach, angefangen mit HTML, dann CSS. und dann auf JavaScript.
Dann sehen Sie sich meinen Blogpost Eine Stories-Komponente erstellen an. um mehr über die schrittweisen Verbesserungen beim Erstellen dieser Komponente zu erfahren.
Einrichtung
- Klicke auf Zum Bearbeiten Remix, damit das Projekt bearbeitet werden kann.
- Öffnen Sie
app/index.html
.
HTML
Ich verwende immer semantisches HTML.
Da jeder Freund beliebig viele Geschichten haben kann, fand ich es sinnvoll,
<section>
-Element für jeden Freund und ein <article>
-Element für jede Geschichte.
Fangen wir aber noch einmal von vorn an. Zunächst benötigen wir einen Container für die
Story-Komponente.
Füge deinem <body>
ein <div>
-Element hinzu:
<div class="stories">
</div>
Fügen Sie einige <section>
-Elemente hinzu, die für Freunde stehen:
<div class="stories">
<section class="user"></section>
<section class="user"></section>
<section class="user"></section>
<section class="user"></section>
</div>
Fügen Sie einige <article>
-Elemente hinzu, um Geschichten darzustellen:
<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>
- Wir verwenden einen Bilddienst (
picsum.com
), um den Prototyp von Geschichten zu unterstützen. - Das Attribut
style
in jedem<article>
ist Teil des Ladevorgangs eines Platzhalters über die wir im nächsten Abschnitt mehr erfahren.
CSS
Unsere Inhalte sind bereit für Stil. Verwandeln wir diese Knochen in etwas, das Leute mit denen Sie interagieren möchten. Wir arbeiten heute an einem Mobile-First-Ansatz.
.stories
Für den <div class="stories">
-Container benötigen wir einen horizontal scrollbaren Container.
Das können wir erreichen, indem wir:
- Container als Raster festlegen
- Festlegen, dass jedes untergeordnete Element den Zeilen-Track füllt
- Die Breite jedes untergeordneten Elements an die Breite des Darstellungsbereichs eines Mobilgeräts anpassen
Neue 100vw
-breite Spalten im Raster werden weiterhin rechts neben der vorherigen platziert
bis alle HTML-Elemente in Ihrem Markup platziert sind.
![Chrome und die Entwicklertools werden mit einem Raster geöffnet, das das Layout in voller Breite zeigt](https://web.dev/static/articles/codelab-building-a-stories-component/image/chrome-devtools-open-a-72aaa3c91f873.png?authuser=6&hl=de)
Fügen Sie am Ende von app/css/index.css
den folgenden CSS-Code hinzu:
.stories {
display: grid;
grid: 1fr / auto-flow 100%;
gap: 1ch;
}
Da Inhalte jetzt über den Darstellungsbereich hinausgehen, ist es an der Zeit zu erkennen,
wie damit umgegangen werden soll. Fügen Sie die markierten Codezeilen zu Ihrem .stories
-Regelsatz hinzu:
.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;
}
Da wir horizontales Scrollen wünschen, setzen wir overflow-x
auf
auto
. Wenn Nutzende scrollen, möchten wir, dass die Komponente leicht
auf der nächsten Story platziert wird.
Also verwenden wir scroll-snap-type: x mandatory
. Weitere Informationen
CSS in den CSS-Scroll-Snap-Punkten
und das Overscroll-Verhalten
in den Abschnitten meines Blogposts.
Es müssen sowohl der übergeordnete Container als auch die untergeordneten Elemente dem
Scroll-Andocken zustimmen, sodass
sollten wir das jetzt angehen. Fügen Sie am Ende von app/css/index.css
den folgenden Code ein:
.user {
scroll-snap-align: start;
scroll-snap-stop: always;
}
Ihre App funktioniert noch nicht, aber im Video unten sehen Sie, was passiert, wenn
scroll-snap-type
ist aktiviert und deaktiviert. Wenn diese Option aktiviert ist,
durch Scrollen zur nächsten Meldung. Wenn diese Option deaktiviert ist, verwendet der Browser seine
Standard-Scrollverhalten.
So kannst du durch deine Freunde scrollen, aber wir haben immer noch ein Problem. mit den zu lösenden Geschichten.
.user
Erstellen wir nun ein Layout im .user
-Abschnitt, in dem diese Child-Story aufbereitet wird.
Elemente an ihrer Stelle. Dieses Problem lässt sich mit einem praktischen Stapeltrick lösen.
Wir erstellen im Grunde ein 1 x 1-Raster, bei dem Zeile und Spalte dasselbe Raster haben
[story]
und jedes Rasterelement in der Story versucht, diesen Bereich zu beanspruchen.
was zu einem Stack führt.
Fügen Sie den hervorgehobenen Code zu Ihrem .user
-Regelsatz hinzu:
.user {
scroll-snap-align: start;
scroll-snap-stop: always;
display: grid;
grid: [story] 1fr / [story] 1fr;
}
Fügen Sie am Ende von app/css/index.css
den folgenden Regelsatz hinzu:
.story {
grid-area: story;
}
Ohne absolute Positionierung, Gleitkommazahlen oder andere Layoutanweisungen, wenn ein Element nicht im Fluss ist, wir sind immer noch im Flow. Außerdem ist es fast wie kein Code, seht euch das an! Dies wird im Video und im Blogpost ausführlicher erläutert.
.story
Jetzt müssen wir nur noch das Story-Element selbst gestalten.
Wir haben bereits erwähnt, dass das style
-Attribut für jedes <article>
-Element Teil einer
Verfahren zum Laden von Platzhaltern:
<article class="story" style="--bg: url(https://picsum.photos/480/840);"></article>
Wir verwenden die CSS-Eigenschaft background-image
, mit der wir
mehrere Hintergrundbilder verwenden. Wir können sie in eine
Reihenfolge bringen, sodass die Nutzenden
wird oben angezeigt und nach Abschluss des Ladevorgangs automatisch angezeigt. Bis
aktivieren wir die Bild-URL in eine benutzerdefinierte Eigenschaft (--bg
) und verwenden diese
in unserem CSS, um mit dem Lade-Platzhalter zu überlagern.
Als Erstes aktualisieren wir den Regelsatz .story
, um einen Farbverlauf durch ein Hintergrundbild zu ersetzen.
sobald der Ladevorgang abgeschlossen ist. Fügen Sie den hervorgehobenen Code zu Ihrem .story
-Regelsatz hinzu:
.story {
grid-area: story;
background-size: cover;
background-image:
var(--bg),
linear-gradient(to top, lch(98 0 0), lch(90 0 0));
}
Wenn Sie background-size
auf cover
festlegen, darf im
da unser Bild ihn füllt. 2 Hintergrund-Images definieren
können wir einen praktischen CSS-Webtrick namens Load Tombstone abrufen:
- Hintergrundbild 1 (
var(--bg)
) ist die URL, die wir inline in den HTML-Code übergeben haben. - Hintergrundbild 2 (
linear-gradient(to top, lch(98 0 0), lch(90 0 0))
ist ein Farbverlauf) die beim Laden der URL angezeigt werden,
CSS ersetzt den Farbverlauf automatisch durch das Bild, sobald das Bild heruntergeladen wurde.
Als Nächstes fügen wir CSS hinzu, um Funktionen zu entfernen, damit der Browser schneller arbeitet.
Fügen Sie den hervorgehobenen Code zu Ihrem .story
-Regelsatz hinzu:
.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
verhindert, dass Nutzer versehentlich Text auswählentouch-action: manipulation
weist den Browser an, dass diese Interaktionen als Touch-Ereignisse behandelt werden, wodurch der Browser zu entscheiden, ob auf eine URL
Zu guter Letzt fügen wir noch ein wenig CSS hinzu, um den Übergang zwischen den Stories zu animieren. Fügen Sie den
hervorgehobenem Code in Ihren .story
-Regelsatz:
.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;
}
}
Der Kurs „.seen
“ wird einer Story hinzugefügt, für die ein Ausgang erforderlich ist.
Ich habe die benutzerdefinierte Easing-Funktion (cubic-bezier(0.4, 0.0, 1,1)
) abgerufen
aus dem Bereich Easing von Material Design
(scrollen Sie zum Abschnitt Beschleunigtes Easing).
Wenn du ein scharfes Auge hast, hast du wahrscheinlich die pointer-events: none
bemerkt
und sich gerade am Kopf kratzen. Ich würde sagen, das ist die einzige
Nachteile der Lösung. Wir benötigen sie, da ein .seen.story
-Element
wird oben angezeigt und erhält beim Tippen auf das Display, obwohl es unsichtbar ist. Wenn Sie die Einstellung
pointer-events
an none
, verwandeln wir die gläserne Geschichte in ein Fenster
mehr Nutzerinteraktionen. Eine gute Kompromisse, aber auch nicht allzu schwer zu verwalten
in unserem Preisvergleichsportal. Wir jonglieren nicht mit z-index
. Mir geht es gut
nicht bewegen.
JavaScript
Die Interaktion der Komponente „Stories“ ist für den Nutzer ganz einfach: Tippe auf das nach rechts, um weiterzugehen, und links, um zurückzugehen. Einfache Dinge für Nutzende neigen dazu, harte Arbeit für Entwickler zu machen. Wir kümmern uns aber darum.
Einrichtung
Rechnen und speichern Sie zunächst
so viele Informationen wie möglich.
Fügen Sie den folgenden Code zu app/js/index.js
hinzu:
const stories = document.querySelector('.stories')
const median = stories.offsetLeft + (stories.clientWidth / 2)
Unsere erste JavaScript-Zeile erfasst und speichert einen Verweis auf unseren primären HTML- Stammelement. Die nächste Zeile berechnet, wo sich die Mitte des Elements befindet. kann entscheiden, ob durch Tippen vor- oder zurückgegangen werden soll.
Status
Als Nächstes erstellen wir ein kleines Objekt mit einem für unsere Logik relevanten Zustand. In dieser
interessieren uns nur für die aktuelle Meldung. In unserem HTML-Markup können wir
Schnapp dir den ersten Freund und seine neueste Geschichte. Markierten Code hinzufügen
auf Ihr app/js/index.js
:
const stories = document.querySelector('.stories')
const median = stories.offsetLeft + (stories.clientWidth / 2)
const state = {
current_story: stories.firstElementChild.lastElementChild
}
Listener
Wir haben jetzt genug Logik, um auf Nutzerereignisse zu warten und sie weiterzuleiten.
Maus
Hören wir uns zuerst das Ereignis 'click'
im Container „Stories“ an.
Fügen Sie den markierten Code zu app/js/index.js
hinzu:
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')
})
Wenn ein Klick erfolgt und es sich nicht um ein <article>
-Element handelt, wird er geschoben und nichts unternehmen.
Bei einem Artikel halten wir die horizontale Position der Maus oder des Fingers
clientX
Wir haben navigateStories
noch nicht implementiert, aber das Argument,
in welche Richtung wir gehen müssen. Wenn diese Nutzerposition
größer als der Medianwert ist, müssen wir next
aufrufen.
prev
(vorherige).
Tastatur
Hören wir jetzt auf das Drücken der Tastatur. Wenn Sie den Abwärtspfeil drücken,
an next
. Wenn es der Aufwärtspfeil ist, rufen wir prev
auf.
Fügen Sie den markierten Code zu app/js/index.js
hinzu:
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')
})
Navigation für Stories
Es ist an der Zeit, die einzigartige Geschäftslogik von Geschichten und der UX, die sie entwickelt wurden, in Angriff zu nehmen. berühmt geworden ist. Das sieht sperrig und knifflig aus, aber ich denke, wird es leicht verständlich sein.
Im Vorfeld befinden sich einige Selektoren, die uns bei der Entscheidung helfen, ob wir zu einer befreundet sind oder eine Geschichte ein-/ausblenden. Da wir im HTML-Code arbeiten, Abfrage nach Anwesenheit von Freunden (Nutzenden) oder Geschichten (Geschichte)
Mithilfe dieser Variablen können wir Fragen wie die „nächsten Artikel“ beantworten. von diesem oder von einem anderen Freund zu einer anderen Geschichte wechseln?“ Dafür habe ich den Baum verwendet von uns entwickelte Strukturen, mit denen wir Eltern und ihre Kinder erreichen.
Fügen Sie am Ende von app/js/index.js
den folgenden Code ein:
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
}
Hier ist unser Ziel der Geschäftslogik, so nah wie möglich an natürlicher Sprache:
- Entscheiden, wie mit dem Tippen umgegangen werden soll
<ph type="x-smartling-placeholder">
- </ph>
- Bei einer nächsten/vorherigen Meldung diese Meldung anzeigen
- Wenn es die letzte/erste Geschichte des Freundes ist: Zeige einem neuen Freund
- Wenn es keine passende Story gibt: nichts unternehmen
- Neue aktuelle Story in
state
aufbewahren
Fügen Sie der Funktion navigateStories
den hervorgehobenen Code hinzu:
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
}
}
}
Jetzt ausprobieren
- Wenn Sie sich eine Vorschau der Website ansehen möchten, klicken Sie auf App ansehen. Drücken Sie dann
Vollbild
Fazit
Damit sind meine Anforderungen an die Komponente abgeschlossen. Bauen Sie auf es mit Daten zu betreiben und im Allgemeinen zu Ihrem eigenen zu machen!