Erstellung eines Fortschrittsbalkens, schnell

Avatar of Jeremias Menichelli
Jeremias Menichelli am

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

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.

Weitere Lektüre