Typewriter Animation That Handles Anything You Throw at It

Avatar of Murtuzaali Surti
Murtuzaali Surti am

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

Ich habe mir Kevin Powells Video angesehen, in dem er eine schöne Schreibmaschinen-ähnliche Animation mit CSS nachbilden konnte. Das ist raffiniert und Sie sollten es sich unbedingt ansehen, denn dort gibt es handfeste CSS-Tricks. Ich bin sicher, Sie haben bereits andere CSS-Versuche dazu gesehen, einschließlich dieses eigenen Snippets dieser Seite.

Wie Kevin habe ich beschlossen, die Animation nachzubilden, sie aber für JavaScript zu öffnen. Auf diese Weise haben wir ein paar zusätzliche Werkzeuge, die das Tippen natürlicher und noch dynamischer wirken lassen können. Viele CSS-Lösungen basieren auf magischen Zahlen, die von der Länge des Textes abhängen, aber mit JavaScript können wir etwas schaffen, das in der Lage ist, jeden beliebigen Text zu verarbeiten.

Also, machen wir das. In diesem Tutorial zeige ich Ihnen, wie wir mehrere Wörter animieren können, indem wir einfach den eigentlichen Text ändern. Sie müssen den Code nicht jedes Mal ändern, wenn Sie ein neues Wort hinzufügen, denn JavaScript erledigt das für Sie!

Beginnend mit dem Text

Beginnen wir mit dem Text. Wir verwenden eine Monospace-Schriftart, um den Effekt zu erzielen. Warum? Weil jedes Zeichen oder Buchstabe in einer Monospace-Schriftart den gleichen horizontalen Platz einnimmt, was nützlich ist, wenn wir das Konzept von steps() beim Animieren des Textes verwenden. Die Dinge sind viel vorhersehbarer, wenn wir die exakte Breite eines Zeichens kennen und alle Zeichen die gleiche Breite haben.

Wir haben drei Elemente, die in einem Container platziert sind: ein Element für den eigentlichen Text, eines zum Verstecken des Textes und eines zur Animation des Cursors.

<div class="container">
  <div class="text_hide"></div>
  <div class="text">Typing Animation</div>
  <div class="text_cursor"></div>
</div>

Wir könnten hier ::before und ::after Pseudoelemente verwenden, aber sie sind nicht ideal für JavaScript. Pseudoelemente sind nicht Teil des DOM, sondern werden als zusätzliche Hooks zum Stylen eines Elements in CSS verwendet. Es wäre besser, mit echten Elementen zu arbeiten.

Wir verstecken den Text vollständig hinter dem .text_hide Element. Das ist entscheidend. Es ist ein leeres Div, das sich über die Breite des Textes erstreckt und ihn blockiert, bis die Animation beginnt – dann sehen wir, wie der Text hinter dem Element hervorgezogen wird.

A light orange rectangle is on top of the words Hidden Text with an orange arrow blow it indicating that it moves from left to right to reveal the text.

Um das gesamte Textelement abzudecken, positionieren Sie das .text_hide Element über dem Textelement mit der gleichen Höhe und Breite wie das Textelement. Denken Sie daran, die background-color des .text_hide Elements genau gleich der Hintergrundfarbe um den Text herum einzustellen, damit alles nahtlos zusammenpasst.

.container {
  position: relative;
}
.text {
  font-family: 'Roboto Mono', monospace;
  font-size: 2rem;
}
.text_hide {
  position: absolute;
  top: 0;
  bottom: 0;
  left: 0;
  right: 0;
  background-color: white;
}

Der Cursor

Als Nächstes machen wir dieses kleine Cursor-Ding, das blinkt, während der Text getippt wird. Wir halten das Blinken noch einen Moment zurück und konzentrieren uns nur auf den Cursor selbst.

