Codelab: Stories-Komponente erstellen

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

  1. Klicke auf Zum Bearbeiten Remix, damit das Projekt bearbeitet werden kann.
  2. Ö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
Die Chrome-Entwicklertools zeigen einen Überlauf von Rasterspalten an, was zu einem horizontalen Bildlauf führt.

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ählen
  • touch-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 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!