Aufbau von Skeleton-Screens mit CSS Custom Properties

Avatar of Max Böck
Max Böck am

DigitalOcean bietet Cloud-Produkte für jede Phase Ihrer Reise. Starten Sie mit 200 $ kostenlosem Guthaben!

Das Designen von Ladezuständen im Web wird oft übersehen oder als nachträglicher Gedanke abgetan. Performance ist nicht nur die Verantwortung eines Entwicklers, sondern das Erschaffen einer Erfahrung, die auch bei langsamen Verbindungen funktioniert, kann ebenso eine Design-Herausforderung sein.

Während Entwickler auf Dinge wie Minifizierung und Caching achten müssen, müssen Designer darüber nachdenken, wie die Benutzeroberfläche in einem „Lade-“ oder „Offline“-Zustand aussehen und sich verhalten wird.

Die Illusion von Geschwindigkeit

Mit der Veränderung unserer Erwartungen an mobile Erlebnisse verändert sich auch unser Verständnis von Performance. Menschen erwarten, dass sich Webanwendungen genauso schnell und reaktionsschnell anfühlen wie native Apps, unabhängig von ihrer aktuellen Netzwerkabdeckung.

Wahrgenommene Performance ist ein Maß dafür, wie schnell sich etwas für den Benutzer anfühlt. Die Idee ist, dass Benutzer geduldiger sind und ein System als schneller empfinden, wenn sie wissen, was vor sich geht und Inhalte antizipieren können, bevor sie tatsächlich da sind. Es geht viel darum, Erwartungen zu managen und den Benutzer zu informieren.

Für eine Webanwendung könnte dieses Konzept das Anzeigen von „Mockups“ von Text, Bildern oder anderen Inhaltselementen beinhalten – genannt Skeleton Screens 💀. Sie können diese in Aktion sehen, verwendet von Unternehmen wie Facebook, Google, Slack und anderen.

Slack desktop app using skeleton screens while loading
Heilige Makrele, Slack, du auch.
Facebooks Skeleton

Ein Beispiel

Nehmen wir an, Sie bauen eine Web-App. Es ist eine Art Reiseberatung, bei der Leute ihre Reisen teilen und Orte empfehlen können, also könnte Ihr Hauptinhalt wie folgt aussehen

card UI of a travel blog post

Sie können diese Karte nehmen und sie auf ihre grundlegenden visuellen Formen reduzieren, das Skelett der UI-Komponente.

skeleton version of the same card, outlined in gray rectangles

Immer wenn jemand neue Inhalte vom Server anfordert, können Sie sofort das Skelett anzeigen, während die Daten im Hintergrund geladen werden. Sobald die Inhalte bereit sind, tauschen Sie einfach das Skelett gegen die tatsächliche Karte aus. Dies kann mit reinem Vanilla JavaScript oder mit einer Bibliothek wie React geschehen.

Nun könnten Sie ein Bild verwenden, um das Skelett anzuzeigen, aber das würde eine zusätzliche Anfrage und Daten-Overhead bedeuten. Wir laden hier bereits Dinge, daher ist es keine gute Idee, darauf zu warten, dass zuerst ein weiteres Bild geladen wird. Außerdem ist es nicht responsiv, und wenn wir uns jemals entscheiden, einige der Stile der Inhaltskarte zu ändern, müssten wir die Änderungen auch auf das Skelettbild duplizieren, damit sie wieder übereinstimmen. 😒 Meh.

Eine bessere Lösung ist, das Ganze nur mit CSS zu erstellen. Keine zusätzlichen Anfragen, minimaler Overhead, nicht einmal zusätzlicher Markup. Und wir können es so aufbauen, dass spätere Designänderungen viel einfacher sind.

Skelette in CSS zeichnen

Zuerst müssen wir die Grundformen zeichnen, aus denen das Kartenskelett bestehen wird. Dies können wir tun, indem wir verschiedene Gradients zur background-image-Eigenschaft hinzufügen. Standardmäßig laufen lineare Gradients von oben nach unten, mit unterschiedlichen Farbunterbrechungen. Wenn wir nur eine Farbunterbrechung definieren und den Rest transparent lassen, können wir Formen zeichnen.

Beachten Sie, dass hier mehrere Hintergrundbilder übereinander gestapelt sind, daher ist die Reihenfolge wichtig. Die letzte Gradientendefinition liegt hinten, die erste vorne.

.skeleton {
  background-repeat: no-repeat;
  background-image: 
    /* layer 2: avatar */
    /* white circle with 16px radius */
    radial-gradient(circle 16px, white 99%, transparent 0),
    /* layer 1: title */
    /* white rectangle with 40px height */
    linear-gradient(white 40px, transparent 0),
    /* layer 0: card bg */
    /* gray rectangle that covers whole element */
    linear-gradient(gray 100%, transparent 0);
}

