Tab-Komponente erstellen

Eine grundlegende Übersicht zum Erstellen einer Tab-Komponente, die der in iOS- und Android-Apps ähnelt.

In diesem Beitrag möchte ich meine Gedanken zum Erstellen einer Tab-Komponente für das Web teilen, die responsiv ist, mehrere Eingabegeräte unterstützt und plattformübergreifend funktioniert. Demo ansehen

Demo

Wenn du lieber ein Video ansiehst, findest du hier eine YouTube-Version dieses Beitrags:

Übersicht

Tabs sind eine gängige Komponente von Designsystemen, können aber viele Formen annehmen. Zuerst gab es Desktop-Tabs, die auf dem <frame>-Element basierten, und jetzt haben wir flüssige mobile Komponenten, die Inhalte basierend auf physikalischen Eigenschaften animieren. Sie alle versuchen, dasselbe zu tun: Platz sparen.

Heute ist ein Bereich mit Schaltflächen für die Navigation das A und O für die Nutzerfreundlichkeit von Tabs. Mit diesen Schaltflächen lässt sich die Sichtbarkeit von Inhalten in einem Anzeigebereich umschalten. Viele verschiedene Inhaltsbereiche teilen sich denselben Bereich, werden aber je nach der in der Navigation ausgewählten Schaltfläche bedingt angezeigt.

Die Collage ist aufgrund der großen Vielfalt der Stile, die das Web auf das Komponentenkonzept angewendet hat, ziemlich chaotisch.
Eine Collage von Webdesignstilen für Tab-Komponenten aus den letzten 10 Jahren

Web-Taktiken

Insgesamt war das Erstellen dieser Komponente dank einiger wichtiger Funktionen der Webplattform ziemlich einfach:

  • scroll-snap-points für elegante Wisch- und Tastaturinteraktionen mit geeigneten Scrollstopppositionen
  • Deeplinks über URL-Hashes für die browsergestützte Scrollanpassung auf der Seite und Freigabeunterstützung
  • Unterstützung für Screenreader mit <a>- und id="#hash"-Element-Markup
  • prefers-reduced-motion zum Aktivieren von Übergängen mit Weichzeichner und sofortigem Scrollen auf der Seite
  • Die Webfunktion @scroll-timeline im Entwurf zum dynamischen Unterstreichen und Farbwechsel des ausgewählten Tabs

HTML

Im Grunde geht es hier um Folgendes: Sie klicken auf einen Link, die URL stellt den verschachtelten Seitenstatus dar und der Inhaltsbereich wird aktualisiert, wenn der Browser zum übereinstimmenden Element scrollt.

Es gibt einige strukturelle Inhaltselemente: Links und :targets. Wir benötigen eine Liste mit Links, für die sich <nav> eignet, und eine Liste mit <article>-Elementen, für die sich <section> eignet. Jeder Link-Hash entspricht einem Abschnitt, sodass der Browser Inhalte per Ankern scrollen kann.

Auf eine Linkschaltfläche wird geklickt, wodurch die fokussierten Inhalte eingeblendet werden.

Wenn du beispielsweise auf einen Link klickst, wird in Chrome 89 automatisch der :target-Artikel fokussiert, ohne dass JS erforderlich ist. Der Nutzer kann dann wie gewohnt mit seinem Eingabegerät durch den Artikelinhalt scrollen. Es handelt sich um ergänzende Inhalte, wie im Markup angegeben.

Ich habe das folgende Markup verwendet, um die Tabs zu organisieren:

<snap-tabs>
  <header>
    <nav>
      <a></a>
      <a></a>
      <a></a>
      <a></a>
    </nav>
  </header>
  <section>
    <article></article>
    <article></article>
    <article></article>
    <article></article>
  </section>
</snap-tabs>

Ich kann Verbindungen zwischen den <a>- und <article>-Elementen mit href- und id-Properties herstellen, z. B. so:

