HTML5-SVG-Füllanimation mit CSS3 und Vanilla-JavaScript

Veröffentlicht: 2022-03-10
Kurze Zusammenfassung ↬ In diesem Artikel erfahren Sie, wie Sie die animierte Notizanzeige von der Awwwards-Website erstellen. Es behandelt das HTML5-SVG-Circle-Element, seine Stricheigenschaften und wie man sie mit CSS-Variablen und Vanilla-JavaScript animiert.

SVG steht für Scalable Vector Graphics und ist eine Standard-XML-basierte Auszeichnungssprache für Vektorgrafiken. Sie können Pfade, Kurven und Formen zeichnen, indem Sie eine Reihe von Punkten in der 2D-Ebene bestimmen. Darüber hinaus können Sie diesen Pfaden Twitch-Eigenschaften hinzufügen (z. B. Strich, Farbe, Dicke, Füllung und mehr), um Animationen zu erstellen.

Seit April 2017 ermöglicht das CSS Level 3 Fill and Stroke Module das Festlegen von SVG-Farben und Füllmustern aus einem externen Stylesheet, anstatt Attribute für jedes Element festzulegen. In diesem Lernprogramm verwenden wir eine einfache einfache Hex-Farbe, aber sowohl Füll- als auch Stricheigenschaften akzeptieren auch Muster, Farbverläufe und Bilder als Werte.

Hinweis : Beim Besuch der Awwwards-Website kann die animierte Notizanzeige nur angezeigt werden, wenn die Browserbreite auf 1024 Pixel oder mehr eingestellt ist.

Hinweis Projektdemo anzeigen
Eine Demo des Endergebnisses (Große Vorschau)
  • Demo: Hinweisanzeigeprojekt
  • Repo: Hinweisanzeige-Repo
Mehr nach dem Sprung! Lesen Sie unten weiter ↓

Dateistruktur

Beginnen wir mit dem Erstellen der Dateien im Terminal:

 mkdir note-display cd note-display touch index.html styles.css scripts.js

HTML

Hier ist die anfängliche Vorlage, die sowohl css als auch js Dateien verknüpft:

 <html lang="en"> <head> <meta charset="UTF-8"> <title>Note Display</title> <link rel="stylesheet" href="./styles.css"> </head> <body> <script src="./scripts.js"></script> </body> </html>

Jedes Notenelement besteht aus einem Listenelement: li , das den circle , den note und seine label enthält.

Listenelementelement und direkte untergeordnete Elemente
Listenelementelement und seine direkten untergeordneten Elemente: .circle , .percent und .label . (Große Vorschau)

.circle_svg ist ein SVG-Element, das zwei <circle>-Elemente umschließt. Der erste ist der zu füllende Pfad, während der zweite die zu animierende Füllung ist.

SVG-Elemente
SVG-Elemente. SVG-Wrapper und Circle-Tags. (Große Vorschau)

Die note ist in Integer- und Dezimalzahlen unterteilt, sodass unterschiedliche Schriftgrößen darauf angewendet werden können. Das label ist ein einfaches <span> . Alles zusammen sieht also so aus:

 <li class="note-display"> <div class="circle"> <svg width="84" height="84" class="circle__svg"> <circle cx="41" cy="41" r="38" class="circle__progress circle__progress--path"></circle> <circle cx="41" cy="41" r="38" class="circle__progress circle__progress--fill"></circle> </svg> <div class="percent"> <span class="percent__int">0.</span> <span class="percent__dec">00</span> </div> </div> <span class="label">Transparent</span> </li>

Die cx und cy -Attribute definieren den x- und y-Achsenmittelpunkt des Kreises. Das r Attribut definiert seinen Radius.

Sie haben wahrscheinlich das Unterstrich/Bindestrich-Muster in Klassennamen bemerkt. Das ist BEM, was für block , element und modifier steht. Es ist eine Methode, die Ihre Elementbenennung strukturierter, organisierter und semantischer macht.

Empfohlene Lektüre : Eine Erklärung von BEM und warum Sie es brauchen

Um die Vorlagenstrukturen fertigzustellen, packen wir die vier Listenelemente in ein ungeordnetes Listenelement:

