Animate Calligraphy with SVG

Avatar of Claus Colloseus
Claus Colloseus am

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

Von Zeit zu Zeit taucht auf Stackoverflow die Frage auf, ob es eine Entsprechung zur stroke-dashoffset-Technik gibt, um den SVG stroke zu animieren, die auch für das fill-Attribut funktioniert. Aber bei genauerer Betrachtung ist das, was die Fragen wirklich zu beantworten versuchen, eher etwas wie dies:

Ich habe etwas, das irgendwie eine Linie ist, aber weil es variierende Pinselbreiten hat, wird es in SVG als Füllung eines Pfades definiert.

Wie kann dieser "Pinsel" animiert werden?

Kurz gesagt: Wie animiert man Kalligraphie?

Ein Maskenpfad bedeckt den kalligraphischen Pinsel

Die grundlegende Technik dafür ist relativ einfach: Zeichnen Sie einen zweiten (glatten) Pfad über die Kalligraphie, so dass er der Pinselstrichlinie folgt, und wählen Sie dann die Strichbreite so, dass sie die Kalligraphie überall bedeckt.

Dieser obere Pfad wird als Maske für den darunter liegenden verwendet. Wenden Sie die stroke-dashoffset-Animationstechnik auf den Maskenpfad an. Das Ergebnis wird aussehen, als ob der untere Pfad in Echtzeit direkt auf den Bildschirm "geschrieben" wird.

Dies ist ein Fall für eine mask, nicht für einen clip-path – das würde nicht funktionieren. Clip-Pfade beziehen sich immer auf die Füllfläche eines Pfades, ignorieren aber den stroke.

Die einfachste Variante ist, für den Pfad in der Maske stroke: white zu setzen. Dann wird alles außerhalb des weiß gemalten Bereichs versteckt und alles innerhalb wird unverändert angezeigt.

Siehe den Pen Writing calligraphy: basic example von ccprog (@ccprog) auf CodePen.

Bis hierhin also einfach. Schwierig wird es jedoch, wenn sich die kalligraphischen Linien überlappen. Dies geschieht in einer naiven Implementierung

Siehe den Pen Writing calligraphy: faulty intersection von ccprog (@ccprog) auf CodePen.

An der Schnittstelle enthüllt die Maske einen Teil des kreuzenden Pinsels. Daher muss die Kalligraphie in nicht überlappende Stücke geschnitten werden. Stapeln Sie sie in Zeichenreihenfolge und definieren Sie für jedes Stück separate Maskenpfade.

Der Schnitt auf dem Maskenpfad und der kalligraphische Pinsel müssen übereinstimmen

Der kniffligste Teil ist, den Eindruck zu erwecken, dass die Zeichnung ein einziger zusammenhängender Strich ist. Wenn Sie einen glatten Pfad schneiden, passen die Enden zusammen, *solange beide Pfadtangenten in ihrem gemeinsamen Punkt die gleiche Richtung haben*. Die Strichenden sind senkrecht dazu, und es ist wichtig, dass der Schnitt in der kalligraphischen Linie genau übereinstimmt. Achten Sie darauf, dass alle Pfade aufeinanderfolgende Richtungen haben. Animieren Sie sie nacheinander.

Während viele Linienanimationen mit groben Längenberechnungen für stroke-dasharray auskommen, erfordert dieses Szenario genaue Messungen (obwohl *kleine* Rundungen nicht schaden sollten). Zur Erinnerung: Sie können diese in der DevTools-Konsole mit

document.querySelector('#mask1 path').getTotalLength()

Siehe den Pen Writing calligraphy: divide up intersections von ccprog (@ccprog) auf CodePen.

Der Teil "nacheinander" ist in CSS etwas umständlich zu schreiben. Das beste Muster ist wahrscheinlich, allen Teilanimationen die gleiche Startzeit und Gesamtdauer zu geben und dann dazwischen liegende Keyframes für die stroke-dashoffset-Änderungen zu setzen.

Etwas wie das

@keyframes brush1 {
  0% { stroke-dashoffset: 160; } /* leave static */
  12% { stroke-dashoffset: 160; } /* start of first brush */
  44% { stroke-dashoffset: 0; }   /* end of first brush equals start of second */
  100% { stroke-dashoffset: 0; }   /* leave static */
}

@keyframes brush2 {
  0% { stroke-dashoffset: 210; } /* leave static */
  44% { stroke-dashoffset: 210; } /* start of second brush equals end of first */
  86% { stroke-dashoffset: 0; }   /* end of second brush */
  100% { stroke-dashoffset: 0; }   /* leave static */
}