Diese Formen dehnen sich aus, um den gesamten Bereich auszufüllen, genau wie normale Block-Level-Elemente. Wenn wir das ändern wollen, müssen wir explizite Dimensionen für sie definieren. Die Wertpaare in background-size legen die Breite und Höhe jeder Ebene fest und behalten dabei die gleiche Reihenfolge bei, die wir in background-image verwendet haben.

.skeleton {
  background-size:
    32px 32px,  /* avatar */
    200px 40px, /* title */
    100% 100%;  /* card bg */
}

Der letzte Schritt ist die Positionierung der Elemente auf der Karte. Dies funktioniert genauso wie position:absolute, wobei die Werte den Eigenschaften left und top entsprechen. Wir können zum Beispiel einen Abstand von 24px für den Avatar und den Titel simulieren, um das Aussehen der tatsächlichen Inhaltskarte nachzuahmen.

.skeleton {
  background-position:
    24px 24px,  /* avatar */
    24px 200px, /* title */
    0 0;        /* card bg */
}

Mit Custom Properties aufteilen

Das funktioniert gut in einem einfachen Beispiel – aber wenn wir etwas Komplexeres aufbauen wollen, wird das CSS schnell unübersichtlich und schwer lesbar. Wenn ein anderer Entwickler diesen Code erhält, wird er keine Ahnung haben, woher all diese magischen Zahlen stammen. Die Wartung wäre sicherlich schwierig.

Glücklicherweise können wir jetzt benutzerdefinierte CSS-Eigenschaften verwenden, um die Skeleton-Styles auf eine viel prägnantere, entwicklerfreundlichere Weise zu schreiben – und sogar die Beziehung zwischen verschiedenen Werten zu berücksichtigen.

.skeleton {
  /*
    define as separate properties
  */
  --card-height: 340px;
  --card-padding:24px;
  --card-skeleton: linear-gradient(gray var(--card-height), transparent 0);

  --title-height: 32px;
  --title-width: 200px;
  --title-position: var(--card-padding) 180px;
  --title-skeleton: linear-gradient(white var(--title-height), transparent 0);

  --avatar-size: 32px;
  --avatar-position: var(--card-padding) var(--card-padding);
  --avatar-skeleton: radial-gradient(
    circle calc(var(--avatar-size) / 2), 
    white 99%, 
    transparent 0
  );

  /* 
    now we can break the background up 
    into individual shapes 
  */
  background-image: 
    var(--avatar-skeleton),
    var(--title-skeleton),
    var(--card-skeleton);

  background-size:
    var(--avatar-size),
    var(--title-width) var(--title-height),
    100% 100%;

  background-position:
    var(--avatar-position),
    var(--title-position),
    0 0;
}

Dies ist nicht nur viel besser lesbar, sondern auch viel einfacher, einige Werte später zu ändern. Außerdem können wir einige der Variablen (denken Sie an --avatar-size, --card-padding usw.) verwenden, um die Stile für die tatsächliche Karte zu definieren und sie immer mit der Skeleton-Version synchron zu halten.

Das Hinzufügen einer Media-Query, um Teile des Skeletts an verschiedenen Breakpoints anzupassen, ist jetzt auch recht einfach.

@media screen and (min-width: 47em) {
  :root {
    --card-padding: 32px;
    --card-height: 360px;
  }
}

Browser-Unterstützung für benutzerdefinierte Eigenschaften ist gut, aber nicht bei 100%. Grundsätzlich haben alle modernen Browser Unterstützung, wobei IE/Edge etwas spät dran sind. Für diesen speziellen Anwendungsfall wäre es einfach, einen Fallback mit Sass-Variablen hinzuzufügen.

Animation hinzufügen

Um dies noch besser zu machen, können wir unser Skelett animieren und es wie eine Ladeanzeige aussehen lassen. Alles, was wir tun müssen, ist, ein neues Gradient auf die oberste Ebene zu legen und dann seine Position mit @keyframes zu animieren.

Hier ist ein vollständiges Beispiel, wie die fertige Skeleton-Karte aussehen könnte.

Skeleton Loading Card von Max Böck (@mxbck) auf CodePen.

Sie können den :empty-Selektor und ein Pseudo-Element verwenden, um das Skelett zu zeichnen, sodass es nur auf leere Kartenelemente angewendet wird. Sobald der Inhalt eingefügt ist, verschwindet der Skeleton-Screen automatisch.

Mehr zum Thema Design für Performance

Für einen genaueren Blick auf das Design für wahrgenommene Performance, schauen Sie sich diese Links an.