Tab-Komponente erstellen

Eine grundlegende Übersicht über das Erstellen einer Tab-Komponente, die den Komponenten in iOS- und Android-Apps ähnelt.

In diesem Beitrag möchte ich meine Überlegungen zum Erstellen einer Tabs-Komponente für das Web teilen, die responsiv ist, mehrere Geräteeingaben unterstützt und browserübergreifend funktioniert. Demo ausprobieren

Demo

Wenn Sie sich lieber ein Video ansehen, finden Sie 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 mobile Komponenten, die Inhalte basierend auf physikalischen Eigenschaften animieren. Sie alle versuchen, dasselbe zu tun: Platz zu sparen.

Heute besteht die Grundlage einer Tab-Nutzererfahrung aus einem Navigationsbereich mit Schaltflächen, über den die Sichtbarkeit von Inhalten in einem Anzeigebereich ein- und ausgeschaltet wird. Viele verschiedene Inhaltsbereiche teilen sich denselben Platz, werden aber je nach der in der Navigation ausgewählten Schaltfläche bedingt dargestellt.

Die Collage ist ziemlich chaotisch, da im Web eine Vielzahl von Stilen auf das Komponentenkonzept angewendet wurde.
Eine Collage von Webdesign-Stilen für Tab-Komponenten aus den letzten 10 Jahren

Webtaktiken

Alles in allem war diese Komponente dank einiger wichtiger Webplattformfunktionen ziemlich einfach zu erstellen:

  • scroll-snap-points für elegante Wisch- und Tastaturinteraktionen mit geeigneten Scroll-Stopp-Positionen
  • Deeplinks über URL-Hashes für die Unterstützung von browsergesteuertem Scroll-Anchoring und In-Page-Teilen
  • Screenreader-Unterstützung mit <a>- und id="#hash"-Element-Markup
  • prefers-reduced-motion, um Überblendungen und sofortiges In-Page-Scrollen zu aktivieren
  • Die Webfunktion @scroll-timeline im Entwurf zum dynamischen Unterstreichen und Ändern der Farbe des ausgewählten Tabs

Das HTML

Im Grunde sieht die UX hier so aus: Ein Link wird angeklickt, die URL stellt den verschachtelten Seitenstatus dar und der Inhaltsbereich wird aktualisiert, während der Browser zum entsprechenden Element scrollt.

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

Ein Link-Button wird angeklickt und der fokussierte Inhalt wird eingeblendet.

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

Ich habe die Tabs mit dem folgenden Markup organisiert:

<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 Elementen <a> und <article> mit den Attributen href und id so herstellen:

<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 habe ich die Artikel mit unterschiedlichen Mengen an Lorem-Ipsum-Text und die Links mit einer unterschiedlichen Länge und einer Reihe von Bildtiteln gefüllt. Da wir jetzt Inhalte haben, können wir mit dem Layout beginnen.

Scrollbare Layouts

Diese Komponente enthält drei verschiedene Arten von Scrollbereichen:

  • Die Navigation (pink) ist horizontal scrollbar.
  • Der Inhaltsbereich (blau) ist horizontal scrollbar.
  • Jeder Artikel (grün) kann vertikal gescrollt werden.
3 farbige Felder mit farblich passenden Richtungspfeilen, die die Scrollbereiche und die Scrollrichtung zeigen.

Beim Scrollen gibt es zwei verschiedene Arten von Elementen:

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

Layout: <snap-tabs>

Das von mir gewählte Layout auf oberster Ebene war „flex“ (Flexbox). Ich habe die Richtung auf column festgelegt, sodass der Header und der Abschnitt vertikal angeordnet sind. Dies ist unser erstes Scrollfenster. Es verbirgt alles mit „overflow: hidden“. Der Header und der Abschnitt werden bald Overscroll als einzelne Zonen verwenden.

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

Zeigt auf das bunte Diagramm mit den drei Schriftrollen:

  • <header> ist jetzt bereit, der (pinke) Scrollcontainer zu sein.
  • <section> ist bereit, der (blaue) Scrollcontainer zu werden.

Die unten mit VisBug hervorgehobenen Frames zeigen die Fenster, die von den Scrollcontainern erstellt wurden.

Die Header- und Abschnittselemente haben pinkfarbene Overlays, die den Platz umreißen, den sie in der Komponente einnehmen.