Weiter unten sehen Sie, wie eine SMIL-Animation eine flüssigere und ausdrucksstärkere Möglichkeit zur Definition des Timings ermöglicht. Wenn wir bei CSS bleiben, können Berechnungen mit Sass ziemlich hilfreich sein, da es einige mathematische Operationen bewältigen kann.

Der Maskenpfad (links) und seine Anwendung (rechts)

Ein vergleichbares Problem tritt auf, wenn der Krümmungsradius des Maskenpfades kleiner wird als die Strichbreite. Während die Animation durch diese Kurve läuft, kann es passieren, dass ein Zwischenzustand ernsthaft schief aussieht.

Die Lösung besteht darin, den Maskenpfad von der kalligraphischen Kurve wegzubewegen. Sie müssen nur darauf achten, dass seine Innenkante den Pinsel weiterhin bedeckt.

Sie können den Maskenpfad sogar zerschneiden und die Enden versetzen, solange die Schnittkanten zusammenpassen.

Der Radius bleibt groß genug

Siehe den Pen Writing calligraphy: divide up intersections von ccprog (@ccprog) auf CodePen.

Und so können Sie sogar etwas Komplexes zeichnen, wie die arabische Kalligraphie in diesem Beispiel

Siehe den Pen Tughra Mahmud II – text animation von ccprog (@ccprog) auf CodePen.

Das Originaldesign, die Tughra des Osmanischen Sultans Mahmud II., stammt von einem unbekannten Kalligraphen des 19. Jahrhunderts. Die vektorisierte Version wurde von Wikipedia-Illustrator Baba66 erstellt. Die Animation ist mein Versuch, die Position der arabischen Buchstaben innerhalb der Zeichnung zu visualisieren. Sie baut auf einer früheren Version von Baba66 auf. Creative Commons Namensnennung – Weitergabe unter gleichen Bedingungen 2.5.

Der folgende Code-Schnipsel zeigt die fortgeschrittene Methode, um die Animationen in der richtigen Reihenfolge und wiederholbar auszuführen.

mask path {
  fill: none;
  stroke: white;
  stroke-width: 16;
}

.brush {
  fill: #0d33f2;
}
<mask id="mask1" maskUnits="userSpaceOnUse">
  <path stroke-dasharray="160 160" stroke-dashoffset="160" d="...">
    <!-- animation begins after document starts and repeats with a click
         on the "repeat" button -->
    <animate id="animate1" attributeName="stroke-dashoffset"
             from="160" to="0" begin="1s;repeat.click" dur="1.6s" />
  </path>
</mask>
<mask id="mask2" maskUnits="userSpaceOnUse">
  <path stroke-dasharray="350 350" stroke-dashoffset="350" d="...">
    <!-- animation begins at the end of the previous one -->
    <animate id="animate2" attributeName="stroke-dashoffset"
             from="350" to="0" begin="animate1.end" dur="3.5s" />
  </path>
</mask>
<!-- more masks... -->
<mask id="mask15" maskUnits="userSpaceOnUse">
  <path stroke-dasharray="230 230" stroke-dashoffset="230" d="...">
    <!-- insert an artificial pause between the animations, as if the
         brush had been lifted -->
    <animate id="animate15" attributeName="stroke-dashoffset"
             from="230" to="0" begin="animate14.end+0.5s" dur="2.3s" />
  </path>
</mask>

<g class="brush">
  <path id="brush1" d="...">
    <!-- The mask is only applied  after document starts/repeats and until
         the animation has run. This makes sure the brushes are visible in
         renderers that do not support SMIL -->
    <set attributeName="mask" to="url(#mask1)"
         begin="0s;repeat.click" end="animate1.end;indefinite" />
  </path>
  <path id="brush2" d="...">
    <set attributeName="mask" to="url(#mask2)"
         begin="0s;repeat.click" end="animate2.end;indefinite" />
  </path>
  <!-- more paths... -->
  <path id="brush15" d="...">
    <set attributeName="mask" to="url(#mask2)"
         begin="0s;repeat.click" end="animate15.end;indefinite" />
  </path>
</g>

Im Gegensatz zu den anderen Beispielen, die wir uns angesehen haben, verwendet diese Animation SMIL, was bedeutet, dass sie nicht in Internet Explorer und Edge funktioniert.


Dieser Artikel ist auf Browser…​unplugged auf Deutsch veröffentlicht.