Tab-Komponente erstellen

Eine grundlegende Übersicht zum Erstellen einer Tab-Komponente, die denen 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. Alle haben dasselbe Ziel: Platz zu sparen.

Das essenzielle Element einer Tabs-Nutzererfahrung ist heute ein Schaltflächennavigationsbereich, mit dem die Sichtbarkeit von Inhalten in einem Anzeigeframe ein- und ausgeschaltet wird. 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 Scrollstopps
  • 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
  • Das Webfeature @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 stimmt mit einem Abschnitt überein, sodass der Browser durch Anker 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 scrollen.
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. Für den Header und den Abschnitt wird bald Overscroll als einzelne Zone 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; } }

Zurück zum bunten Diagramm mit 3 Scrollen:

  • <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;
}

.snap-indicator sollte sich horizontal mit der Gruppe von Links bewegen. Dieses Kopfzeilenlayout dient dazu, den Grundstein dafür zu legen. Hier gibt es keine absolut positionierten Elemente.

Die Elemente „nav“ und „span.indicator“ haben Hotpink-Überlagerungen, die den in der Komponente eingenommenen Platz 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 Überscroll-Ereignissen, 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. Swift funktioniert für 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 Richtung und Ablauf angegeben werden müssen. Durch die eindeutige Breite von Navigationselementen macht der Übergang zwischen Tabs Spaß, da die Breite des Indikators an das neue Ziel angepasst wird. Je nachdem, wie viele Elemente hier vorhanden sind, rendert der Browser eine Bildlaufleiste oder nicht.

Die A-Elemente der Navigationsleiste haben Hotpink-Overlays, die den Platz in der Komponente und die Überlaufbereiche umreißen.

<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 erstellt werden, in denen die Artikel platziert werden sollen. Auch hier gilt: Schnelle Arbeit für 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-Overlays, die den Platz in der Komponente darstellen und darüber, wo sie überlaufen

Manchmal fällt es mir schwer, das zu verstehen. Dieses Abschnittselement passt in einen Rahmen, hat aber auch eine Reihe von Rahmen 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 Element für das Scrollen und ein untergeordnetes Element vom Typ „Scrollen“. 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 eingerastet sind. Besonders gut gefällt mir, wie die Navigationslinkelemente und die Artikelelemente am Inline-Start ihrer jeweiligen Scroll-Container ausgerichtet werden. Es sieht aus wie eine harmonische Beziehung.

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 setze „overflow-y“ auf „auto“ und übertrage die Scroll-Interaktionen mit der Eigenschaft „Overscroll-Verhalten“.

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. Denken Sie daran, dass jeder dieser Scroll-Window-Frames auch flexible oder rasterbasierte übergeordnete Elemente eines Layouts ist.

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-Entwicklertools mit dem Flexbox-Navigationselement-Layout voller Ankerelemente, dem Rasterabschnittslayout voller Artikelelemente und den Artikelelementen voller Absätze 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

Beim Scrollen fixierte untergeordnete Elemente behalten ihre Sperrposition während der Größenänderung. Das bedeutet, dass JavaScript beim Drehen des Geräts oder Ändern der Browsergröße nichts in den Blick bringen muss. Du kannst die Funktion im Gerätemodus der Chromium-Entwicklertools ausprobieren. Wähle dazu einen anderen Modus als Responsiv aus und passe dann die Größe des Geräterahmens an. 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 werde einen Tab-Unterstrich mit der Scrollposition des Artikels verknüpfen. Beim Andocken geht es nicht nur darum, sie auszurichten, sondern auch den Anfang und das Ende einer Animation. So bleibt die <nav>, die als Minikarte fungiert, mit den Inhalten verbunden. Wir überprüfen die Bewegungseinstellungen des Nutzers sowohl in CSS als auch in JS. Es gibt viele Möglichkeiten, Rücksicht zu nehmen.

Scrollverhalten

Es besteht 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überblendungen border-bottom 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 Chromium-Entwicklertools kann ich die Einstellung ändern 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 ihn durch border-block-end-Stile und ein transition-Element. Beachten Sie auch, dass das aktive Navigationselement bei der Interaktion mit den Tabs 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 geht es um experimentelle Funktionen. Ich hoffe, du freust dich genauso wie ich.

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

Ich prüfe zuerst die Bewegungseinstellungen des Nutzers in JavaScript. Lautet das Ergebnis false, was bedeutet, dass der Nutzer reduzierte Bewegungen bevorzugt, werden keine Bewegungseffekte zum Scrollen 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

Zwar können sowohl mit CSS als auch mit JavaScript Scroll-Zeitachsen erstellt werden, aber ich habe mich für JavaScript entschieden, um Messwerte für Live-Elemente in der Animation zu verwenden.

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. Durch das Erstellen eines 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, anhand einer benutzerdefinierten Zeitachse animiert wird, dem Scrollen unseres Abschnitts. Damit ist die Verknüpfung abgeschlossen, es fehlen jedoch die endgültigen zustandsorientierten Punkte, zwischen denen animiert werden kann (auch Keyframes genannt).

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. Deshalb iterieren wir 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 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.

Vielleicht haben Sie es noch nicht bemerkt, 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. Üblicherweise wird eine Übergangsfarbe für Text verwendet, z. B. wenn der Mauszeiger darauf bewegt wird oder wenn der Text ausgewählt wird. Auf der nächsten Ebene wird diese Farbe beim Scrollen mit der Unterstrich-Anzeige synchronisiert.

Gehen Sie dazu folgendermaßen vor:

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 Navigationslink in einem Tab ist diese neue Farbanimation erforderlich. Dabei gilt dieselbe Scroll-Zeitachse wie der Unterstrich. Ich verwende dieselbe Zeitachse wie zuvor: Da beim Scrollen ein Tick ausgegeben wird, können wir diesen Tick in jeder beliebigen Animationsart 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 macht.

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

Synchronisierung des Scrollendes

Unsere Nutzer klicken oder verwenden nicht immer die Tastatur, manchmal nutzen sie einfach nur kostenloses Scrollen. Wenn der Abschnitts-Scroller anhält, muss die Position in der oberen Navigationsleiste übereinstimmen.

So warte ich auf das Scroll-Ende: 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 das Scrollen zu Abschnitten beendet wird, löschen Sie das Zeitlimit nicht und lösen Sie nach einer Pause 100 ms aus. Wenn sie ausgelöst wird, rufen Sie eine Funktion auf, die versucht, herauszufinden, wo der Nutzer aufgehört 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() führt zu einer unterhaltsamen Interaktion mit CSS, die Sie beachten sollten.

.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-Dienstprogramm für das Snap-Tool zum horizontalen Scrollen haben wir eine Medienabfrage verschachtelt, die das smooth-Scrollen anwendet, wenn der Nutzer bewegungstolerant ist. JavaScript kann beliebig Aufrufe zum Scrollen von Elementen in den Blickbereich ausführen und CSS kann die UX deklarativ verwalten. Ziemlich die entzückende kleine Betreuung, die sie manchmal machen.

Fazit

Jetzt, da du weißt, wie ich das gemacht habe, wie würdest du es tun?! Das macht die Komponentenarchitektur interessant. Wer erstellt die erste Version mit Slots in seinem bevorzugten Framework? 🙂

Lassen Sie uns unsere Herangehensweisen diversifizieren und alle Möglichkeiten kennenlernen, wie wir das 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