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.


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

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

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.
- Designer VS. Entwickler #8: Design für großartige Performance
- Vince Speelman: Die neun Zustände des Designs
- Harry Roberts: Verbesserung der wahrgenommenen Performance mit mehreren Hintergrundbildern
- Sitepoint: Ein Leitfaden für Designer zu schnellen Websites und wahrgenommener Performance
- Manuel Wieser: Dominant Color Lazy Loading
Wenn Sie keine definierte Breite und Höhe haben, sollten Sie trotzdem mindestens eine Höhe für Responsivität definieren? Und dann den Rest einfach ausblenden (abschneiden), wenn er kleiner skaliert wird?
Oder wäre es besser, dies für jede Media-Query anzupassen?
Ziemlich genial, :empty zu verwenden, das gefällt mir.
Hallo Chris!
Sie benötigen die Höhe, um die Hintergrundposition zu berechnen, aber Sie können die feste Breite leicht durch Prozentsätze ersetzen. Oder Sie könnten etwas wie
calc(100% - var(--card-padding) * 2)tun, um Block-Level-Elemente zu „faken“.Schauen Sie sich diesen schnellen Fork für eine responsive Version an
@Max Sehr nett! Ich werde diese Technik vielleicht für die Web-App ausprobieren, die ich entwickle. Wir haben ein paar Ajax-Aufrufe, und das wäre großartig dafür. Vielen Dank für den Artikel und das nachfolgende Codepen!
Sehr gut erklärt. Vielen Dank für das Teilen dieses Artikels.
Obwohl ich das Konzept von Skeleton-Elementen mag, hatte ich immer das Gefühl, dass, wenn man Animationen in einem benötigt, die „wahrgenommene“ Performance immer noch schlecht ist, da die Benutzer immer noch spürbar lange auf tatsächliche Inhalte warten. Ich versuche immer, Backend-Antworten unter 100 ms für jede JavaScript-Aktion zu erhalten.
@Max danke für den großartigen Schnipsel. Ich werde ihn in einigen kommenden Projekten verwenden.
fantastisch! Ich freue mich, dass er Ihnen nützlich ist.
Sie benötigen kein zusätzliches Pseudo-Element wie ::after, wenn Sie die :empty-Pseudo-Klasse verwenden. Solange der Container keine Kind-Knoten jeglicher Art hat, wird der Selektor abgeglichen und Sie können den Hintergrund des Containers mit dem Skeleton-Screen stylen, wodurch unnötige Komplexität entfällt.
Ich habe dieselbe Demo vor über einem Jahr erstellt: https://codepen.io/oslego/pen/XdvWmd
Außerdem sollten Sie den 99%-Hack in der Farbunterbrechung für den radialen Gradienten erklären. Er ist in Chrome nicht mehr notwendig (Bug behoben), aber die Leute werden verwirrt sein.
Obwohl die Animation cool ist, würde ich sie vermeiden, insbesondere bei großen Flächengradienten. Sie verursacht ständige Neuanzeigen im Browser und verschlechtert damit die Performance. Zur Überprüfung: Öffnen Sie Chrome DevTools, wechseln Sie zum Rendering-Panel (neben der unteren Konsole) und aktivieren Sie das Kontrollkästchen „Paint flashing“.
@Razvan und/oder @Max,
Erstens – danke, das ist *großartig.
Zweitens – könnte jemand den 99%-Hack erklären/einen Link zu einer Erklärung bereitstellen, ein Dokument über die Bug-Behebung, oder einfach nur, was da vor sich geht?
Hallo Schuyler,
Grundsätzlich möchten Sie, dass der radiale Gradient klare Kanten hat, im Gegensatz zur standardmäßigen Überblendung.
Wie Razvan richtig bemerkt hat, gab es einen Fehler in Chrome (und er ist in Firefox und Safari immer noch vorhanden), bei dem radiale Verläufe mit einer 100%-igen und einer transparenten 0%-igen Farbstoppung nicht als Kreise gerendert wurden, sondern den gesamten Bereich ausfüllten. Der 99%-Hack ist eine Möglichkeit, dies zu umgehen.
Sie könnten es auch so schreiben:
radial-gradient(circle $radius, $color 99%, transparent 100%). Es ist nicht perfekt glatt an den Rändern, aber nah genug ;)Oh, verstanden. Danke für die Erklärung!
Ich habe das Konzept wirklich verstanden, aber der Animationsteil war nicht gut erklärt. Vielleicht hat der Autor angenommen, dass sein Publikum die Animation kennt.
Beim Versuch, den Keyframe-Abschnitt zu verstehen, stellte ich fest, dass CSS-Variablen Problemlöser sind, aber manchmal benötigt man das genaue Ergebnis, ohne die Variablen sperren zu müssen.
Fantastische Erklärung. Dies ist eine so großartige Demonstration, wie mehrere einfache Dinge (mehrere Hintergründe, CSS-Eigenschaften, einfache Übergänge und Animationen) kombiniert eine beeindruckende Lösung ergeben können.
Ich ändere den Wert davon für eine sanftere Animation, anstatt dass sie abgehackt aussieht. Danke für die
background-position-200% 0
Aber es ist ein verlorenes Spiel, nicht wahr?
Die Tricks, die wir anwenden, um die Wahrnehmung des Ladens zu erzeugen, werden abgenutzt, und der Trick selbst wird so nervig wie die ursprüngliche Wartezeit.
Wir haben Spinner und Fortschrittsbalken eingeführt, um dem Benutzer die Wahrnehmung von Aktivität zu vermitteln, anstatt ihm einen leeren Raum zu präsentieren. Aber jetzt reicht der Spinner nicht mehr aus. Warum? Einfach. Benutzer haben sich an den Spinner gewöhnt und festgestellt, dass es sich um eine helle und glänzende Sache handelt, die dazu dient, sie abzulenken. Der Spinner und die leere Seite wurden eins.
Und das Gleiche wird mit Skeleton Screens passieren. Sie sind ziemlich verbreitet, aber wer schaut sich diesen Effekt an und merkt nicht, was vor sich geht?
Das Einzige, was jemals von Bedeutung sein wird, sind tatsächliche Inhalte. Vielleicht lädt die Seite etwas im Hintergrund, das sofort angezeigt wird, während der Benutzer wartet. Das scheint eine gute Gelegenheit zu sein, dem Benutzer Werbeinformationen oder eine Handlungsaufforderung anzubieten.
Oder bleiben Sie einfach beim Spinner.
Wahrnehmung ist Realität. Dies ahmt die Form des Inhalts nach, daher ist es besser als ein Spinner (vorerst). Wenn Sie darauf warten, dass Sie ein Freund abholt, schauen die Leute immer die Straße hinunter, um das Auto zu sehen, bevor es sie erreicht, anstatt nur auf ihre Uhr zu schauen.
Hallo Nuwanda,
Du hast Recht, das Konzept der Skeleton Screens ist nicht ganz neu – ich glaube, die meisten Leute haben es schon irgendwo gesehen, z.B. bei Facebook. Niemand wird wirklich „getäuscht“, zu glauben, dass dies kein Ladezustand ist – es ist nur eine Art von Spinner, der etwas mehr Informationen vermittelt.
Dennoch funktionieren unsere Gehirne so, dass die Antizipation von Inhalten sich besser anfühlt als das Starren auf einen sich drehenden Kreis. Das ist der Sinn von wahrgenommener Performance.
Dies ist natürlich keine Entschuldigung, *tatsächliche* Performance-Optimierung zu überspringen – wenn Sie aussagekräftige Inhalte cachen und anzeigen können – großartig!