Layout „Tabs <header>“

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

Die .snap-indicator sollte sich horizontal mit der Gruppe von Links bewegen. Dieses Header-Layout hilft dabei, diese Bühne zu schaffen. Hier gibt es keine absolut positionierten Elemente.

Die Elemente „nav“ und „span.indicator“ haben Hotpink-Overlays, die den Platz umreißen, den sie in der Komponente einnehmen.

Als Nächstes die Scrollstile. Es stellt sich heraus, dass wir die Scrollstile zwischen unseren beiden horizontalen Scrollbereichen (Header und Abschnitt) teilen können. Deshalb habe ich eine Utility-Klasse .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 jedes Element ist ein Überlauf auf der x-Achse, ein Scroll-Containment zum Abfangen von Overscroll, ausgeblendete Scrollleisten für Touchgeräte und schließlich Scroll-Snap zum Sperren von Inhaltspräsentationsbereichen erforderlich. Die Tabulatorreihenfolge unserer Tastatur ist barrierefrei und alle Interaktionen führen den Fokus auf natürliche Weise. Scroll-Snap-Container erhalten außerdem eine schöne Karussell-Interaktion über die Tastatur.

<nav>-Layout für Tab-Überschriften

Die Navigationslinks müssen in einer Zeile ohne Zeilenumbrüche angeordnet sein, vertikal zentriert, und jedes Linkelement sollte am Scroll-Snap-Container einrasten. Swift work for 2021 CSS!

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 hat einen eigenen Stil und eine eigene Größe, sodass im Navigationslayout nur Richtung und Fluss angegeben werden müssen. Die unterschiedlichen Breiten der Navigationselemente machen den Übergang zwischen den Tabs interessant, da sich die Breite des Indikators an das neue Ziel anpasst. Je nachdem, wie viele Elemente sich darin befinden, wird im Browser eine Bildlaufleiste gerendert oder nicht.

Die a-Elemente des nav-Elements haben hotpinkfarbene Overlays, die den Platz, den sie in der Komponente einnehmen, sowie den Bereich, in dem sie überlaufen, umreißen.

Layout „Tabs <section>“

Dieser Abschnitt ist ein Flex-Element und muss den größten Teil des Platzes einnehmen. Außerdem müssen Spalten erstellt werden, in die die Artikel eingefügt werden. Nochmals vielen Dank für Ihre schnelle Arbeit für CSS 2021! Mit block-size: 100% wird dieses Element so weit wie möglich gestreckt, um das übergeordnete Element auszufüllen. Für das eigene Layout wird dann eine Reihe von Spalten erstellt, die 100% der Breite des übergeordneten Elements entsprechen. Prozentangaben funktionieren hier gut, weil 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: „Vertikal so weit wie möglich ausdehnen, auf eine aufdringliche Weise“ (denken Sie an den Header, den wir auf flex-shrink: 0 gesetzt haben: Er ist eine Abwehr gegen diesen Ausdehnungsdruck), wodurch die Zeilenhöhe für eine Reihe von Spalten mit voller Höhe festgelegt wird. Der Stil auto-flow weist das Raster an, untergeordnete Elemente immer in einer horizontalen Linie ohne Zeilenumbruch anzuordnen. Das ist genau das, was wir wollen: Das übergeordnete Fenster soll überlaufen.

Die Artikelelemente haben pinkfarbene Overlays, die den Platz, den sie in der Komponente einnehmen, und den Bereich, in dem sie überlaufen, umreißen.

Ich finde es manchmal schwierig, das zu verstehen. Dieses Abschnittselement passt in eine Box, hat aber auch eine Reihe von Boxen erstellt. Ich hoffe, die Bilder und Erklärungen helfen Ihnen weiter.

Layout „Tabs <article>“

Der Nutzer sollte in der Lage sein, im Artikelinhalt zu scrollen, und die Bildlaufleisten sollten nur angezeigt werden, wenn es einen Überlauf gibt. Diese Artikelelemente sind ordentlich positioniert. Sie sind gleichzeitig ein übergeordnetes und ein untergeordnetes Scroll-Element. Der Browser übernimmt hier einige knifflige 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 mich dafür entschieden, dass die Artikel innerhalb des übergeordneten Scrollers einrasten. Mir gefällt, wie die Navigationslinks und die Artikelelemente am Inline-Start ihrer jeweiligen Scroll-Container einrasten. Es sieht aus wie eine harmonische Beziehung und fühlt sich auch so an.