Wrapper für ungeordnete Listen
Wrapper für ungeordnete Listen enthält vier li Kinder (große Vorschau)
 <ul class="display-container"> <li class="note-display"> <div class="circle"> <svg width="84" height="84" class="circle__svg"> <circle cx="41" cy="41" r="38" class="circle__progress circle__progress--path"></circle> <circle cx="41" cy="41" r="38" class="circle__progress circle__progress--fill"></circle> </svg> <div class="percent"> <span class="percent__int">0.</span> <span class="percent__dec">00</span> </div> </div> <span class="label">Transparent</span> </li> <li class="note-display"> <div class="circle"> <svg width="84" height="84" class="circle__svg"> <circle cx="41" cy="41" r="38" class="circle__progress circle__progress--path"></circle> <circle cx="41" cy="41" r="38" class="circle__progress circle__progress--fill"></circle> </svg> <div class="percent"> <span class="percent__int">0.</span> <span class="percent__dec">00</span> </div> </div> <span class="label">Reasonable</span> </li> <li class="note-display"> <div class="circle"> <svg width="84" height="84" class="circle__svg"> <circle cx="41" cy="41" r="38" class="circle__progress circle__progress--path"></circle> <circle cx="41" cy="41" r="38" class="circle__progress circle__progress--fill"></circle> </svg> <div class="percent"> <span class="percent__int">0.</span> <span class="percent__dec">00</span> </div> </div> <span class="label">Usable</span> </li> <li class="note-display"> <div class="circle"> <svg width="84" height="84" class="circle__svg"> <circle cx="41" cy="41" r="38" class="circle__progress circle__progress--path"></circle> <circle cx="41" cy="41" r="38" class="circle__progress circle__progress--fill"></circle> </svg> <div class="percent"> <span class="percent__int">0.</span> <span class="percent__dec">00</span> </div> </div> <span class="label">Exemplary</span> </li> </ul>

Sie fragen sich sicher, was die Bezeichnungen Transparent , Reasonable , Usable und Exemplary bedeuten. Je mehr Sie sich mit der Programmierung vertraut machen, desto mehr werden Sie feststellen, dass es beim Schreiben von Code nicht nur darum geht, die Anwendung funktionsfähig zu machen, sondern auch sicherzustellen, dass sie langfristig wartbar und skalierbar ist. Das wird nur erreicht, wenn Ihr Code leicht zu ändern ist.

„Das Akronym TRUE sollte bei der Entscheidung helfen, ob der von Ihnen geschriebene Code in der Lage sein wird, Änderungen in der Zukunft zu berücksichtigen oder nicht.“

Fragen Sie sich also beim nächsten Mal:

  • Transparent : Sind die Konsequenzen von Codeänderungen klar?
  • Reasonable : Lohnt sich der Kostenvorteil?
  • Usable : Kann ich es in unerwarteten Szenarien wiederverwenden?
  • Exemplary : Stellt es eine hohe Qualität als Beispiel für zukünftigen Code dar?

Hinweis : „Praktisches objektorientiertes Design in Ruby“ von Sandi Metz erklärt TRUE zusammen mit anderen Prinzipien und wie man diese durch Designmuster erreicht. Wenn Sie sich noch nicht die Zeit genommen haben, Designmuster zu studieren, sollten Sie dieses Buch zu Ihrer Gute-Nacht-Lektüre hinzufügen.

CSS

Lassen Sie uns die Schriftarten importieren und alle Elemente zurücksetzen:

 @import url('https://fonts.googleapis.com/css?family=Nixie+One|Raleway:200'); * { padding: 0; margin: 0; box-sizing: border-box; }

Die Eigenschaft box-sizing: border-box bezieht Polsterungs- und Rahmenwerte in die Gesamtbreite und -höhe eines Elements ein, sodass seine Abmessungen einfacher berechnet werden können.

