Auf manchen besonders umfangreichen Websites benötigt der Benutzer einen temporären visuellen Hinweis, der anzeigt, dass Ressourcen und Assets noch geladen werden, bevor die Website vollständig angezeigt wird. Es gibt verschiedene Ansätze, um diese Art von Benutzererlebnis zu lösen, von Spinners bis hin zu Skeleton Screens.
Wenn wir eine fertige Lösung verwenden, die uns den aktuellen Fortschritt liefert, wie es das Preloader-Paket von Jam3 tut, wird die Erstellung einer Ladeanzeige einfacher.
Dazu erstellen wir einen Ring/Kreis, gestalten ihn, animieren ihn entsprechend dem Fortschritt und verpacken ihn dann in eine Komponente zur Entwicklung.
Schritt 1: Erstellen wir einen SVG-Ring
Von den vielen Möglichkeiten, einen Kreis mit HTML und CSS zu zeichnen, wähle ich SVG, da es möglich ist, ihn über Attribute zu konfigurieren und zu gestalten, während seine Auflösung auf allen Bildschirmen erhalten bleibt.
<svg
class="progress-ring"
height="120"
width="120"
>
<circle
class="progress-ring__circle"
stroke-width="1"
fill="transparent"
r="58"
cx="60"
cy="60"
/>
</svg>
Innerhalb eines <svg> Elements platzieren wir ein <circle> Tag, in dem wir den Radius des Rings mit dem r Attribut, seine Position vom Zentrum im SVG viewBox mit cx und cy sowie die Breite des Kreisstrichs deklarieren.
Sie haben vielleicht bemerkt, dass der Radius 58 und nicht 60 beträgt, was korrekt erscheinen würde. Wir müssen den Strich abziehen, sonst läuft der Kreis aus dem SVG-Wrapper heraus.
radius = (width / 2) - (strokeWidth * 2)
Das bedeutet, wenn wir den Strich auf 4 erhöhen, sollte der Radius 52 betragen.
52 = (120 / 2) - (4 * 2)
Um den Ring zu vervollständigen, müssen wir fill auf transparent setzen und eine stroke Farbe für den Kreis wählen.
Siehe den Pen SVG ring von Jeremias Menichelli (@jeremenichelli) auf CodePen.
Schritt 2: Hinzufügen des Strichs
Der nächste Schritt ist die Animation der Länge der äußeren Linie unseres Rings, um den visuellen Fortschritt zu simulieren.
Wir werden zwei CSS-Eigenschaften verwenden, von denen Sie vielleicht noch nichts gehört haben, da sie exklusiv für SVG-Elemente sind: stroke-dasharray und stroke-dashoffset.
stroke-dasharray
Diese Eigenschaft ist wie border-style: dashed, aber sie erlaubt Ihnen, die Breite der Striche und den Abstand dazwischen zu definieren.
.progress-ring__circle {
stroke-dasharray: 10 20;
}
Mit diesen Werten hat unser Ring 10px lange Striche, die durch 20px getrennt sind.
Siehe den Pen Dashed SVG ring von Jeremias Menichelli (@jeremenichelli) auf CodePen.
stroke-dashoffset
Der zweite erlaubt Ihnen, den Startpunkt dieser Strich-Lücken-Sequenz entlang des Pfades des SVG-Elements zu verschieben.
Stellen Sie sich nun vor, wir würden den Umfang des Kreises an beide stroke-dasharray Werte übergeben. Unsere Form hätte einen langen Strich, der die gesamte Länge einnimmt, und eine Lücke von gleicher Länge, die nicht sichtbar wäre.
Dies führt zunächst zu keiner Änderung, aber wenn wir stroke-dashoffset ebenfalls auf die gleiche Länge setzen, bewegt sich der lange Strich ganz nach hinten und enthüllt die Lücke.
Das Verringern von stroke-dasharray würde beginnen, unsere Form zu enthüllen.
Vor ein paar Jahren erklärte Jake Archibald diese Technik in diesem Artikel, der auch ein Live-Beispiel enthält, das Ihnen helfen wird, es besser zu verstehen. Sie sollten seinen Tutorial lesen.
Der Umfang
Was wir jetzt brauchen, ist diese Länge, die mit dem Radius und dieser einfachen trigonometrischen Formel berechnet werden kann.
circumference = radius * 2 * PI
Da wir wissen, dass 52 der Radius unseres Rings ist
326.7256 ~= 52 * 2 * PI
Wir könnten diesen Wert auch per JavaScript ermitteln, wenn wir wollen
const circle = document.querySelector('.progress-ring__circle');
const radius = circle.r.baseVal.value;
const circumference = radius * 2 * Math.PI;
Auf diese Weise können wir später Stile für unser Kreiselement zuweisen.
circle.style.strokeDasharray = `${circumference} ${circumference}`;
circle.style.strokeDashoffset = circumference;
Schritt 3: Fortschritt zu Offset
Mit diesem kleinen Trick wissen wir, dass die Zuweisung des Umfangs zu stroke-dashoffset den Status von null Fortschritt widerspiegelt und der Wert 0 den vollständigen Fortschritt anzeigt.
Daher müssen wir, wenn der Fortschritt wächst, den Offset wie folgt verringern
function setProgress(percent) {
const offset = circumference - percent / 100 * circumference;
circle.style.strokeDashoffset = offset;
}
Durch die Übergabe der Eigenschaft erhalten wir den Animationseffekt
.progress-ring__circle {
transition: stroke-dashoffset 0.35s;
}
Eine Besonderheit bei stroke-dashoffset ist, dass sein Startpunkt vertikal zentriert und horizontal nach rechts geneigt ist. Es ist notwendig, den Kreis negativ zu drehen, um den gewünschten Effekt zu erzielen.
.progress-ring__circle {
transition: stroke-dashoffset 0.35s;
transform: rotate(-90deg);
transform-origin: 50% 50%,
}
Wenn wir all dies zusammenfügen, erhalten wir etwas wie das hier.
Siehe den Pen vegymB von Jeremias Menichelli (@jeremenichelli) auf CodePen.
In diesem Beispiel wurde eine numerische Eingabe hinzugefügt, um Ihnen beim Testen der Animation zu helfen.
Damit dies einfach in Ihre Anwendung integriert werden kann, ist es am besten, die Lösung in einer Komponente zu kapseln.
Als Web-Komponente
Jetzt, da wir die Logik, die Stile und das HTML für unseren Ladekreis haben, können wir es leicht in jede Technologie oder jedes Framework portieren.
Zuerst verwenden wir Web Components.
class ProgressRing extends HTMLElement {...}
window.customElements.define('progress-ring', ProgressRing);
Dies ist die Standarddeklaration eines benutzerdefinierten Elements, das die native HTMLElement Klasse erweitert und über Attribute konfiguriert werden kann.
<progress-ring stroke="4" radius="60" progress="0"></progress-ring>
Im Konstruktor des Elements erstellen wir einen Shadow Root, um die Stile und seine Vorlage zu kapseln.
constructor() {
super();
// get config from attributes
const stroke = this.getAttribute('stroke');
const radius = this.getAttribute('radius');
const normalizedRadius = radius - stroke * 2;
this._circumference = normalizedRadius * 2 * Math.PI;
// create shadow dom root
this._root = this.attachShadow({mode: 'open'});
this._root.innerHTML = `
<svg
height="${radius * 2}"
width="${radius * 2}"
>
<circle
stroke="white"
stroke-dasharray="${this._circumference} ${this._circumference}"
style="stroke-dashoffset:${this._circumference}"
stroke-width="${stroke}"
fill="transparent"
r="${normalizedRadius}"
cx="${radius}"
cy="${radius}"
/>
</svg>
<style>
circle {
transition: stroke-dashoffset 0.35s;
transform: rotate(-90deg);
transform-origin: 50% 50%;
}
</style>
`;
}
Sie haben vielleicht bemerkt, dass wir die Werte nicht fest in unser SVG codiert haben, sondern sie aus den Attributen des Elements beziehen.
Außerdem berechnen wir den Umfang des Rings und setzen stroke-dasharray und stroke-dashoffset im Voraus.
Als nächstes beobachten wir das Attribut progress und ändern die Kreiselemente.
setProgress(percent) {
const offset = this._circumference - (percent / 100 * this._circumference);
const circle = this._root.querySelector('circle');
circle.style.strokeDashoffset = offset;
}
static get observedAttributes() {
return [ 'progress' ];
}
attributeChangedCallback(name, oldValue, newValue) {
if (name === 'progress') {
this.setProgress(newValue);
}
}
Hier wird setProgress zu einer Klassenmethode, die aufgerufen wird, wenn sich das Attribut progress ändert.
Die observedAttributes werden durch einen statischen Getter definiert, der attributeChangeCallback auslöst, wenn in diesem Fall progress geändert wird.
Siehe den Pen ProgressRing web component von Jeremias Menichelli (@jeremenichelli) auf CodePen.
Dieser Pen funktioniert zum Zeitpunkt des Schreibens nur in Chrome. Ein Intervall wurde hinzugefügt, um die Änderung des Fortschritts zu simulieren.
Als Vue-Komponente
Web Components sind großartig. Dennoch können einige der verfügbaren Bibliotheken und Frameworks, wie Vue.js, einen Großteil der Arbeit erledigen.
Zuerst definieren wir die Ansichtskomponente.
const ProgressRing = Vue.component('progress-ring', {});
Das Schreiben einer Single-File-Komponente ist ebenfalls möglich und wahrscheinlich sauberer, aber wir verwenden die Factory-Syntax, um dem endgültigen Code-Demo zu entsprechen.
Wir definieren die Attribute als Props und die Berechnungen als Daten.
const ProgressRing = Vue.component('progress-ring', {
props: {
radius: Number,
progress: Number,
stroke: Number
},
data() {
const normalizedRadius = this.radius - this.stroke * 2;
const circumference = normalizedRadius * 2 * Math.PI;
return {
normalizedRadius,
circumference
};
}
});
Da berechnete Eigenschaften in Vue out-of-the-box unterstützt werden, können wir sie verwenden, um den Wert von stroke-dashoffset zu berechnen.
computed: {
strokeDashoffset() {
return this._circumference - percent / 100 * this._circumference;
}
}
Als Nächstes fügen wir unser SVG als Vorlage hinzu. Beachten Sie, dass der einfache Teil hier darin besteht, dass Vue uns Bindings zur Verfügung stellt, die JavaScript-Ausdrücke in Attribute und Stile bringen.
template: `
<svg
:height="radius * 2"
:width="radius * 2"
>
<circle
stroke="white"
fill="transparent"
:stroke-dasharray="circumference + ' ' + circumference"
:style="{ strokeDashoffset }"
:stroke-width="stroke"
:r="normalizedRadius"
:cx="radius"
:cy="radius"
/>
</svg>
`
Wenn wir die progress Prop des Elements in unserer App aktualisieren, kümmert sich Vue um die Berechnung der Änderungen und aktualisiert die Elementstile.
Siehe den Pen Vue ProgressRing component von Jeremias Menichelli (@jeremenichelli) auf CodePen.
Hinweis: Ein Intervall wurde hinzugefügt, um die Änderung des Fortschritts zu simulieren. Das tun wir auch im nächsten Beispiel.
Als React-Komponente
Ähnlich wie bei Vue.js hilft uns React, die gesamte Konfiguration und die berechneten Werte dank Props und JSX-Notation zu verwalten.
Zuerst erhalten wir einige Daten von übergebenen Props.
class ProgressRing extends React.Component {
constructor(props) {
super(props);
const { radius, stroke } = this.props;
this.normalizedRadius = radius - stroke * 2;
this.circumference = this.normalizedRadius * 2 * Math.PI;
}
}
Unsere Vorlage ist der Rückgabewert der render Funktion der Komponente, wo wir die progress Prop verwenden, um den Wert von stroke-dashoffset zu berechnen.
render() {
const { radius, stroke, progress } = this.props;
const strokeDashoffset = this.circumference - progress / 100 * this.circumference;
return (
<svg
height={radius * 2}
width={radius * 2}
>
<circle
stroke="white"
fill="transparent"
strokeWidth={ stroke }
strokeDasharray={ this.circumference + ' ' + this.circumference }
style={ { strokeDashoffset } }
stroke-width={ stroke }
r={ this.normalizedRadius }
cx={ radius }
cy={ radius }
/>
</svg>
);
}
Eine Änderung der progress Prop löst einen neuen Render-Zyklus aus, der die Variable strokeDashoffset neu berechnet.
Siehe den Pen React ProgressRing component von Jeremias Menichelli (@jeremenichelli) auf CodePen.
Zusammenfassung
Das Rezept für diese Lösung basiert auf SVG-Formen und Stilen, CSS-Übergängen und etwas JavaScript, um spezielle Attribute zu berechnen und den Zeichnungsumfang zu simulieren.
Sobald wir dieses kleine Stück getrennt haben, können wir es in jede moderne Bibliothek oder jedes moderne Framework portieren und in unsere App integrieren. In diesem Artikel haben wir Web Components, Vue und React behandelt.
Wird einfacher sein, sobald conic-gradient implementiert ist
konische Farbverläufe können nicht schnell genug kommen
Das ist cool, für sich allein. Aber was noch cooler ist: Dies ist eine fantastische reale Demonstration der Template Literals :)
cu, w0lf.
JAAA :D Ich liebe, wirklich wirklich liebe, Template Literals
Wenn ich versuche, ein solches Element zu erstellen, finde ich es nützlich, dem Kreis einen Radius von 16 zu geben und dann die gesamte SVG-Element zu skalieren. Das liegt daran, dass der Umfang eines Kreises mit Radius 16 nahe genug an 100 liegt (er ist 100,53...), was Ihre
stroke-dasharrayBerechnungen erheblich erleichtert.Alles, was Sie tun müssen, ist, Ihren
stroke-dasharrayWert auf100 100zu setzen und dannstroke-dashoffsetauf 100 minus Ihren Zielwert zu setzen. Ich finde es einfacher,stroke-dashoffsetalsstroke-dasharrayzu ändern, da nur ein Wert modifiziert werden muss, obwohl dies einige zusätzliche Subtraktionen bei der Prozentberechnung erfordert. Wenn Sie es vorziehen, den Wertstroke-dasharrayzu ändern, setzen Sie einfach die erste Zahl auf den gewünschten Prozentsatz und lassen Siestroke-dashoffsetganz weg.Hier ist eine einfache SVG-Beispiel, die ich für so etwas erstellt habe
Beachten Sie, dass die
viewBoxauf0 0 36 36gesetzt ist. Das ist nicht unbedingt notwendig, erleichtert aber die präzise Skalierung des Kreises, falls das wichtig ist. Die letzten beiden Werte sind einfach der Durchmesser plus die Strichbreite. So kann ich, wenn ich zum Beispiel einen Kreis möchte, der genau 100px mal 100px groß ist, einfach die SVG-Breite und -Höhe auf 100 setzen. Ganz einfach.const normalizedRadius = radius - stroke * 2;Ich bin mir nicht sicher, aber sollte das nicht
const normalizedRadius = radius - stroke / 2;sein? Oder liege ich falsch bei der Annahme, dass der Strich auf der Mitte der Kontur liegt?Der Strich ist nicht im Radius (r) der Form enthalten, sodass Sie zwei Optionen haben: (1) Sie erhöhen die Breite und Höhe um das Zweifache des Strichs oder (2) Sie verringern den Radius, um die erwartete Breite und Höhe beizubehalten. Der Strich *beginnt* am äußeren Rand des Kreises.
Und er wird mit zwei multipliziert, weil er in der Mitte, sowohl vertikal als auch horizontal, an der oberen und unteren Kante oder an der linken und rechten Kante vorhanden ist.
Ich hoffe, die Erklärung war klar :)
Funktioniert nicht in IE11.
Tut mir leid Leute, es lebt noch.