<snap-tabs>
  <header>
    <nav>
      <a href="#responsive"></a>
      <a href="#accessible"></a>
      <a href="#overscroll"></a>
      <a href="#more"></a>
    </nav>
  </header>
  <section>
    <article id="responsive"></article>
    <article id="accessible"></article>
    <article id="overscroll"></article>
    <article id="more"></article>
  </section>
</snap-tabs>

Als Nächstes füllte ich die Artikel mit unterschiedlichen Mengen an Lorem Ipsum und die Links mit Titeln mit unterschiedlicher Länge und Bildern. Jetzt können wir mit dem Layout beginnen.

Scrolllayouts

In dieser Komponente gibt es drei verschiedene Arten von Scrollbereichen:

  • Die Navigation (rosa) ist horizontal scrollbar
  • Der Inhaltsbereich (blau) ist horizontal scrollbar
  • Jedes Artikelelement (grün) kann vertikal gescrollt werden.
Drei farbige Felder mit farblich passenden Richtungspfeilen, die die Scrollbereiche umreißen und die Scrollrichtung anzeigen.

Beim Scrollen gibt es zwei verschiedene Arten von Elementen:

  1. Fenster
    Ein Feld mit definierten Abmessungen, das den Stil der Eigenschaft overflow hat.
  2. Eine übergroße Oberfläche
    In diesem Layout sind das die Listencontainer: Navigationslinks, Abschnittsartikel und Artikelinhalte.

Layout: <snap-tabs>

Als Layout der obersten Ebene habe ich „flex“ (Flexbox) ausgewählt. Ich habe die Richtung auf column festgelegt, damit die Überschrift und der Abschnitt vertikal angeordnet werden. Dies ist unser erstes Scrollfenster, bei dem alles mit „overflow hidden“ ausgeblendet wird. Die Überschrift und der Abschnitt werden bald als einzelne Zonen mit Overscroll verwendet.

HTML
<snap-tabs>
  <header></header>
  <section></section>
</snap-tabs>
CSS
  snap-tabs {
  display: flex;
  flex-direction: column;

  /* establish primary containing box */
  overflow: hidden;
  position: relative;

  & > section {
    /* be pushy about consuming all space */
    block-size: 100%;
  }

  & > header {
    /* defend against 
needing 100% */ flex-shrink: 0; /* fixes cross browser quarks */ min-block-size: fit-content; } }

Denken Sie an das bunte Diagramm mit drei Scrollebenen:

  • <header> ist jetzt bereit, der (rosa) Scrollcontainer zu sein.
  • <section> ist für den (blauen) Scrollcontainer vorgesehen.

Die Frames, die ich unten mit VisBug hervorgehoben habe, helfen uns, die Fenster zu sehen, die die Scrollcontainer erstellt haben.

Die Überschriften und Abschnittselemente haben Hotpink-Überlagerungen, die den in der Komponente eingenommenen Platz umreißen.

<header>-Layout für Tabs

Das nächste Layout ist fast identisch: Ich verwende Flex, um eine vertikale Anordnung zu erstellen.

HTML
<snap-tabs>
  <header>
    <nav></nav>
    <span class="snap-indicator"></span>
  </header>
  <section></section>
</snap-tabs>
CSS
header {
  display: flex;
  flex-direction: column;
}

Das .snap-indicator sollte horizontal mit der Gruppe von Links wandern. Dieses Kopfzeilenlayout trägt dazu bei, diese Bühne zu schaffen. Hier gibt es keine absolut positionierten Elemente.

Die Elemente „nav“ und „span.indicator“ haben Hotpink-Überlagerungen, die den in der Komponente belegten Bereich umreißen.

Als Nächstes die Scroll-Stile. Die Scroll-Stile können wir für unsere beiden horizontalen Scrollbereiche (Header und Abschnitt) verwenden. Deshalb habe ich die Dienstprogrammklasse .scroll-snap-x erstellt.