Das article-Element und seine untergeordneten Elemente haben pinke Overlays, die den Platz, den sie in der Komponente einnehmen, und die Richtung, in die sie überlaufen, umreißen.

Der Artikel ist ein untergeordnetes Element des Rasters und seine Größe ist so festgelegt, dass er dem Darstellungsbereich entspricht, für den wir die Scroll-UX bereitstellen möchten. Das bedeutet, dass ich hier keine Höhen- oder Breitenstile benötige, sondern nur definieren muss, wie der Überlauf erfolgt. Ich setze „overflow-y“ auf „auto“ und fange die Scroll-Interaktionen mit der praktischen Eigenschaft „overscroll-behavior“ ab.

Zusammenfassung zu den 3 Scrollbereichen

Unten habe ich in meinen Systemeinstellungen „Bildlaufleisten immer einblenden“ 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 Scrollleisten werden angezeigt und nehmen Platz im Layout ein. Die Komponente sieht aber immer noch gut aus.

Ich denke, dass die Anzeige der Bildlaufleiste in dieser Komponente deutlich macht, wo sich die Bildlaufbereiche befinden, welche Richtung sie unterstützen und wie sie miteinander interagieren. Beachten Sie, dass jeder dieser Scrollfenster-Frames auch ein Flex- oder Grid-Elternelement für ein Layout ist.

Die Entwicklertools können uns dabei helfen, dies zu visualisieren:

Die Scrollbereiche haben Raster- und Flexbox-Tool-Overlays, die den Platz, den sie in der Komponente einnehmen, und die Richtung, in die sie überlaufen, umreißen.
Chromium DevTools mit dem Flexbox-Layout des Navigationselements voller Ankerelemente, dem Grid-Layout des Abschnitts voller Artikelelemente und den Artikelelementen voller Absätze und einem Überschriftenelement.

Die Scroll-Layouts sind vollständig: Einrasten, Deeplinks und Tastaturzugriff sind möglich. Solide Grundlage für UX-Verbesserungen, Stil und Freude.

Funktionshighlight

Untergeordnete Elemente mit Scroll Snap behalten ihre gesperrte Position bei, wenn die Größe geändert wird. Das bedeutet, dass JavaScript beim Drehen des Geräts oder beim Ändern der Browsergröße nichts einblenden muss. Probieren Sie es im Gerätemodus der Chromium-Entwicklertools aus, indem Sie einen anderen Modus als Responsiv auswählen und dann die Größe des Geräterahmens anpassen. Beachten Sie, dass das Element sichtbar bleibt und mit seinem Inhalt gesperrt ist. Diese Funktion ist verfügbar, seit Chromium seine Implementierung an die Spezifikation angepasst hat. Weitere Informationen finden Sie in diesem Blogpost.

Animation

Das Ziel der Animationen ist es, Interaktionen klar mit UI-Feedback zu verknüpfen. So können Nutzer alle Inhalte (hoffentlich) nahtlos entdecken. Ich füge Bewegung mit Bedacht und nur unter bestimmten Bedingungen hinzu. Nutzer können jetzt ihre Bewegungseinstellungen in ihrem Betriebssystem festlegen und ich berücksichtige diese Einstellungen gern in meinen Benutzeroberflächen.

Ich verknüpfe die Unterstreichung eines Tabs mit der Scrollposition des Artikels. Das Einrasten dient nicht nur der Ausrichtung, sondern verankert auch den Anfang und das Ende einer Animation. So bleibt die <nav>, die wie eine Minikarte funktioniert, mit dem Inhalt verbunden. Wir prüfen die Bewegungseinstellung des Nutzers sowohl über CSS als auch über JS. Es gibt einige gute Möglichkeiten, rücksichtsvoll zu sein.

Scrollverhalten

Es gibt die Möglichkeit, das Bewegungsverhalten von :target und element.scrollIntoView() zu verbessern. Standardmäßig erfolgt die Überweisung sofort. Der Browser legt nur die Scrollposition fest. Was aber, wenn wir zu dieser Scrollposition übergehen möchten, anstatt dorthin zu springen?