Erstellen wir ein weiteres Element mit der Klasse .text_cursor. Die Eigenschaften werden denen des .text_hide Elements ähneln, mit einem kleinen Unterschied: Anstatt einer background-color setzen wir die background-color auf transparent (da es technisch unnötig ist, und fügen einen Rand am linken Rand des neuen .text_cursor Elements hinzu.

.text_cursor{
  position: absolute;
  top: 0;
  bottom: 0;
  left: 0;
  right: 0;
  background-color: transparent;
  border-left: 3px solid black;
}

Jetzt haben wir etwas, das wie ein Cursor aussieht und bereit ist, sich zu bewegen, wenn sich der Text bewegt.

The words hidden text behind a light orange rectangle that representing the element hiding the text. A cursor is on the left side of the hidden text.

JavaScript-Animation

Nun kommt der super lustige Teil – animieren wir diese Dinge mit JavaScript! Wir beginnen damit, alles in eine Funktion namens typing_animation() zu packen.

function typing_animation(){
  // code here
}
typing_animation();

Die nächste Aufgabe ist es, jedes einzelne Zeichen des Textes mit der Methode split() in einem einzigen Array zu speichern. Dies teilt den String in einen Unterstring, der nur ein Zeichen enthält, und gibt ein Array zurück, das alle Unterstrings enthält.

function typing_animation(){
  let text_element = document.querySelector(".text");
  let text_array = text_element.innerHTML.split("");
}

Zum Beispiel, wenn wir „Typing Animation“ als String nehmen, ist die Ausgabe

(16) ["T", "y", "p", "i", "n", "g", " ", "A", "n", "i", "m", "a", "t", "i", "o", "n"]

Wir können auch die Gesamtzahl der Zeichen im String ermitteln. Um nur die Wörter im String zu erhalten, ersetzen wir split("") durch split(" "). Beachten Sie, dass es einen Unterschied zwischen den beiden gibt. Hier fungiert " " als Trennzeichen. Wann immer wir auf ein einzelnes Leerzeichen stoßen, wird der Unterstring beendet und als Array-Element gespeichert. Dann geht der Prozess für den gesamten String weiter.

function typing_animation(){
  let text_element = document.querySelector(".text");
  let text_array = text_element.innerHTML.split("");
  let all_words = text_element.innerHTML.split(" ");
}

Zum Beispiel, für einen String wie 'Typing Animation', wird die Ausgabe lauten:

(2") ["Typing", "Animation"]

Nun berechnen wir die Länge des gesamten Strings sowie die Länge jedes einzelnen Wortes.

function typing_animation() {
  let text_element = document.querySelector(".text");
  let text_array = text_element.innerHTML.split("");
  let all_words = text_element.innerHTML.split(" ");
  let text_len = text_array.length;

  const word_len = all_words.map((word) => {
    return word.length;
  });
}

Um die Länge des gesamten Strings zu erhalten, müssen wir auf die Länge des Arrays zugreifen, das alle Zeichen als einzelne Elemente enthält. Wenn wir über die Länge eines einzelnen Wortes sprechen, können wir die Methode map() verwenden, die ein Wort nach dem anderen aus dem all_words Array aufruft und dann die Länge des Wortes in einem neuen Array namens word_len speichert. Beide Arrays haben die gleiche Anzahl von Elementen, aber eines enthält das *tatsächliche Wort* als Element, und das andere hat die *Länge des Wortes* als Element.

Jetzt können wir animieren! Wir verwenden die Web Animation API, da wir hier auf reines JavaScript setzen – keine CSS-Animationen für uns in diesem Beispiel.

Zuerst animieren wir den Cursor. Er muss unendlich oft blinken. Wir brauchen Keyframes und Animationseigenschaften, die beide in ihren eigenen JavaScript-Objekten gespeichert werden. Hier sind die Keyframes

document.querySelector(".text_cursor").animate([
  {
    opacity: 0
  },
  {
    opacity: 0, offset: 0.7
  },
  {
    opacity: 1
  }
], cursor_timings);

Wir haben drei Keyframes als Objekte definiert, die in einem Array gespeichert sind. Der Begriff offset: 0.7 bedeutet einfach, dass nach 70% des Animationsfortschritts die Deckkraft von 0 auf 1 übergeht.

Nun müssen wir die Animationseigenschaften definieren. Dazu erstellen wir ein JavaScript-Objekt, das sie zusammenhält.

let cursor_timings = {
  duration: 700, // milliseconds (0.7 seconds)
  iterations: Infinity, // number of times the animation will work
  easing: 'cubic-bezier(0,.26,.44,.93)' // timing-function
}

Wir können der Animation einen Namen geben, so wie hier:

let animation = document.querySelector(".text_cursor").animate([
  // keyframes
], //properties);

Hier ist eine Demo dessen, was wir bisher getan haben:

Großartig! Nun animieren wir das .text_hide Element, das, seinem Namen getreu, den Text versteckt. Wir definieren Animationseigenschaften für dieses Element:

let timings = {
  easing: `steps(${Number(word_len[0])}, end)`,
  delay: 2000, // milliseconds
  duration: 2000, // milliseconds
  fill: 'forwards'
}

Die easing-Eigenschaft definiert, wie sich die Animationsrate im Laufe der Zeit ändert. Hier haben wir die Timing-Funktion steps() verwendet. Diese animiert das Element in diskreten Segmenten anstelle einer glatten, kontinuierlichen Animation – Sie wissen schon, für eine natürlichere Tippbewegung. Zum Beispiel ist die Animationsdauer zwei Sekunden, also animiert die steps()-Funktion das Element in 9 Schritten (ein Schritt für jedes Zeichen in "Animation") über zwei Sekunden, wobei jeder Schritt eine Dauer von 2/9 = 0,22 Sekunden hat.

Das Argument end bewirkt, dass das Element in seinem Anfangszustand verbleibt, bis die Dauer des ersten Schritts abgeschlossen ist. Dieses Argument ist optional und sein Standardwert ist auf end gesetzt. Wenn Sie einen tieferen Einblick in steps() wünschen, können Sie diesen großartigen Artikel von Joni Trythall konsultieren.

Die fill-Eigenschaft ist dasselbe wie die animation-fill-mode-Eigenschaft in CSS. Indem wir ihren Wert auf forwards setzen, verbleibt das Element in der gleichen Position wie durch den letzten Keyframe definiert, nachdem die Animation abgeschlossen ist.

Als Nächstes definieren wir die Keyframes.

let reveal_animation_1 = document.querySelector(".text_hide").animate([
  { left: '0%' },
  { left: `${(100 / text_len) * (word_len[0])}%` }
], timings);

Momentan animieren wir nur ein Wort. Später werden wir sehen, wie man mehrere Wörter animiert.

Der letzte Keyframe ist entscheidend. Nehmen wir an, wir wollen das Wort „Animation“ animieren. Seine Länge ist 9 (da es neun Zeichen gibt) und wir wissen, dass es dank unserer Funktion typing_animation() als Variable gespeichert wird. Die Deklaration 100/text_len ergibt 100/9 oder 11,11%, was die Breite jedes Zeichens im Wort „Animation“ ist. Das bedeutet, dass die Breite jedes Zeichens 11,11% der Breite des gesamten Wortes beträgt. Wenn wir diesen Wert mit der Länge des ersten Wortes (was in unserem Fall 9 ist) multiplizieren, erhalten wir 100%. Ja, wir hätten auch direkt 100% schreiben können, anstatt all diese Dinge zu tun. Aber diese Logik wird uns helfen, wenn wir mehrere Wörter animieren.

Das Ergebnis all dessen ist, dass das .text_hide Element von left: 0% nach left: 100% animiert wird. Mit anderen Worten, die Breite dieses Elements verringert sich von 100% auf 0%, während es sich bewegt.

Wir müssen die gleiche Animation auch dem .text_cursor Element hinzufügen, da wir möchten, dass es sich zusammen mit dem .text_hide Element von links nach rechts bewegt.

Juhu! Wir haben ein einzelnes Wort animiert. Was ist, wenn wir mehrere Wörter animieren wollen? Machen wir das als Nächstes.

Mehrere Wörter animieren

Nehmen wir an, wir haben zwei Wörter, die wir tippen lassen wollen, vielleicht „Typing Animation“. Wir animieren das erste Wort, indem wir dem gleichen Verfahren folgen wie beim letzten Mal. Dieses Mal ändern wir jedoch den Wert der easing-Funktion in den Animationseigenschaften.

let timings = {
  easing: `steps(${Number(word_len[0] + 1)}, end)`,
  delay: 2000,
  duration: 2000,
  fill: 'forwards'
}

Wir haben die Anzahl um einen Schritt erhöht. Warum? Nun, was ist mit einem Leerzeichen nach einem Wort? Das müssen wir berücksichtigen. Aber was, wenn in einem Satz nur ein Wort steht? Dafür schreiben wir eine if-Bedingung, bei der, wenn die Anzahl der Wörter gleich 1 ist, dann steps(${Number(word_len[0])}, end). Wenn die Anzahl der Wörter nicht 1 ist, dann steps(${Number(word_len[0] + 1)}, end).

function typing_animation() {
  let text_element = document.querySelector(".text");
  let text_array = text_element.innerHTML.split("");
  let all_words = text_element.innerHTML.split(" ");
  let text_len = text_array.length;
  const word_len = all_words.map((word) => {
    return word.length;
  })
  let timings = {
    easing: `steps(${Number(word_len[0])}, end)`,
    delay: 2000,
    duration: 2000,
    fill: 'forwards'
  }
  let cursor_timings = {
    duration: 700,
    iterations: Infinity,
    easing: 'cubic-bezier(0,.26,.44,.93)'
  }
  document.querySelector(".text_cursor").animate([
    {
      opacity: 0
    },
    {
      opacity: 0, offset: 0.7
    },
    {
      opacity: 1
    }
  ], cursor_timings);
  if (all_words.length == 1) {
    timings.easing = `steps(${Number(word_len[0])}, end)`;
    let reveal_animation_1 = document.querySelector(".text_hide").animate([
      { left: '0%' },
      { left: `${(100 / text_len) * (word_len[0])}%` }
    ], timings);
    document.querySelector(".text_cursor").animate([
      { left: '0%' },
      { left: `${(100 / text_len) * (word_len[0])}%` }
    ], timings);
  } else {
    document.querySelector(".text_hide").animate([
      { left: '0%' },
      { left: `${(100 / text_len) * (word_len[0] + 1)}%` }
    ], timings);
    document.querySelector(".text_cursor").animate([
      { left: '0%' },
      { left: `${(100 / text_len) * (word_len[0] + 1)}%` }
  ], timings);
  }
}
typing_animation();

Für mehr als ein Wort verwenden wir eine for-Schleife, um jedes Wort zu durchlaufen und zu animieren, das nach dem ersten Wort kommt.

for(let i = 1; i < all_words.length; i++){
  // code
}

Warum haben wir i = 1 gewählt? Weil zu dem Zeitpunkt, zu dem diese for-Schleife ausgeführt wird, das erste Wort bereits animiert wurde.

Als Nächstes greifen wir auf die Länge des jeweiligen Wortes zu:

for(let i = 1; i < all_words.length; i++){
  const single_word_len = word_len[i];
}

Definieren wir auch die Animationseigenschaften für alle Wörter, die nach dem ersten kommen.

// the following code goes inside the for loop
let timings_2 = {
  easing: `steps(${Number(single_word_len + 1)}, end)`,
  delay: (2 * (i + 1) + (2 * i)) * (1000),
  duration: 2000,
  fill: 'forwards'
}

Das Wichtigste hier ist die delay-Eigenschaft. Wie Sie wissen, hatten wir für das erste Wort die delay-Eigenschaft einfach auf zwei Sekunden gesetzt; aber jetzt müssen wir die Verzögerung für die Wörter nach dem ersten Wort dynamisch erhöhen.

Das erste Wort hat eine Verzögerung von zwei Sekunden. Die Dauer seiner Animation beträgt ebenfalls zwei Sekunden, was zusammen vier Sekunden ergibt. Aber es sollte ein Intervall zwischen der Animation des ersten und des zweiten Wortes liegen, um die Animation realistischer zu gestalten. Was wir tun können, ist, zwischen jedem Wort eine zweisegündige Verzögerung hinzuzufügen, anstatt einer. Das ergibt für das zweite Wort eine Gesamtverzögerung von 2 + 2 + 2 oder sechs Sekunden. Ebenso beträgt die Gesamtlaufzeit für die Animation des dritten Wortes 10 Sekunden und so weiter.

Die Funktion für dieses Muster sieht in etwa so aus:

(2 * (i + 1) + (2 * i)) * (1000)

...wobei wir mit 1000 multiplizieren, um Sekunden in Millisekunden umzurechnen.

Länge des WortesDauer, die ein Zeichen zum Animieren benötigt
62/6 = 0,33 Sekunden
82/8 = 0,25 Sekunden
92/9 = 0,22 Sekunden
122/12 = 0,17 Sekunden
* Gesamtdauer beträgt 2 Sekunden

Je länger das Wort, desto schneller wird es enthüllt. Warum? Weil die Dauer gleich bleibt, egal wie lang das Wort ist. Spielen Sie mit den Eigenschaften Dauer und Verzögerung, um alles richtig hinzubekommen.

Erinnern Sie sich, als wir den Wert von steps() unter Berücksichtigung eines Leerzeichens nach einem Wort geändert haben? Auf die gleiche Weise hat das letzte Wort im Satz kein Leerzeichen danach, und daher sollten wir dies in einer weiteren if-Anweisung berücksichtigen.

// the following code goes inside the for loop
if (i == (all_words.length - 1)) {
  timings_2.easing = `steps(${Number(single_word_len)}, end)`;
  let reveal_animation_2 = document.querySelector(".text_hide").animate([
    { left: `${left_instance}%` },
    { left: `${left_instance + ((100 / text_len) * (word_len[i]))}%` }
  ], timings_2);
  document.querySelector(".text_cursor").animate([
    { left: `${left_instance}%` },
    { left: `${left_instance + ((100 / text_len) * (word_len[i]))}%` }
  ], timings_2);
} else {
  document.querySelector(".text_hide").animate([
    { left: `${left_instance}%` },
    { left: `${left_instance + ((100 / text_len) * (word_len[i] + 1))}%` }
  ], timings_2);
  document.querySelector(".text_cursor").animate([
    { left: `${left_instance}%` },
    { left: `${left_instance + ((100 / text_len) * (word_len[i] + 1))}%` }
  ], timings_2);
}

Was ist diese left_instance Variable? Wir haben sie noch nicht besprochen, aber sie ist der wichtigste Teil dessen, was wir tun. Lassen Sie es mich erklären.

0% ist der Anfangswert der left-Eigenschaft des ersten Wortes. Aber der Anfangswert des zweiten Wortes sollte dem *finalen* left-Eigenschaftswert des ersten Wortes entsprechen.

if (i == 1) {
  var left_instance = (100 / text_len) * (word_len[i - 1] + 1);
}

word_len[i - 1] + 1 bezieht sich auf die Länge des vorherigen Wortes (einschließlich eines Leerzeichens).

Wir haben zwei Wörter, „Typing Animation“. Das bedeutet, dass text_len gleich 16 ist, was bedeutet, dass jedes Zeichen 6,25% der Gesamtbreite ist (100/text_len = 100/16), das mit der Länge des ersten Wortes, 7, multipliziert wird. All diese Berechnungen ergeben 43,75, was tatsächlich die Breite des ersten Wortes ist. Mit anderen Worten, die Breite des ersten Wortes beträgt 43,75% der Breite des gesamten Strings. Das bedeutet, dass das zweite Wort dort zu animieren beginnt, wo das erste Wort aufgehört hat.

Zuletzt aktualisieren wir die left_instance Variable am Ende der for-Schleife:

left_instance = left_instance + ((100 / text_len) * (word_len[i] + 1));

Sie können jetzt beliebig viele Wörter in HTML eingeben und die Animation *funktioniert* einfach!

Bonus

Haben Sie bemerkt, dass die Animation nur einmal läuft? Was ist, wenn wir sie unendlich wiederholen wollen? Das ist möglich.


Da haben wir es: eine robustere JavaScript-Version einer Schreibmaschinenanimation. Es ist super cool, dass CSS auch einen Ansatz (oder sogar mehrere Ansätze) hat, um die gleiche Art von Ding zu tun. CSS könnte in einer gegebenen Situation sogar der bessere Ansatz sein. Aber wenn wir Verbesserungen benötigen, die über das hinausgehen, was CSS leisten kann, ist das Einbringen von etwas JavaScript sehr hilfreich. In diesem Fall haben wir Unterstützung für alle Wörter hinzugefügt, unabhängig davon, wie viele Zeichen sie enthalten, und die Möglichkeit, mehrere Wörter zu animieren. Und mit einer kleinen zusätzlichen Verzögerung zwischen den Wörtern erhalten wir eine super natürlich aussehende Animation.

Das war's, ich hoffe, Sie fanden das interessant! Ich melde mich ab.