.scroll-snap-x {
  /* browser decide if x is ok to scroll and show bars on, y hidden */
  overflow: auto hidden;
  /* prevent scroll chaining on x scroll */
  overscroll-behavior-x: contain;
  /* scrolling should snap children on x */
  scroll-snap-type: x mandatory;

  @media (hover: none) {
    scrollbar-width: none;

    &::-webkit-scrollbar {
      width: 0;
      height: 0;
    }
  }
}

Für beide ist ein Überlauf auf der X-Achse, eine Scroll-Begrenzung zum Einfangen von Überscrolls, ausgeblendete Bildlaufleisten für Touchgeräte und schließlich ein Scroll-Snap zum Sperren von Inhaltsbereichen erforderlich. Die Tab-Reihenfolge der Tastatur ist barrierefrei und alle Interaktionen lenken den Fokus auf natürliche Weise. Scroll-Snap-Container bieten außerdem eine schöne Karussell-Interaktion über die Tastatur.

Layout der Tab-Überschrift <nav>

Die Navigationslinks müssen in einer Zeile ohne Zeilenumbrüche vertikal zentriert angeordnet sein. Jedes Linkelement sollte an den Scroll-Snap-Container angedockt werden. Gute Arbeit für das CSS 2021!

HTML
<nav>
  <a></a>
  <a></a>
  <a></a>
  <a></a>
</nav>
CSS
  nav {
  display: flex;

  & a {
    scroll-snap-align: start;

    display: inline-flex;
    align-items: center;
    white-space: nowrap;
  }
}

Jeder Link wird automatisch formatiert und skaliert, sodass im Navigationslayout nur die Richtung und der Fluss angegeben werden müssen. Die unterschiedlichen Breiten der Navigationselemente machen den Wechsel zwischen Tabs unterhaltsam, da der Indikator seine Breite an das neue Ziel anpasst. Je nachdem, wie viele Elemente hier vorhanden sind, rendert der Browser eine Bildlaufleiste oder nicht.

Die A-Elemente der Navigationsleiste haben Hotpink-Überlagerungen, die den Platz in der Komponente und die Überlaufbereiche anzeigen.

<section>-Layout für Tabs

Dieser Abschnitt ist ein Flex-Element und muss den größten Teil des Bereichs einnehmen. Außerdem müssen Spalten für die Artikel erstellt werden. Ich wünsche euch viel Erfolg bei CSS 2021! Das block-size: 100% dehnt dieses Element, um das übergeordnete Element so weit wie möglich auszufüllen, und erstellt dann für sein eigenes Layout eine Reihe von Spalten, die 100% der Breite des übergeordneten Elements entsprechen. Prozentsätze eignen sich hier hervorragend, da wir für das übergeordnete Element strenge Einschränkungen festgelegt haben.

HTML
<section>
  <article></article>
  <article></article>
  <article></article>
  <article></article>
</section>
CSS
  section {
  block-size: 100%;

  display: grid;
  grid-auto-flow: column;
  grid-auto-columns: 100%;
}

Es ist, als würden wir sagen: „Erweitern Sie sich vertikal so weit wie möglich, aufdringlich.“ (Denken Sie an die Überschrift, die wir auf flex-shrink: 0 gesetzt haben: Sie ist ein Schutz vor dieser Erweiterung.) Dadurch wird die Zeilenhöhe für eine Reihe von Spalten mit voller Höhe festgelegt. Der Stil auto-flow weist das Raster an, untergeordnete Elemente immer in einer horizontalen Linie anzuordnen, ohne Umbruch. Genau das ist hier gewünscht, damit das übergeordnete Fenster überläuft.

Die Artikelelemente haben Hotpink-Überlagerungen, die den Platz in der Komponente und den Überlaufbereich anzeigen