Hinweis : Für eine visuelle Erklärung zur Größenanpassung von box-sizing lesen Sie bitte „Make Your Life Easier With CSS Box Sizing“.

 body { height: 100vh; color: #fff; display: flex; background: #3E423A; font-family: 'Nixie One', cursive; } .display-container { margin: auto; display: flex; }

Durch die Kombination der Regeln display: flex im body und margin-auto im .display-container ist es möglich, das untergeordnete Element sowohl vertikal als auch horizontal zu zentrieren. Das .display-container Element wird auch ein flex-container sein; Auf diese Weise werden seine Kinder in derselben Reihe entlang der Hauptachse platziert.

Das .note-display ist ebenfalls ein flex-container . Da es viele Kinder zum Zentrieren gibt, machen wir das über die Eigenschaften justify-content und align-items . Alle flex-items werden entlang der cross und main zentriert. Wenn Sie sich nicht sicher sind, welche das sind, sehen Sie sich den Ausrichtungsabschnitt unter „CSS Flexbox Fundamentals Visual Guide“ an.

 .note-display { display: flex; flex-direction: column; align-items: center; margin: 0 25px; }

Wenden wir einen Strich auf die Kreise an, indem wir die Regeln stroke-width -Width , stroke-opacity und stroke-linecap , die zusammen die Live-Enden des Strichs gestalten. Als nächstes fügen wir jedem Kreis eine Farbe hinzu:

 .circle__progress { fill: none; stroke-width: 3; stroke-opacity: 0.3; stroke-linecap: round; } .note-display:nth-child(1) .circle__progress { stroke: #AAFF00; } .note-display:nth-child(2) .circle__progress { stroke: #FF00AA; } .note-display:nth-child(3) .circle__progress { stroke: #AA00FF; } .note-display:nth-child(4) .circle__progress { stroke: #00AAFF; }

Um das percent absolut zu positionieren, muss man absolut wissen, worauf es ankommt. Das .circle Element sollte die Referenz sein, also fügen wir position: relative dazu hinzu.

Hinweis : Für eine tiefere, visuelle Erklärung zur absoluten Positionierung lesen Sie bitte „So verstehen Sie die absolute CSS-Position ein für alle Mal“.

Eine andere Möglichkeit, Elemente zu zentrieren, besteht darin, top: 50% , left: 50% und transform: translate(-50%, -50%); die die Mitte des Elements in der Mitte des übergeordneten Elements positionieren.

 .circle { position: relative; } .percent { width: 100%; top: 50%; left: 50%; position: absolute; font-weight: bold; text-align: center; line-height: 28px; transform: translate(-50%, -50%); } .percent__int { font-size: 28px; } .percent__dec { font-size: 12px; } .label { font-family: 'Raleway', serif; font-size: 14px; text-transform: uppercase; margin-top: 15px; }

Inzwischen sollte die Vorlage so aussehen:

Fertige Ausgangsvorlage
Fertige Vorlagenelemente und Stile (Große Vorschau)

Übergang füllen

Die Kreisanimation kann mit Hilfe von zwei Kreis-SVG-Eigenschaften erstellt werden: stroke-dasharray und stroke-dashoffset .

stroke-dasharray definiert das Strich-Lücke-Muster in einem Strich.“

Es kann bis zu vier Werte annehmen:

  • Wenn es auf eine reine Ganzzahl ( stroke-dasharray: 10 ) eingestellt ist, haben Bindestriche und Lücken dieselbe Größe;
  • Bei zwei Werten ( stroke-dasharray: 10 5 ) wird der erste auf Bindestriche angewendet, der zweite auf Lücken;
  • Die dritte und vierte Form ( stroke-dasharray: 10 5 2 und stroke-dasharray: 10 5 2 3 ) erzeugen Striche und Lücken in verschiedenen Größen.
Stroke dasharray Eigenschaftswerte
stroke-dasharray Eigenschaftswerte (Große Vorschau)

Das Bild auf der linken Seite zeigt, wie die Eigenschaft stroke-dasharray von 0 auf 238 Pixel gesetzt wird, was der Länge des Kreisumfangs entspricht.

Das zweite Bild stellt die Eigenschaft stroke-dashoffset , die den Anfang des Bindestrich-Arrays versetzt. Sie wird auch von 0 auf die Kreisumfangslänge eingestellt.

Stroke dasharray- und dashoffset-Eigenschaften
stroke-dasharray und Strich-Dashoffset Eigenschaften (Große Vorschau)

Um den Fülleffekt zu erzeugen, setzen wir den stroke-dasharray auf die Umfangslänge, sodass seine gesamte Länge mit einem großen Strich und ohne Lücke gefüllt wird. Wir werden es auch um denselben Wert versetzen, damit es „versteckt“ wird. Dann wird das stroke-dashoffset auf den entsprechenden Notenwert aktualisiert, wodurch der Stroke entsprechend der Übergangsdauer gefüllt wird.

Die Aktualisierung der Eigenschaften erfolgt in den Skripten über CSS-Variablen. Lassen Sie uns die Variablen deklarieren und die Eigenschaften festlegen:

 .circle__progress--fill { --initialStroke: 0; --transitionDuration: 0; stroke-opacity: 1; stroke-dasharray: var(--initialStroke); stroke-dashoffset: var(--initialStroke); transition: stroke-dashoffset var(--transitionDuration) ease; }

Um den Initialwert zu setzen und die Variablen zu aktualisieren, selektieren wir zunächst alle .note-display Elemente mit document.querySelectorAll . Die transitionDuration wird auf 900 Millisekunden festgelegt.

Dann durchlaufen wir das Displays-Array, wählen dessen .circle__progress.circle__progress--fill und extrahieren das im HTML festgelegte r -Attribut, um die Umfangslänge zu berechnen. Damit können wir die Anfangswerte --dasharray und --dashoffset .

Die Animation tritt auf, wenn die Variable --dashoffset durch ein setTimeout von 100 ms aktualisiert wird:

 const displays = document.querySelectorAll('.note-display'); const transitionDuration = 900; displays.forEach(display => { let progress = display.querySelector('.circle__progress--fill'); let radius = progress.r.baseVal.value; let circumference = 2 * Math.PI * radius; progress.style.setProperty('--transitionDuration', `${transitionDuration}ms`); progress.style.setProperty('--initialStroke', circumference); setTimeout(() => progress.style.strokeDashoffset = 50, 100); });

Um den Übergang von oben beginnend zu bekommen, muss das .circle__svg Element gedreht werden:

 .circle__svg { transform: rotate(-90deg); } 
Übergang der Stricheigenschaften
Stricheigenschaften-Übergang (große Vorschau)

Lassen Sie uns nun den dashoffset -Wert berechnen – relativ zur Note. Der Notenwert wird über das Attribut data-* in jedes li -Element eingefügt. Das * kann gegen einen beliebigen Namen ausgetauscht werden, der Ihren Anforderungen entspricht, und es kann dann in JavaScript über das Dataset des Elements abgerufen werden: element.dataset.* .

Hinweis : Weitere Informationen zum data-*-Attribut finden Sie in MDN Web Docs.

Unser Attribut heißt „ data-note “:

 <ul class="display-container"> + <li class="note-display" data-note="7.50"> <div class="circle"> <svg width="84" height="84" class="circle__svg"> <circle cx="41" cy="41" r="38" class="circle__progress circle__progress--path"></circle> <circle cx="41" cy="41" r="38" class="circle__progress circle__progress--fill"></circle> </svg> <div class="percent"> <span class="percent__int">0.</span> <span class="percent__dec">00</span> </div> </div> <span class="label">Transparent</span> </li> + <li class="note-display" data-note="9.27"> <div class="circle"> <svg width="84" height="84" class="circle__svg"> <circle cx="41" cy="41" r="38" class="circle__progress circle__progress--path"></circle> <circle cx="41" cy="41" r="38" class="circle__progress circle__progress--fill"></circle> </svg> <div class="percent"> <span class="percent__int">0.</span> <span class="percent__dec">00</span> </div> </div> <span class="label">Reasonable</span> </li> + <li class="note-display" data-note="6.93"> <div class="circle"> <svg width="84" height="84" class="circle__svg"> <circle cx="41" cy="41" r="38" class="circle__progress circle__progress--path"></circle> <circle cx="41" cy="41" r="38" class="circle__progress circle__progress--fill"></circle> </svg> <div class="percent"> <span class="percent__int">0.</span> <span class="percent__dec">00</span> </div> </div> <span class="label">Usable</span> </li> + <li class="note-display" data-note="8.72"> <div class="circle"> <svg width="84" height="84" class="circle__svg"> <circle cx="41" cy="41" r="38" class="circle__progress circle__progress--path"></circle> <circle cx="41" cy="41" r="38" class="circle__progress circle__progress--fill"></circle> </svg> <div class="percent"> <span class="percent__int">0.</span> <span class="percent__dec">00</span> </div> </div> <span class="label">Exemplary</span> </li> </ul>

Die parseFloat Methode konvertiert die von display.dataset.note zurückgegebene Zeichenfolge in eine Fließkommazahl. Der offset stellt den Prozentsatz dar, der fehlt, um die maximale Punktzahl zu erreichen. Für eine 7.50 Note hätten wir also (10 - 7.50) / 10 = 0.25 , was bedeutet, dass die circumference um 25% ihres Wertes versetzt werden sollte:

 let note = parseFloat(display.dataset.note); let offset = circumference * (10 - note) / 10;

Aktualisieren der scripts.js :

 const displays = document.querySelectorAll('.note-display'); const transitionDuration = 900; displays.forEach(display => { let progress = display.querySelector('.circle__progress--fill'); let radius = progress.r.baseVal.value; let circumference = 2 * Math.PI * radius; + let note = parseFloat(display.dataset.note); + let offset = circumference * (10 - note) / 10; progress.style.setProperty('--initialStroke', circumference); progress.style.setProperty('--transitionDuration', `${transitionDuration}ms`); + setTimeout(() => progress.style.strokeDashoffset = offset, 100); }); 
Stricheigenschaften wechseln bis zum Notenwert
Übergang der Stricheigenschaften bis zum Notenwert (große Vorschau)

Bevor wir fortfahren, extrahieren wir den Stoke-Übergang in seine eigene Methode:

 const displays = document.querySelectorAll('.note-display'); const transitionDuration = 900; displays.forEach(display => { - let progress = display.querySelector('.circle__progress--fill'); - let radius = progress.r.baseVal.value; - let circumference = 2 * Math.PI * radius; let note = parseFloat(display.dataset.note); - let offset = circumference * (10 - note) / 10; - progress.style.setProperty('--initialStroke', circumference); - progress.style.setProperty('--transitionDuration', `${transitionDuration}ms`); - setTimeout(() => progress.style.strokeDashoffset = offset, 100); + strokeTransition(display, note); }); + function strokeTransition(display, note) { + let progress = display.querySelector('.circle__progress--fill'); + let radius = progress.r.baseVal.value; + let circumference = 2 * Math.PI * radius; + let offset = circumference * (10 - note) / 10; + progress.style.setProperty('--initialStroke', circumference); + progress.style.setProperty('--transitionDuration', `${transitionDuration}ms`); + setTimeout(() => progress.style.strokeDashoffset = offset, 100); + }

Hinweis Wertsteigerung

Es muss noch der Notenübergang von 0.00 zum Notenwert aufgebaut werden. Das erste, was Sie tun müssen, ist, die Ganzzahl- und Dezimalwerte zu trennen. Wir werden die String-Methode split() verwenden (sie nimmt ein Argument, das bestimmt, wo der String unterbrochen wird, und gibt ein Array zurück, das beide unterbrochenen Strings enthält). Diese werden in Zahlen umgewandelt und zusammen mit dem display und einem Flag, das angibt, ob es sich um eine Ganzzahl oder eine Dezimalzahl handelt, als Argumente an die Funktion increaseNumber() übergeben.

 const displays = document.querySelectorAll('.note-display'); const transitionDuration = 900; displays.forEach(display => { let note = parseFloat(display.dataset.note); + let [int, dec] = display.dataset.note.split('.'); + [int, dec] = [Number(int), Number(dec)]; strokeTransition(display, note); + increaseNumber(display, int, 'int'); + increaseNumber(display, dec, 'dec'); });

In der Funktion raiseNumber increaseNumber() wählen wir entweder das Element .percent__int oder .percent__dec , je nach className , und auch, ob die Ausgabe einen Dezimalpunkt enthalten soll oder nicht. Wir haben unsere transitionDuration auf 900ms . Um nun beispielsweise eine Zahl von 0 bis 7 zu animieren, muss die Dauer durch die Note 900 / 7 = 128.57ms werden. Das Ergebnis stellt dar, wie lange jede Steigerungsiteration dauern wird. Das bedeutet, dass unser setInterval alle 128.57ms ms ausgelöst wird.

Wenn diese Variablen gesetzt sind, definieren setInterval . Die counter wird als Text an das Element angehängt und bei jeder Iteration erhöht:

 function increaseNumber(display, number, className) { let element = display.querySelector(`.percent__${className}`), decPoint = className === 'int' ? '.' : '', interval = transitionDuration / number, counter = 0; let increaseInterval = setInterval(() => { element.textContent = counter + decPoint; counter++; }, interval); } 
Unendliche Erhöhung des Zählers
Unendliche Erhöhung des Zählers (große Vorschau)

Cool! Es erhöht zwar die Werte, aber es tut es irgendwie für immer. Wir müssen setInterval wenn die Noten den gewünschten Wert erreichen. Dies geschieht mit der Funktion clearInterval :

 function increaseNumber(display, number, className) { let element = display.querySelector(`.percent__${className}`), decPoint = className === 'int' ? '.' : '', interval = transitionDuration / number, counter = 0; let increaseInterval = setInterval(() => { + if (counter === number) { window.clearInterval(increaseInterval); } element.textContent = counter + decPoint; counter++; }, interval); } 
Fertiges Projekt zur Anzeige von Notizen
Fertiges Projekt (Große Vorschau)

Jetzt wird die Zahl bis zum Notenwert aktualisiert und mit der Funktion clearInterval() gelöscht.

Das ist so ziemlich alles für dieses Tutorial. Ich hoffe, dass es Ihnen gefallen hat!

Wenn Sie Lust haben, etwas Interaktiveres zu bauen, schauen Sie sich mein Memory-Spiel-Tutorial an, das mit Vanilla JavaScript erstellt wurde. Es behandelt grundlegende HTML5-, CSS3- und JavaScript-Konzepte wie Positionierung, Perspektive, Übergänge, Flexbox, Ereignisbehandlung, Timeouts und Ternaries.

Viel Spaß beim Codieren!