@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), wenden wir diesen Stil nur an, wenn der Nutzer in seinem Betriebssystem keine Einstellung für reduzierte Bewegung festgelegt hat. So führen wir Scrollbewegungen nur für Nutzer ein, die damit einverstanden sind.

Tab-Anzeige

Diese Animation soll helfen, die Anzeige mit dem Status des Inhalts zu verknüpfen. Ich habe mich für eine Farbüberblendung von border-bottom-Stilen für Nutzer entschieden, die weniger Bewegung bevorzugen, und für eine scrollverknüpfte Schiebe- und Farbüberblendungsanimation für Nutzer, die mit Bewegung einverstanden sind.

In den Chromium-Entwicklertools kann ich die Einstellung umschalten und die beiden verschiedenen Übergangsstile demonstrieren. Das hat mir viel Spaß gemacht.

@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 reduzierte Bewegung bevorzugt, da ich es nicht mehr benötige. Dann ersetze ich es durch border-block-end-Stile und ein transition. Beachten Sie auch, dass das aktive Navigationselement in der Tab-Interaktion nicht nur mit einer Markenunterstreichung hervorgehoben ist, sondern auch eine dunklere Textfarbe hat. Das aktive Element hat einen höheren Textfarbkontrast und einen hellen Akzent unter dem Element.

Mit nur wenigen zusätzlichen CSS-Zeilen können Sie dafür sorgen, dass sich jemand gesehen fühlt (im Sinne, dass wir seine Bewegungseinstellungen berücksichtigen). Ich liebe das.

@scroll-timeline

Im obigen Abschnitt habe ich Ihnen gezeigt, wie ich die Überblendungsstile für reduzierte Bewegung handhabe. In diesem Abschnitt zeige ich Ihnen, wie ich die Anzeige und einen Scrollbereich miteinander verknüpft habe. Als Nächstes kommen ein paar interessante experimentelle Funktionen. Ich hoffe, Sie sind genauso gespannt wie ich.

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

Zuerst prüfe ich die Bewegungseinstellung des Nutzers über JavaScript. Wenn das Ergebnis false ist, d. h. der Nutzer bevorzugt reduzierte Bewegung, werden keine der Bewegungseffekte für die Scrollverknüpfung ausgeführt.

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

Zum Zeitpunkt der Erstellung dieses Artikels wird @scroll-timeline von keinem Browser unterstützt. Es handelt sich um eine Spezifikation im Entwurfsstadium, die nur experimentelle Implementierungen enthält. Es gibt jedoch ein Polyfill, das ich in dieser Demo verwende.

ScrollTimeline

Sowohl mit CSS als auch mit JavaScript können Scroll-Zeitachsen erstellt werden. Ich habe mich für JavaScript entschieden, um Live-Elementmessungen in der Animation verwenden zu können.

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 folgt. Durch das Erstellen einer ScrollTimeline definiere ich den Treiber der Scrollverknüpfung, die scrollSource. Normalerweise wird eine Animation im Web mit einem globalen Zeitrahmen-Tick ausgeführt, aber mit einem benutzerdefinierten sectionScrollTimeline im Speicher kann ich das alles ä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 Zeitleiste, dem Scrollen unseres Abschnitts, animiert wird. Damit ist die Verknüpfung abgeschlossen, aber es fehlt noch die letzte Zutat: zustandsorientierte Punkte, zwischen denen animiert werden soll, auch Keyframes genannt.

Dynamische Keyframes

Es gibt eine sehr leistungsstarke, rein deklarative CSS-Methode, um mit @scroll-timeline zu animieren, aber die von mir gewählte Animation war zu dynamisch. Es gibt keine Möglichkeit, zwischen der Breite auto zu wechseln, und es gibt keine Möglichkeit, 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 jeden 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. Jedes Element wird nach seiner dynamischen Breite gefragt, die dann als Keyframe-Wert verwendet wird.

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

TranslateX-Keyframes:

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

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

Breiten-Keyframes:

[...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 die Tab-Anzeige jetzt über vier Keyframes animiert wird, je nach Scroll-Snap-Position des Abschnitts-Scrollers. Die Einrastpunkte sorgen für eine klare Abgrenzung zwischen den Keyframes und tragen zur Synchronität der Animation bei.

Aktiver und inaktiver Tab werden mit VisBug-Overlays angezeigt, die für beide Tabs ausreichende Kontrastwerte aufweisen.

Der Nutzer steuert die Animation durch seine Interaktion. Die Breite und Position der Anzeige ändert sich von einem Abschnitt zum nächsten und folgt genau dem Scrollen.

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

Das nicht ausgewählte hellere Grau wirkt noch weiter zurückgesetzt, wenn das hervorgehobene Element mehr Kontrast hat. Es ist üblich, die Farbe von Text zu ändern, z. B. beim Bewegen des Mauszeigers und beim Auswählen. Es ist jedoch noch besser, diese Farbe beim Scrollen zu ändern, synchronisiert mit der Unterstreichung.

So bin ich vorgegangen:

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

Jeder Navigationslink auf einem Tab benötigt diese neue Farbanimation, die dieselbe Scroll-Zeitachse wie die Unterstreichung verwendet. Ich verwende dieselbe Zeitachse wie zuvor: Da sie beim Scrollen einen Tick ausgibt, können wir diesen Tick in jeder Art von Animation verwenden. Wie zuvor erstelle ich vier Keyframes 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 Keyframe mit der Farbe var(--text-active-color) hebt den Link hervor. Ansonsten 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 Keyframes jedes Navigationselements ist. Ich prüfe, ob das Element der äußeren Schleife mit dem der inneren Schleife übereinstimmt, und verwende das, um zu wissen, wann es ausgewählt ist.

Es hat mir viel Spaß gemacht, diesen Artikel zu schreiben. Sehr.

Noch mehr JavaScript-Verbesserungen

Es ist wichtig, daran zu denken, dass die Kernfunktionen, die ich Ihnen hier zeige, ohne JavaScript funktionieren. Sehen wir uns nun an, wie wir die Seite verbessern können, wenn JS verfügbar ist.

Deeplinks sind eher ein Begriff aus dem Mobilbereich, aber ich denke, dass die Absicht des Deeplinks hier mit Tabs erfüllt wird, da Sie eine URL direkt für den Inhalt eines Tabs freigeben können. Der Browser navigiert auf der Seite zu der ID, die im URL-Hash übereinstimmt. Ich habe festgestellt, dass dieser onload-Handler den Effekt auf allen Plattformen erzielt.

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

Synchronisierung des Scrollendes

Unsere Nutzer klicken oder verwenden nicht immer eine Tastatur, manchmal scrollen sie einfach nur, was sie auch können sollten. Wenn der Abschnittsscroller aufhört zu scrollen, muss die Stelle, an der er landet, in der oberen Navigationsleiste übereinstimmen.

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

Wenn die Abschnitte gescrollt werden, wird das Zeitlimit für den Abschnitt gelöscht, falls vorhanden, und ein neues gestartet. Wenn Abschnitte nicht mehr gescrollt werden, wird das Zeitlimit nicht gelöscht und 100 ms nach dem Stillstand ausgelöst. Wenn er ausgelöst wird, rufen Sie die Funktion auf, die ermittelt, wo der Nutzer aufgehört hat.

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

  matchingNavItem && setActiveTab(matchingNavItem);
};

Wenn das Scrollen eingerastet ist, sollte die Division der aktuellen Scrollposition durch die Breite des Scrollbereichs eine Ganzzahl und keine Dezimalzahl ergeben. Dann versuche ich, über diesen berechneten Index ein Navigationselement aus dem Cache abzurufen. Wenn etwas gefunden wird, wird die Übereinstimmung als aktiv festgelegt.

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

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

Beim Festlegen des aktiven Tabs wird zuerst der aktuell aktive Tab gelöscht und dann dem eingehenden Navigationselement das Attribut für den aktiven Status zugewiesen. 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 für das horizontale Scroll-Snap-Utility haben wir eine Medienabfrage verschachtelt, die smooth-Scrolling anwendet, wenn der Nutzer bewegungstolerant ist. JavaScript kann Elemente frei scrollen, um sie sichtbar zu machen, und CSS kann die UX deklarativ verwalten. Manchmal passen sie ganz gut zusammen.

Fazit

Jetzt, da du weißt, wie ich es gemacht habe, wie würdest du es machen?! Das sorgt für eine interessante Komponentenarchitektur. Wer erstellt die erste Version mit Slots in seinem Lieblings-Framework? 🙂

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

Community-Remixe