Manchmal fällt es mir schwer, das zu verstehen. Dieses Abschnittselement passt in ein Feld, hat aber auch eine Reihe von Feldern erstellt. Ich hoffe, die Visualisierungen und Erklärungen helfen Ihnen weiter.

<article>-Layout für Tabs

Der Nutzer sollte den Artikelinhalt scrollen können und die Scrollleisten sollten nur angezeigt werden, wenn der Inhalt nicht vollständig sichtbar ist. Diese Artikelelemente sind gut positioniert. Sie sind gleichzeitig ein übergeordnetes und ein untergeordnetes Element für die Bildlaufsteuerung. Der Browser verarbeitet hier einige schwierige Touch-, Maus- und Tastaturinteraktionen für uns.

HTML
<article>
  <h2></h2>
  <p></p>
  <p></p>
  <h2></h2>
  <p></p>
  <p></p>
  ...
</article>
CSS
article {
  scroll-snap-align: start;

  overflow-y: auto;
  overscroll-behavior-y: contain;
}

Ich habe dafür gesorgt, dass die Artikel im übergeordneten Scroller anspringen. Mir gefällt, wie die Navigationslink-Elemente und die Artikelelemente an den Inline-Anfang ihrer jeweiligen Scrollcontainer angedockt sind. Es sieht nach einer harmonischen Beziehung aus.

Das Artikelelement und seine untergeordneten Elemente haben Hotpink-Überlagerungen, die den in der Komponente belegten Bereich und die Richtung des Überlaufs umreißen.

Der Artikel ist ein untergeordnetes Element des Rasters und seine Größe ist auf den Bereich des Darstellungsbereichs festgelegt, für den wir die Scroll-UX bereitstellen möchten. Das bedeutet, dass ich hier keine Stile für Höhe oder Breite benötige, sondern nur definieren muss, wie der Text überläuft. Ich habe „overflow-y“ auf „auto“ gesetzt und dann auch die Scroll-Interaktionen mit der praktischen Eigenschaft „overscroll-behavior“ erfasst.

3 Scrollbereiche – Zusammenfassung

Unten habe ich in den Systemeinstellungen die Option „Bildlaufleisten immer anzeigen“ ausgewählt. Ich finde es doppelt wichtig, dass das Layout mit dieser Einstellung funktioniert, da ich das Layout und die Scroll-Orchestrierung überprüfen muss.

Die drei Bildlaufleisten sind jetzt sichtbar und belegen Layoutfläche. Unsere Komponente sieht trotzdem gut aus.

Ich denke, dass die Scrollleiste in dieser Komponente deutlich zeigt, wo sich die Scrollbereiche befinden, welche Richtung sie unterstützen und wie sie miteinander interagieren. Beachten Sie, dass jeder dieser Scrollfenster-Frames auch Flex- oder Grid-Elternelemente eines Layouts sind.

Mit den DevTools können wir Folgendes visualisieren:

Die Scrollbereiche haben Raster- und Flexbox-Tool-Overlays, die den in der Komponente belegten Bereich und die Richtung des Überlaufs umreißen
Chromium Devtools mit dem Layout des Flexbox-Navigationselements mit Ankerelementen, dem Layout des Rasterbereichs mit Artikelelementen und den Artikelelementen mit Absätzen und einem Überschriftenelement.

Die Scroll-Layouts sind vollständig: Anpassen, Deeplink-fähig und per Tastatur zugänglich. Solide Grundlage für UX-Optimierungen, Stil und Nutzerfreundlichkeit.

Funktionshighlight

Kinderelemente, die beim Scrollen an einer bestimmten Position fixiert sind, behalten beim Ändern der Größe ihre Position bei. Das bedeutet, dass JavaScript beim Drehen des Geräts oder Ändern der Browsergröße nichts in den Blick bringen muss. Probieren Sie es im Gerätemodus der Chromium-Entwicklertools aus. Wählen Sie dazu einen anderen Modus als Responsive aus und ändern Sie dann die Größe des Geräteframes. Das Element bleibt im Blickfeld und ist mit seinem Inhalt verbunden. Diese Funktion ist verfügbar, seit Chromium seine Implementierung an die Spezifikation angepasst hat. Hier finden Sie einen Blogpost dazu.

Animation

Ziel der Animation ist es, Interaktionen klar mit UI-Feedback zu verknüpfen. So können Nutzer alle Inhalte (hoffentlich) nahtlos entdecken. Ich werde Bewegungen mit Bedacht und bedingt hinzufügen. Nutzer können jetzt in ihrem Betriebssystem ihre Bewegungseinstellungen festlegen. Ich finde es sehr spannend, auf ihre Einstellungen in meinen Benutzeroberflächen zu reagieren.

Ich verknüpfe einen Tab-Unterstrich mit der Scrollposition des Artikels. Anpassen ist nicht nur eine schöne Ausrichtung, sondern auch eine Verankerung des Anfangs und Endes einer Animation. So bleibt die <nav>, die als Minikarte dient, mit den Inhalten verbunden. Wir prüfen die Bewegungseinstellung des Nutzers sowohl in CSS als auch in JS. Es gibt viele Möglichkeiten, Rücksicht zu nehmen.

Scrollverhalten

Sie haben die Möglichkeit, das Bewegungsverhalten von :target und element.scrollIntoView() zu verbessern. Standardmäßig ist die Auslieferung sofort. Der Browser legt nur die Scrollposition fest. Was ist, wenn wir zu dieser Scrollposition wechseln möchten, anstatt dort zu blinken?

@media (prefers-reduced-motion: no-preference) {
  .scroll-snap-x {
    scroll-behavior: smooth;
  }
}

Da wir hier Bewegungen einführen, die der Nutzer nicht steuert (z. B. Scrollen), wird dieser Stil nur angewendet, wenn der Nutzer in seinem Betriebssystem keine Einstellungen für reduzierte Bewegungen festgelegt hat. So wird das Scrollen nur für Nutzer aktiviert, die damit einverstanden sind.

Tab-Anzeige

Der Zweck dieser Animation besteht darin, den Indikator mit dem Status der Inhalte zu verknüpfen. Ich habe mich für Farb-Crossfade-border-bottom-Stile für Nutzer entschieden, die weniger Bewegung bevorzugen, und für eine Scroll-synchronisierte Schiebe- und Farbüberblendungsanimation für Nutzer, die Bewegung mögen.

In den Chromium-Entwicklertools kann ich die Einstellung umschalten und die beiden verschiedenen Übergangsstile demonstrieren. Ich hatte viel Spaß beim Erstellen dieses Modells.

@media (prefers-reduced-motion: reduce) {
  snap-tabs > header a {
    border-block-end: var(--indicator-size) solid hsl(var(--accent) / 0%);
    transition: color .7s ease, border-color .5s ease;

    &:is(:target,:active,[active]) {
      color: var(--text-active-color);
      border-block-end-color: hsl(var(--accent));
    }
  }

  snap-tabs .snap-indicator {
    visibility: hidden;
  }
}

Ich blende das .snap-indicator aus, wenn der Nutzer weniger Bewegung bevorzugt, da ich es dann nicht mehr benötige. Dann ersetze ich sie durch border-block-end-Stile und einen transition. Beachten Sie auch, dass das aktive Navigationselement bei der Tab-Interaktion nicht nur durch eine unterstrichene Markenfarbe hervorgehoben wird, sondern auch durch eine dunklere Textfarbe. Das aktive Element hat einen höheren Textfarbkontrast und einen hellen Akzent durch Unterbeleuchtung.

Mit nur wenigen zusätzlichen Zeilen CSS können Sie Nutzer das Gefühl geben, dass ihre Präferenzen für Bewegungen berücksichtigt werden. Ich liebe das.

@scroll-timeline

Im vorherigen Abschnitt habe ich gezeigt, wie ich mit den Crossfade-Stilen mit reduzierter Bewegung umgehe. In diesem Abschnitt zeige ich Ihnen, wie ich den Indikator und einen Scrollbereich miteinander verknüpft habe. Als Nächstes kommen einige lustige experimentelle Funktionen. Ich hoffe, du bist genauso gespannt wie ich.

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

Ich prüfe zuerst die Bewegungseinstellungen des Nutzers in JavaScript. Wenn das Ergebnis false ist, also der Nutzer weniger Bewegung bevorzugt, werden keine Bewegungseffekte für die Scroll-Verknüpfung ausgeführt.

if (motionOK) {
  // motion based animation code
}

Zum Zeitpunkt der Erstellung dieses Artikels gibt es keine Browserunterstützung für @scroll-timeline. Es handelt sich um eine Entwurfsspezifikation mit nur experimentellen Implementierungen. Es gibt jedoch eine Polyfill-Version, die ich in dieser Demo verwende.

ScrollTimeline

Mit CSS und JavaScript lassen sich Scroll-Zeitleisten erstellen. Ich habe mich für JavaScript entschieden, damit ich in der Animation Live-Elementmessungen verwenden konnte.

const sectionScrollTimeline = new ScrollTimeline({
  scrollSource: tabsection,  // snap-tabs > section
  orientation: 'inline',     // scroll in the direction letters flow
  fill: 'both',              // bi-directional linking
});

Ich möchte, dass ein Element der Scrollposition eines anderen Elements folgt. Mit einem ScrollTimeline definiere ich den Auslöser des Scroll-Links, den scrollSource. Normalerweise wird eine Animation im Web anhand eines globalen Zeitrahmens ausgeführt. Mit einer benutzerdefinierten sectionScrollTimeline im Arbeitsspeicher kann ich das jedoch ändern.

tabindicator.animate({
    transform: ...,
    width: ...,
  }, {
    duration: 1000,
    fill: 'both',
    timeline: sectionScrollTimeline,
  }
);

Bevor ich auf die Keyframes der Animation eingehe, möchte ich darauf hinweisen, dass der Follower des Scrollens, tabindicator, basierend auf einer benutzerdefinierten Zeitachse animiert wird, dem Scrollen unseres Abschnitts. Damit ist die Verknüpfung abgeschlossen, aber es fehlt noch die letzte Zutat: zustandsabhängige Punkte, zwischen denen animiert werden soll, auch als ‑frames bezeichnet.

Dynamische Keyframes

Es gibt eine wirklich leistungsstarke, rein deklarative CSS-Methode, mit der man mit @scroll-timeline animieren kann, aber die von mir gewählte Animation war zu dynamisch. Es gibt keine Möglichkeit, zwischen auto-Breiten zu wechseln, und es ist auch nicht möglich, dynamisch eine Anzahl von Keyframes basierend auf der Länge der untergeordneten Elemente zu erstellen.

JavaScript weiß jedoch, wie diese Informationen abgerufen werden. Wir iterieren also selbst über die untergeordneten Elemente und rufen die berechneten Werte zur Laufzeit ab:

tabindicator.animate({
    transform: [...tabnavitems].map(({offsetLeft}) =>
      `translateX(${offsetLeft}px)`),
    width: [...tabnavitems].map(({offsetWidth}) =>
      `${offsetWidth}px`)
  }, {
    duration: 1000,
    fill: 'both',
    timeline: sectionScrollTimeline,
  }
);

Für jede tabnavitem-Instanz wird die offsetLeft-Position destrukturiert und ein String zurückgegeben, der sie als translateX-Wert verwendet. Dadurch werden vier Transformations-Keyframes für die Animation erstellt. Dasselbe gilt für die Breite. Bei jedem Frame wird die dynamische Breite abgefragt und dann als Keyframe-Wert verwendet.

Hier ist eine Beispielausgabe, die auf meinen Schriftarten und Browsereinstellungen basiert:

Keyframes für „TranslateX“:

[...tabnavitems].map(({offsetLeft}) =>
  `translateX(${offsetLeft}px)`)

// results in 4 array items, which represent 4 keyframe states
// ["translateX(0px)", "translateX(121px)", "translateX(238px)", "translateX(464px)"]

Keyframes für die Breite:

[...tabnavitems].map(({offsetWidth}) =>
  `${offsetWidth}px`)

// results in 4 array items, which represent 4 keyframe states
// ["121px", "117px", "226px", "67px"]

Zusammenfassend lässt sich sagen, dass sich der Tab-Indikator jetzt je nach Scroll-Snap-Position des Abschnitts-Scrollers über vier Frames animiert. Die Snap-Punkte sorgen für eine klare Abgrenzung zwischen den Keyframes und tragen wesentlich zum synchronisierten Erscheinungsbild der Animation bei.

Aktiver und inaktiver Tab werden mit VisBug-Overlays angezeigt, die für beide einen bestandenen Kontrastwert anzeigen

Der Nutzer steuert die Animation durch seine Interaktion. Dabei sieht er, wie sich die Breite und Position des Indikators von Abschnitt zu Abschnitt ändert und perfekt mit dem Scrollen übereinstimmt.

Möglicherweise ist es Ihnen nicht aufgefallen, aber ich bin sehr stolz auf den Farbübergang, wenn das hervorgehobene Navigationselement ausgewählt wird.

Nicht ausgewählte Elemente erscheinen noch weiter im Hintergrund, wenn das hervorgehobene Element einen höheren Kontrast hat. Es ist üblich, die Farbe für Text zu ändern, z. B. beim Hovering und bei der Auswahl. Noch besser ist es jedoch, die Farbe beim Scrollen zu ändern, synchronisiert mit dem Unterstrich-Indikator.

So habe ich es gemacht:

tabnavitems.forEach(navitem => {
  navitem.animate({
      color: [...tabnavitems].map(item =>
        item === navitem
          ? `var(--text-active-color)`
          : `var(--text-color)`)
    }, {
      duration: 1000,
      fill: 'both',
      timeline: sectionScrollTimeline,
    }
  );
});

Für jeden Tab-Navigationslink ist diese neue Farbanimation erforderlich, die derselben Scroll-Zeitachse wie der Unterstrich-Indikator folgt. Ich verwende dieselbe Zeitleiste wie zuvor: Da es seine Aufgabe ist, beim Scrollen ein Häkchen auszugeben, können wir dieses Häkchen in jeder Art von Animation verwenden. Wie zuvor erstelle ich vier Frames in der Schleife und gebe Farben zurück.

[...tabnavitems].map(item =>
  item === navitem
    ? `var(--text-active-color)`
    : `var(--text-color)`)

// results in 4 array items, which represent 4 keyframe states
// [
  "var(--text-active-color)",
  "var(--text-color)",
  "var(--text-color)",
  "var(--text-color)",
]

Der ‑Frame mit der Farbe var(--text-active-color) hebt den Link hervor. Andernfalls wird eine Standardtextfarbe verwendet. Die verschachtelte Schleife macht es relativ einfach, da die äußere Schleife jedes Navigationselement und die innere Schleife die persönlichen Frames jedes Navigationselements ist. Ich prüfe, ob das Element der äußeren Schleife mit dem Element der inneren Schleife übereinstimmt, und anhand dieser Information weiß ich, wann es ausgewählt ist.

Das Schreiben hat mir viel Spaß gemacht. Sehr.

Weitere JavaScript-Optimierungen

Zur Erinnerung: Der Großteil dessen, was ich hier zeige, funktioniert ohne JavaScript. Sehen wir uns an, wie wir das verbessern können, wenn JS verfügbar ist.

Deeplinks sind eher ein Begriff aus der mobilen Welt, aber ich denke, dass der Zweck eines Deeplinks hier mit Tabs erfüllt wird, da Sie eine URL direkt zum Inhalt eines Tabs teilen können. Der Browser wechselt auf der Seite zur ID, die im URL-Hash übereinstimmt. Ich habe festgestellt, dass dieser onload-Handler den Effekt plattformübergreifend ermöglicht.

window.onload = () => {
  if (location.hash) {
    tabsection.scrollLeft = document
      .querySelector(location.hash)
      .offsetLeft;
  }
}

Synchronisierung des Scrollendes

Unsere Nutzer klicken nicht immer oder verwenden nicht immer eine Tastatur. Manchmal scrollen sie einfach kostenlos, wie es sein sollte. Wenn der Abschnitts-Scroller anhält, muss die Position in der oberen Navigationsleiste übereinstimmen.

So warte ich auf das Ende des Scrollens: js tabsection.addEventListener('scroll', () => { clearTimeout(tabsection.scrollEndTimer); tabsection.scrollEndTimer = setTimeout(determineActiveTabSection, 100); });

Wenn die Abschnitte gescrollt werden, löschen Sie gegebenenfalls das Zeitlimit für den Abschnitt und starten Sie ein neues. Wenn die Abschnitte nicht mehr gescrollt werden, löschen Sie das Zeitlimit nicht und starten Sie den Timer nach 100 Millisekunden wieder. Wenn er ausgelöst wird, wird eine Funktion aufgerufen, die herausfinden soll, wo der Nutzer angehalten hat.

const determineActiveTabSection = () => {
  const i = tabsection.scrollLeft / tabsection.clientWidth;
  const matchingNavItem = tabnavitems[i];

  matchingNavItem && setActiveTab(matchingNavItem);
};

Wenn das Scrollen angesprungen ist, sollte die Division der aktuellen Scrollposition durch die Breite des Scrollbereichs eine Ganzzahl und keine Dezimalzahl ergeben. Ich versuche dann, über diesen berechneten Index ein Navitem aus unserem Cache abzurufen. Wenn etwas gefunden wird, sende ich die Übereinstimmung, um sie als aktiv zu setzen.

const setActiveTab = tabbtn => {
  tabnav
    .querySelector(':scope a[active]')
    .removeAttribute('active');

  tabbtn.setAttribute('active', '');
  tabbtn.scrollIntoView();
};

Um den aktiven Tab festzulegen, müssen Sie zuerst den derzeit aktiven Tab löschen und dann dem eingehenden Navigationselement das Attribut „active“ zuweisen. Der Aufruf von scrollIntoView() hat eine interessante Interaktion mit CSS, die erwähnenswert ist.

.scroll-snap-x {
  overflow: auto hidden;
  overscroll-behavior-x: contain;
  scroll-snap-type: x mandatory;

  @media (prefers-reduced-motion: no-preference) {
    scroll-behavior: smooth;
  }
}

Im CSS-Code für das horizontale Scroll-Snap-Dienstprogramm haben wir eine verschachtelte Medienabfrage eingefügt, die das Scrollen mit smooth anwendet, wenn der Nutzer Bewegungen toleriert. JavaScript kann beliebig Aufrufe zum Scrollen von Elementen in den Blickbereich ausführen und CSS kann die UX deklarativ verwalten. Manchmal sind sie ein ganz reizendes Paar.

Fazit

Jetzt, da Sie wissen, wie ich das gemacht habe, wie würden Sie es machen? Das macht die Komponentenarchitektur interessant. Wer erstellt die erste Version mit Slots in seinem bevorzugten Framework? 🙂

Lassen Sie uns unsere Ansätze diversifizieren und alle Möglichkeiten kennenlernen, wie Sie im Web entwickeln können. Erstelle einen Glitch, tweete mir deine Version und ich füge sie dem Abschnitt Community-Remixe unten hinzu.

Remixe der Community