In meinem vorherigen Artikel habe ich gezeigt, wie man mit Vanilla JavaScript sanft von einem Zustand in einen anderen übergeht. Sehen Sie sich diesen unbedingt zuerst an, da ich auf einige Dinge verweisen werde, die ich dort im Detail erklärt habe, wie z. B. Demos als Beispiele, Formeln für verschiedene Timing-Funktionen oder wie man die Timing-Funktion nicht umkehrt, wenn man vom Endzustand eines Übergangs zum Anfangszustand zurückkehrt.
Das letzte Beispiel zeigte, wie man die Form eines Mundes von traurig zu froh macht, indem man das d-Attribut des path ändert, mit dem dieser Mund gezeichnet wurde.
Die Manipulation von Pfaddaten kann auf die nächste Stufe gehoben werden, um interessantere Ergebnisse zu erzielen, wie z. B. einen Stern, der sich in ein Herz verwandelt.

Die Idee
Beide bestehen aus fünf kubischen Bézier-Kurven. Die interaktive Demo unten zeigt die einzelnen Kurven und die Punkte, an denen diese Kurven verbunden sind. Das Klicken auf eine Kurve oder einen Punkt hebt sie hervor, ebenso wie ihre entsprechenden Kurven/Punkte der anderen Form.
Sehen Sie sich den Pen von thebabydino (@thebabydino) auf CodePen an.
Beachten Sie, dass alle diese Kurven als kubische Kurven erstellt werden, auch wenn bei einigen davon die beiden Kontrollpunkte übereinstimmen.
Die Formen sowohl des Sterns als auch des Herzens sind ziemlich simpel und unrealistisch, aber sie reichen aus.
Der Startcode
Wie im Gesichtsanimationsbeispiel zu sehen ist, wähle ich oft, solche Formen mit Pug zu generieren. Hier, da diese Pfaddaten, die wir generieren, auch mit JavaScript für den Übergang manipuliert werden müssen, scheint die Wahl von reinem JavaScript, einschließlich der Berechnung der Koordinaten und ihrer Platzierung im d-Attribut, die beste Option zu sein.
Das bedeutet, wir müssen nicht viel Markup schreiben
<svg>
<path id='shape'/>
</svg>
In Bezug auf JavaScript beginnen wir damit, das SVG-Element und das path-Element abzurufen – dies ist die Form, die sich von einem Stern zu einem Herz und zurück verwandelt. Wir setzen auch ein viewBox-Attribut auf das SVG-Element, sodass seine Abmessungen entlang der beiden Achsen gleich sind und der Punkt (0,0) genau in der Mitte liegt. Das bedeutet, dass die Koordinaten der oberen linken Ecke (-.5*D,-.5*D) sind, wobei D der Wert für die viewBox-Abmessungen ist. Und zu guter Letzt erstellen wir ein Objekt, um Informationen über den Anfangs- und Endzustand des Übergangs zu speichern und darüber, wie von den interpolierten Werten zu den tatsächlichen Attributwerten gelangt wird, die wir auf unsere SVG-Form anwenden müssen.
const _SVG = document.querySelector('svg'),
_SHAPE = document.getElementById('shape'),
D = 1000,
O = { ini: {}, fin: {}, afn: {} };
(function init() {
_SVG.setAttribute('viewBox', [-.5*D, -.5*D, D, D].join(' '));
})();
Nachdem wir dies hinter uns gebracht haben, können wir zum interessanteren Teil übergehen!
Die Geometrie der Formen
Die Anfangskoordinaten der Endpunkte und Kontrollpunkte sind diejenigen, für die wir den Stern erhalten, und die Endkoordinaten sind diejenigen, für die wir das Herz erhalten. Der Bereich für jede Koordinate ist die Differenz zwischen ihrem Endwert und ihrem Anfangswert. Hier drehen wir die Form auch während der Verwandlung, weil wir möchten, dass der Stern nach oben zeigt, und wir ändern die fill, um vom goldenen Stern zum purpurroten Herz zu wechseln.
In Ordnung, aber wie bekommen wir die Koordinaten der End- und Kontrollpunkte in den beiden Fällen?
Stern
Im Fall des Sterns beginnen wir mit einem regelmäßigen Pentagramm. Die Endpunkte unserer Kurven befinden sich an den Schnittpunkten der Pentagrammkanten, und wir verwenden die Eckpunkte des Pentagramms als Kontrollpunkte.
Das Ermitteln der Eckpunkte unseres regelmäßigen Pentagramms ist ziemlich einfach, gegeben den Radius (oder Durchmesser) seines Umkreises, den wir als Bruchteil der viewBox-Größe unseres SVG wählen (hier als Quadrat angenommen, wir wollen hier keine enge Packung). Aber wie bekommen wir ihre Schnittpunkte?
Zunächst betrachten wir das kleine Pentagon, das im folgenden Bild im Pentagramm hervorgehoben ist. Da das Pentagramm regelmäßig ist, ist auch das kleine Pentagon, dessen Eckpunkte mit den Schnittpunkten der Pentagrammkanten zusammenfallen, regelmäßig. Es hat auch den gleichen Inkreis wie das Pentagramm und daher den gleichen Inradius.
Wenn wir also den Inradius des Pentagramms berechnen, haben wir auch den Inradius des kleinen Pentagons, der uns zusammen mit dem Zentralwinkel, der einer Kante eines regelmäßigen Fünfecks entspricht, ermöglicht, den Umkreisradius dieses Fünfecks zu ermitteln, was uns wiederum ermöglicht, seine Eckpunktkoordinaten zu berechnen, und das sind genau die Schnittpunkte der Pentagrammkanten und die Endpunkte unserer kubischen Bézier-Kurven.
Unser regelmäßiges Pentagramm wird durch das Schläfli-Symbol {5/2} dargestellt, was bedeutet, dass es 5 Eckpunkte hat und, gegeben diese 5 Eckpunkte, die gleichmäßig auf seinem Umkreis verteilt sind, 360°/5 = 72° voneinander entfernt sind, wir vom ersten ausgehen, den nächsten Punkt auf dem Kreis überspringen und mit dem zweiten verbinden (das ist die Bedeutung der 2 im Symbol; 1 würde ein Fünfeck beschreiben, da wir keinen Punkt überspringen, sondern zum ersten verbinden). Und so weiter – wir überspringen immer den Punkt direkt danach.
Wählen Sie in der interaktiven Demo unten entweder Fünfeck oder Pentagramm, um zu sehen, wie sie konstruiert werden.
Sehen Sie sich den Pen von thebabydino (@thebabydino) auf CodePen an.
Auf diese Weise erhalten wir, dass der Zentralwinkel, der einer Kante des regelmäßigen Pentagramms entspricht, doppelt so groß ist wie der, der einem regelmäßigen Fünfeck mit denselben Eckpunkten entspricht. Wir haben 1·(360°/5) = 1·72° = 72° (oder 1·(2·π/5) im Bogenmaß) für das Fünfeck gegenüber 2·(360°/5) = 2·72° = 144° (2·(2·π/5) im Bogenmaß) für das Pentagramm. Im Allgemeinen ist für ein regelmäßiges Polygon (ob konvex oder Sternpolygon spielt keine Rolle) mit dem Schläfli-Symbol {p,q} der Zentralwinkel, der einer seiner Kanten entspricht, q·(360°/p) (q·(2·π/p) im Bogenmaß).
144°) vs. Fünfeck (rechts, 72°) (live).Wir kennen auch den Umkreisradius des Pentagramms, von dem wir sagten, dass wir ihn als Bruchteil der Größe des quadratischen viewBox wählen. Das bedeutet, wir können den Inradius des Pentagramms (der gleich dem des kleinen Fünfecks ist) aus einem rechtwinkligen Dreieck erhalten, in dem wir die Hypotenuse (es ist der Umkreisradius des Pentagramms) und einen spitzen Winkel (die Hälfte des Zentralwinkels, der einer Pentagrammkante entspricht) kennen.
Der Kosinus der Hälfte des Zentralwinkels ist der Inradius geteilt durch den Umkreisradius, was uns ergibt, dass der Inradius der Umkreisradius multipliziert mit diesem Kosinuswert ist.
Jetzt, da wir den Inradius des kleinen regelmäßigen Fünfecks innerhalb unseres Pentagramms haben, können wir seinen Umkreisradius aus einem ähnlichen rechtwinkligen Dreieck berechnen, das den Umkreisradius als Hypotenuse, die Hälfte des Zentralwinkels als einen der spitzen Winkel und den Inradius als die Kathete neben diesem spitzen Winkel hat.
Die folgende Abbildung hebt ein rechtwinkliges Dreieck hervor, das aus einem Umkreisradius eines regelmäßigen Fünfecks, seinem Inradius und der Hälfte einer Kante gebildet wird. Aus diesem Dreieck können wir den Umkreisradius berechnen, wenn wir den Inradius und den Zentralwinkel kennen, der einer Fünfeckkante entspricht, da der spitze Winkel zwischen diesen beiden Radien die Hälfte dieses Zentralwinkels ist.
Denken Sie daran, dass in diesem Fall der Zentralwinkel nicht derselbe ist wie beim Pentagramm, er ist die Hälfte davon (360°/5 = 72°).
Gut, jetzt, da wir diesen Radius haben, können wir alle gewünschten Koordinaten erhalten. Es sind die Koordinaten von Punkten, die in gleichen Winkeln auf zwei Kreisen verteilt sind. Wir haben 5 Punkte auf dem äußeren Kreis (dem Umkreis unseres Pentagramms) und 5 auf dem inneren (dem Umkreis des kleinen Fünfecks). Das sind insgesamt 10 Punkte, mit Winkeln von 360°/10 = 36° zwischen den radialen Linien, auf denen sie liegen.
Wir kennen die Radien beider Kreise. Der Radius des äußeren ist der Umkreisradius des regelmäßigen Pentagramms, den wir als einen beliebigen Bruchteil der viewBox-Dimension wählen (.5 oder .25 oder .32 oder irgendeinen Wert, von dem wir glauben, dass er am besten funktioniert). Der Radius des inneren ist der Umkreisradius des kleinen regelmäßigen Fünfecks, das im Pentagramm gebildet wird, den wir als Funktion des Zentralwinkels, der einer seiner Kanten entspricht, und seines Inradius berechnen können, der dem des Pentagramms gleich ist und den wir daher aus dem Umkreisradius des Pentagramms und dem Zentralwinkel, der einer Pentagrammkante entspricht, berechnen können.
An diesem Punkt können wir also die Pfaddaten generieren, die unseren Stern zeichnen, er hängt von nichts Unbekanntem ab.
Lassen Sie uns das also tun und alles oben Genannte in Code umsetzen!
Wir beginnen mit der Erstellung einer Funktion getStarPoints(f), die von einem beliebigen Faktor (f) abhängt, der uns hilft, den Umkreisradius des Pentagramms aus der viewBox-Größe zu erhalten. Diese Funktion gibt ein Array von Koordinaten zurück, die wir später für die Interpolation verwenden.
Innerhalb dieser Funktion berechnen wir zunächst die konstanten Dinge, die sich im Verlauf nicht ändern – den Umkreisradius des Pentagramms (Radius des äußeren Kreises), die zentralen (Basis-)Winkel, die einer Kante eines regelmäßigen Pentagramms und Polygons entsprechen, den Inradius, den sich das Pentagramm und das innere Fünfeck teilen, dessen Eckpunkte die Punkte sind, an denen sich die Pentagrammkanten kreuzen, den Umkreisradius dieses inneren Fünfecks und schließlich die Gesamtzahl der eindeutigen Punkte, deren Koordinaten wir berechnen müssen, und den Basiswinkel für diese Verteilung.
Danach berechnen wir innerhalb einer Schleife die Koordinaten der gewünschten Punkte und fügen sie dem Koordinatenarray hinzu.
const P = 5; /* number of cubic curves/ polygon vertices */
function getStarPoints(f = .5) {
const RCO = f*D /* outer (pentagram) circumradius */,
BAS = 2*(2*Math.PI/P) /* base angle for star poly */,
BAC = 2*Math.PI/P /* base angle for convex poly */,
RI = RCO*Math.cos(.5*BAS) /*pentagram/ inner pentagon inradius */,
RCI = RI/Math.cos(.5*BAC) /* inner pentagon circumradius */,
ND = 2*P /* total number of distinct points we need to get */,
BAD = 2*Math.PI/ND /* base angle for point distribution */,
PTS = [] /* array we fill with point coordinates */;
for(let i = 0; i < ND; i++) {}
return PTS;
}
Um die Koordinaten unserer Punkte zu berechnen, verwenden wir den Radius des Kreises, auf dem sie liegen, und den Winkel der radialen Linie, die sie mit dem Ursprung verbindet, in Bezug auf die horizontale Achse, wie in der folgenden interaktiven Demo veranschaulicht (ziehen Sie den Punkt, um zu sehen, wie sich seine kartesischen Koordinaten ändern).
Sehen Sie sich den Pen von thebabydino (@thebabydino) auf CodePen an.
In unserem Fall ist der aktuelle Radius der Radius des äußeren Kreises (Umkreisradius des Pentagramms RCO) für gerade Indexpunkte (0, 2, …) und der Radius des inneren Kreises (Umkreisradius des inneren Fünfecks RCI) für ungerade Indexpunkte (1, 3, …), während der Winkel der radialen Linie, die den aktuellen Punkt mit dem Ursprung verbindet, der Punktindex (i) multipliziert mit dem Basiswinkel für die Punktverteilung (BAD, der in unserem speziellen Fall 36° oder π/10 ist) ist.
Innerhalb der Schleife haben wir also
for(let i = 0; i < ND; i++) {
let cr = i%2 ? RCI : RCO,
ca = i*BAD,
x = Math.round(cr*Math.cos(ca)),
y = Math.round(cr*Math.sin(ca));
}
Da wir einen ziemlich großen Wert für die Größe der viewBox gewählt haben, können wir die Koordinatenwerte sicher runden, damit unser Code übersichtlicher wird, ohne Dezimalstellen.
Beim Hinzufügen dieser Koordinaten zum Punkte-Array tun wir dies zweimal, wenn wir uns auf dem äußeren Kreis befinden (bei geraden Indizes), da wir dort tatsächlich zwei überlappende Kontrollpunkte haben, aber nur für den Stern. Daher müssen wir jeden dieser überlappenden Punkte an verschiedene Positionen verschieben, um das Herz zu erhalten.
for(let i = 0; i < ND; i++) {
/* same as before */
PTS.push([x, y]);
if(!(i%2)) PTS.push([x, y]);
}
Als Nächstes legen wir Daten in unser Objekt O. Für das Pfaddaten-Attribut (d) speichern wir das Array von Punkten, das wir durch Aufrufen der obigen Funktion erhalten, als Anfangswert. Wir erstellen auch eine Funktion zur Generierung des tatsächlichen Attributwerts (in diesem Fall die Pfaddatenzeichenfolge – Einfügen von Befehlen zwischen den Koordinatenpaaren, damit der Browser weiß, was er mit diesen Koordinaten tun soll). Schließlich nehmen wir jedes Attribut, für das wir Daten gespeichert haben, und setzen seinen Wert auf den Wert, der von der zuvor genannten Funktion zurückgegeben wird.
(function init() {
/* same as before */
O.d = {
ini: getStarPoints(),
afn: function(pts) {
return pts.reduce((a, c, i) => {
return a + (i%3 ? ' ' : 'C') + c
}, `M${pts[pts.length - 1]}`)
}
};
for(let p in O) _SHAPE.setAttribute(p, O[p].afn(O[p].ini))
})();
Das Ergebnis ist im folgenden Pen zu sehen.
Sehen Sie sich den Pen von thebabydino (@thebabydino) auf CodePen an.
Dies ist ein vielversprechender Anfang. Wir möchten jedoch, dass die erste Spitze des generierenden Pentagramms nach unten zeigt und die erste Spitze des resultierenden Sterns nach oben. Derzeit zeigen beide nach rechts. Das liegt daran, dass wir bei 0° (3 Uhr) beginnen. Um also bei 6 Uhr zu beginnen, addieren wir 90° (π/2 im Bogenmaß) zu jedem aktuellen Winkel in der Funktion getStarPoints().
ca = i*BAD + .5*Math.PI
Dadurch zeigt die erste Spitze des generierenden Pentagramms und des resultierenden Sterns nach unten. Um den Stern zu drehen, müssen wir sein transform-Attribut auf eine halbe Kreisdrehung setzen. Dazu setzen wir zunächst einen Anfangsdrehwinkel von -180. Danach setzen wir die Funktion, die den tatsächlichen Attributwert generiert, auf eine Funktion, die einen String aus einem Funktionsnamen und einem Argument generiert.
function fnStr(fname, farg) { return `${fname}(${farg})` };
(function init() {
/* same as before */
O.transform = { ini: -180, afn: (ang) => fnStr('rotate', ang) };
/* same as before */
})();
Wir geben unserem Stern auf ähnliche Weise auch eine goldene fill. Wir setzen ein RGB-Array als Anfangswert im Fall von fill und verwenden eine ähnliche Funktion, um den tatsächlichen Attributwert zu generieren.
(function init() {
/* same as before */
O.fill = { ini: [255, 215, 0], afn: (rgb) => fnStr('rgb', rgb) };
/* same as before */
})();
Wir haben jetzt einen schönen goldenen SVG-Stern, der aus fünf kubischen Bézier-Kurven besteht.
Sehen Sie sich den Pen von thebabydino (@thebabydino) auf CodePen an.
Herz
Da wir den Stern haben, sehen wir uns als Nächstes an, wie wir das Herz bekommen können!
Wir beginnen mit zwei sich schneidenden Kreisen gleicher Radien, beide ein Bruchteil (sagen wir für den Moment .25) der viewBox-Größe. Diese Kreise schneiden sich so, dass das Segment, das ihre Mittelpunkte verbindet, auf der x-Achse liegt und das Segment, das ihre Schnittpunkte verbindet, auf der y-Achse liegt. Wir nehmen auch an, dass diese beiden Segmente gleich sind.
Als Nächstes zeichnen wir Durchmesser durch den oberen Schnittpunkt und dann Tangenten durch die gegenüberliegenden Punkte dieser Durchmesser. Diese Tangenten schneiden sich auf der y-Achse.
Der obere Schnittpunkt und die diametral gegenüberliegenden Punkte ergeben drei der fünf benötigten Endpunkte. Die anderen beiden Endpunkte teilen die äußeren Halbkreisbögen in zwei gleiche Teile, was uns vier Viertelkreisbögen ergibt.
Beide Kontrollpunkte für die untere Kurve fallen mit dem Schnittpunkt der beiden zuvor gezeichneten Tangenten zusammen. Aber was ist mit den anderen vier Kurven? Wie können wir von Kreisbögen zu kubischen Bézier-Kurven gelangen?
Wir haben keine kubische Bézier-Kurven-Entsprechung für einen Viertelkreisbogen, aber wir können eine sehr gute Annäherung finden, wie in diesem Artikel erklärt.
Der Kernpunkt ist, dass wir von einem Viertelkreisbogen mit Radius R ausgehen und Tangenten an die Endpunkte dieses Bogens (N und Q) zeichnen. Diese Tangenten schneiden sich bei P. Das Viereck ONPQ hat alle Winkel gleich 90° (oder π/2), drei davon per Konstruktion (O entspricht einem 90° Bogen und die Tangente an einen Punkt dieses Kreises ist immer senkrecht zur radialen Linie zum selben Punkt) und der letzte per Berechnung (die Summe der Winkel in einem Viereck beträgt immer 360° und die anderen drei Winkel ergeben 270°). Das macht ONPQ zu einem Rechteck. Aber ONPQ hat auch zwei aufeinanderfolgende gleiche Kanten (ON und OQ sind beide radiale Linien mit einer Länge von R), was es zu einem Quadrat der Kante R macht. Die Längen von NP und QP sind also ebenfalls gleich R.
Die Kontrollpunkte der kubischen Kurve, die unseren Bogen annähert, liegen auf den Tangentenlinien NP und QP, im Abstand von C·R von den Endpunkten, wobei C die Konstante ist, die der zuvor verlinkte Artikel mit .551915 berechnet.
Angesichts all dessen können wir nun mit der Berechnung der Koordinaten der Endpunkte und Kontrollpunkte der kubischen Kurven beginnen, aus denen unser Stern besteht.
Aufgrund der Art und Weise, wie wir dieses Herz konstruiert haben, ist TO0SO1 (siehe Abbildung unten) ein Quadrat, da es alle gleichen Kanten hat (alle sind Radien eines unserer beiden gleich großen Kreise) und seine Diagonalen per Konstruktion gleich sind (wir sagten, dass die Entfernung zwischen den Mittelpunkten gleich der Entfernung zwischen den Schnittpunkten ist). Hier ist O der Schnittpunkt der Diagonalen und OT ist die Hälfte der ST-Diagonale. T und S liegen auf der y-Achse, also ist ihre x-Koordinate 0. Ihre y-Koordinate im Betrag entspricht dem Segment OT, das die Hälfte der Diagonale ist (ebenso wie das Segment OS).
Wir können jedes Quadrat der Kantenlänge l in zwei gleiche rechtwinklige gleichschenklige Dreiecke teilen, bei denen die Katheten mit den Quadratkanten und die Hypotenuse mit einer Diagonale zusammenfallen.
Mithilfe eines dieser rechtwinkligen Dreiecke können wir die Hypotenuse (und damit die Quadratdiagonale) mit dem Satz des Pythagoras berechnen: d² = l² + l². Dies ergibt die Quadratdiagonale als Funktion der Kante d = √(2∙l) = l∙√2 (umgekehrt ist die Kante als Funktion der Diagonale l = d/√2). Es bedeutet auch, dass die Hälfte der Diagonale d/2 = (l∙√2)/2 = l/√2 ist.
Wenn wir dies auf unser Quadrat TO0SO1 der Kantenlänge R anwenden, erhalten wir, dass die y-Koordinate von T (die im Betrag der Hälfte dieser Quadratdiagonale entspricht) -R/√2 ist und die y-Koordinate von S R/√2 ist.
Ähnlich liegen die Punkte Ok auf der x-Achse, sodass ihre y-Koordinaten 0 sind, während ihre x-Koordinaten durch die halbe Diagonale OOk gegeben sind: ±R/√2.
Dass TO0SO1 ein Quadrat ist, bedeutet auch, dass alle seine Winkel 90° (π/2 im Bogenmaß) Winkel sind.
In der obigen Abbildung sind die Segmente TBk Durchmessersegmente, was bedeutet, dass die Bögen TBk Halbkreise oder 180° Bögen sind und wir sie mit den Punkten Ak in zwei gleiche Hälften geteilt haben, was uns zwei gleiche 90° Bögen ergibt – TAk und AkBk, die zwei gleichen 90° Winkeln entsprechen, ∠TOkAk und ∠AkOkBk.
Angesichts der Tatsache, dass die Winkel ∠TOkS 90° Winkel sind und die Winkel ∠TOkAk ebenfalls 90° Winkel per Konstruktion sind, ergibt sich, dass die Segmente SAk ebenfalls Durchmessersegmente sind. Dies führt dazu, dass in den Vierecken TAkBkS die Diagonalen TBk und SAk senkrecht, gleich sind und sich in der Mitte schneiden (TOk, OkBk, SOk und OkAk sind alle gleich dem ursprünglichen Kreisradius R). Das bedeutet, die Vierecke TAkBkS sind Quadrate, deren Diagonalen 2∙R betragen.
Von hier aus können wir die Kantenlänge der Vierecke TAkBkS als 2∙R/√2 = R∙√2 ermitteln. Da alle Winkel eines Quadrats 90° sind und die Kante TS mit der vertikalen Achse zusammenfällt, bedeutet dies, dass die Kanten TAk und SBk horizontal und parallel zur x-Achse sind und ihre Länge uns die x-Koordinaten der Punkte Ak und Bk gibt: ±R∙√2.
Da TAk und SBk horizontale Segmente sind, entsprechen die y-Koordinaten der Punkte Ak und Bk denen der Punkte T (-R/√2) und S (R/√2).
Eine weitere Erkenntnis ist, dass, da TAkBkS Quadrate sind, AkBk parallel zu TS sind, das auf der y-Achse (vertikal) liegt, daher sind die Segmente AkBk vertikal. Zusätzlich, da die x-Achse parallel zu den Segmenten TAk und SBk ist und die TS-Achse schneidet, schneidet sie folglich auch die Segmente AkBk in der Mitte.
Nun kommen wir zu den Kontrollpunkten.
Wir beginnen mit den zusammenfallenden Kontrollpunkten für die untere Kurve.
Das Viereck TB0CB1 hat alle Winkel gleich 90° (∠T, da TO0SO1 ein Quadrat ist; ∠Bk per Konstruktion, da die Segmente BkC Tangenten an den Kreis bei Bk sind und daher senkrecht zu den radialen Linien OkBk an diesem Punkt; und schließlich muss ∠C 90° sein, da die Summe der Winkel in einem Viereck 360° beträgt und die anderen drei Winkel zusammen 270° ergeben), was es zu einem Rechteck macht. Es hat auch zwei aufeinanderfolgende gleiche Kanten – TB0 und TB1 sind beides Durchmesser der ursprünglichen Quadrate und daher beide gleich 2∙R. All dies macht es zu einem Quadrat der Kante 2∙R.
Von hier aus können wir seine Diagonale TC ermitteln – sie ist 2∙R∙√2. Da C auf der y-Achse liegt, ist seine x-Koordinate 0. Seine y-Koordinate ist die Länge des Segments OC. Das Segment OC ist das Segment TC minus das Segment OT: 2∙R∙√2 - R/√2 = 4∙R/√2 - R/√2 = 3∙R/√2.
Somit haben wir nun die Koordinaten der beiden zusammenfallenden Kontrollpunkte für die untere Kurve: (0,3∙R/√2).
Um die Koordinaten der Kontrollpunkte für die anderen Kurven zu erhalten, zeichnen wir Tangenten durch ihre Endpunkte und ermitteln die Schnittpunkte dieser Tangenten bei Dk und Ek.
In den Vierecken TOkAkDk haben wir, dass alle Winkel 90° (rechte) Winkel sind, drei davon per Konstruktion (∠DkTOk und ∠DkAkOk sind die Winkel zwischen den radialen und Tangentenlinien bei T und Ak bzw., während ∠TOkAk die Winkel sind, die den Viertelkreisbögen TAk entsprechen) und der vierte per Berechnung (die Summe der Winkel in einem Viereck beträgt 360° und die anderen drei ergeben 270°). Dies macht TOkAkDk zu Rechtecken. Da sie zwei aufeinanderfolgende gleiche Kanten haben (OkT und OkAk sind radiale Segmente der Länge R), sind sie auch Quadrate.
Das bedeutet, die Diagonalen TAk und OkDk sind R∙√2 lang. Wir wissen bereits, dass TAk horizontal sind und da die Diagonalen eines Quadrats senkrecht zueinander stehen, ergibt sich, dass die Segmente OkDk vertikal sind. Das bedeutet, die Punkte Ok und Dk haben die gleiche x-Koordinate, die wir bereits für Ok als ±R/√2 berechnet haben. Da wir die Länge von OkDk kennen, können wir auch die y-Koordinaten ermitteln – sie sind die Diagonallänge (R∙√2) mit einem Minus davor.
Ähnlich haben wir in den Vierecken AkOkBkEk, dass alle Winkel 90° (rechte) Winkel sind, drei davon durch Konstruktion (∠EkAkOk und ∠EkBkOk sind die Winkel zwischen der Radialen und der Tangente bei Ak bzw. Bk, während ∠AkOkBk die Winkel sind, die den Viertelkreisbögen AkBk entsprechen) und der vierte durch Berechnung (die Summe der Winkel in einem Viereck beträgt 360° und die anderen drei ergeben zusammen 270°). Das macht AkOkBkEk zu Rechtecken. Da sie zwei aufeinanderfolgende gleich lange Kanten haben (OkAk und OkBk sind radiale Segmente der Länge R), sind sie auch Quadrate.
Von hier aus erhalten wir, dass die Diagonalen AkBk und OkEk R∙√2 lang sind. Wir wissen, dass die Segmente AkBk vertikal sind und von der horizontalen Achse halbiert werden, was bedeutet, dass die Segmente OkEk auf dieser Achse liegen und die y-Koordinaten der Punkte Ek 0 sind. Da die x-Koordinaten der Punkte Ok ±R/√2 sind und die Segmente OkEk R∙√2 lang sind, können wir auch die der Punkte Ek berechnen – sie sind ±3∙R/√2.
Nun gut, aber diese Schnittpunkte für die Tangenten sind nicht die Kontrollpunkte, die wir für die Kreisbogen-Approximationen benötigen. Die Kontrollpunkte, die wir wollen, liegen auf den Segmenten TDk, AkDk, AkEk und BkEk etwa 55% (dieser Wert wird durch die Konstante C aus dem zuvor erwähnten Artikel gegeben) von den Endpunkten der Kurve (T, Ak, Bk) entfernt. Das bedeutet, dass die Segmente von den Endpunkten zu den Kontrollpunkten C∙R lang sind.
In dieser Situation sind die Koordinaten unserer Kontrollpunkte 1 - C derer der Endpunkte (T, Ak und Bk) plus C derer der Punkte, an denen sich die Tangenten an den Endpunkten schneiden (Dk und Ek).
Also, legen wir das alles in JavaScript-Code!
Genau wie im Sternfall beginnen wir mit einer Funktion getStarPoints(f), die von einem beliebigen Faktor (f) abhängt, der uns hilft, den Radius der Hilfskreise aus der Größe des viewBox zu erhalten. Diese Funktion gibt auch ein Array von Koordinaten zurück, das wir später für die Interpolation verwenden.
Im Inneren berechnen wir die Dinge, die sich während der Funktion nicht ändern. Zuerst der Radius der Hilfskreise. Davon ausgehend, die halbe Diagonale der kleinen Quadrate, deren Kantenlänge diesem Radius des Hilfskreises entspricht, eine halbe Diagonale, die auch der Umkreisradius dieser Quadrate ist. Danach die Koordinaten der Endpunkte unserer kubischen Kurven (die Punkte T, Ak, Bk), im Absolutwert für die entlang der horizontalen Achse. Dann gehen wir zu den Koordinaten der Punkte über, an denen sich die Tangenten durch die Endpunkte schneiden (die Punkte C, Dk, Ek). Diese fallen entweder mit den Kontrollpunkten (C) zusammen oder können uns helfen, die Kontrollpunkte zu erhalten (dies ist der Fall für Dk und Ek).
function getHeartPoints(f = .25) {
const R = f*D /* helper circle radius */,
RC = Math.round(R/Math.SQRT2) /* circumradius of square of edge R */,
XT = 0, YT = -RC /* coords of point T */,
XA = 2*RC, YA = -RC /* coords of A points (x in abs value) */,
XB = 2*RC, YB = RC /* coords of B points (x in abs value) */,
XC = 0, YC = 3*RC /* coords of point C */,
XD = RC, YD = -2*RC /* coords of D points (x in abs value) */,
XE = 3*RC, YE = 0 /* coords of E points (x in abs value) */;
}
Die interaktive Demo unten zeigt die Koordinaten dieser Punkte bei Klick
Siehe den Pen von thebabydino (@thebabydino) auf CodePen.
Nun können wir auch die Kontrollpunkte aus den Endpunkten und den Punkten, an denen sich die Tangenten durch die Endpunkte schneiden, ermitteln
function getHeartPoints(f = .25) {
/* same as before */
const /* const for cubic curve approx of quarter circle */
C = .551915,
CC = 1 - C,
/* coords of ctrl points on TD segs */
XTD = Math.round(CC*XT + C*XD), YTD = Math.round(CC*YT + C*YD),
/* coords of ctrl points on AD segs */
XAD = Math.round(CC*XA + C*XD), YAD = Math.round(CC*YA + C*YD),
/* coords of ctrl points on AE segs */
XAE = Math.round(CC*XA + C*XE), YAE = Math.round(CC*YA + C*YE),
/* coords of ctrl points on BE segs */
XBE = Math.round(CC*XB + C*XE), YBE = Math.round(CC*YB + C*YE);
/* same as before */
}
Als Nächstes müssen wir die relevanten Koordinaten in ein Array packen und dieses Array zurückgeben. Im Fall des Sterns begannen wir mit der unteren Kurve und gingen dann im Uhrzeigersinn vor, also machen wir dasselbe hier. Für jede Kurve fügen wir zwei Sätze von Koordinaten für die Kontrollpunkte und dann einen Satz für den Punkt hinzu, an dem die aktuelle Kurve endet.
Siehe den Pen von thebabydino (@thebabydino) auf CodePen.
Beachten Sie, dass im Fall der ersten (unteren) Kurve die beiden Kontrollpunkte zusammenfallen, sodass wir denselben Koordinatenpaar zweimal hinzufügen. Der Code sieht nicht annähernd so gut aus wie im Fall des Sterns, aber er muss ausreichen
return [
[XC, YC], [XC, YC], [-XB, YB],
[-XBE, YBE], [-XAE, YAE], [-XA, YA],
[-XAD, YAD], [-XTD, YTD], [XT, YT],
[XTD, YTD], [XAD, YAD], [XA, YA],
[XAE, YAE], [XBE, YBE], [XB, YB]
];
Wir können nun unsere Stern-Demo nehmen und die Funktion getHeartPoints() für den Endzustand verwenden, ohne Rotation und stattdessen mit einer karminroten fill. Dann setzen wir den aktuellen Zustand auf die Endform, nur damit wir das Herz sehen können
function fnStr(fname, farg) { return `${fname}(${farg})` };
(function init() {
_SVG.setAttribute('viewBox', [-.5*D, -.5*D, D, D].join(' '));
O.d = {
ini: getStarPoints(),
fin: getHeartPoints(),
afn: function(pts) {
return pts.reduce((a, c, i) => {
return a + (i%3 ? ' ' : 'C') + c
}, `M${pts[pts.length - 1]}`)
}
};
O.transform = {
ini: -180,
fin: 0,
afn: (ang) => fnStr('rotate', ang)
};
O.fill = {
ini: [255, 215, 0],
fin: [220, 20, 60],
afn: (rgb) => fnStr('rgb', rgb)
};
for(let p in O) _SHAPE.setAttribute(p, O[p].afn(O[p].fin))
})();
Das ergibt ein schön aussehendes Herz
Siehe den Pen von thebabydino (@thebabydino) auf CodePen.
Sicherstellung einer konsistenten Ausrichtung der Formen
Wenn wir jedoch die beiden Formen übereinander legen, ohne fill oder transform, nur mit einem stroke, sehen wir, dass die Ausrichtung ziemlich schlecht aussieht
Siehe den Pen von thebabydino (@thebabydino) auf CodePen.
Der einfachste Weg, dieses Problem zu lösen, ist, das Herz um einen Betrag nach oben zu verschieben, der vom Radius der Hilfskreise abhängt
return [ /* same coords */ ].map(([x, y]) => [x, y - .09*R])
Wir haben jetzt eine viel bessere Ausrichtung, unabhängig davon, wie wir den Faktor f in beiden Fällen anpassen. Dies ist der Faktor, der den Umkreisradius des Pentagramms im Verhältnis zur viewBox-Größe bestimmt (wenn der Standard .5 ist) und den Radius der Hilfskreise im Verhältnis zur gleichen viewBox-Größe im Fall des Herzens (wenn der Standard .25 ist).
Siehe den Pen von thebabydino (@thebabydino) auf CodePen.
Wechseln zwischen den beiden Formen
Wir wollen bei jedem Klick von einer Form zur anderen wechseln. Um dies zu erreichen, legen wir eine Richtungsvariable dir fest, die 1 ist, wenn wir vom Stern zum Herz wechseln, und -1, wenn wir vom Herz zum Stern wechseln. Anfangs ist sie -1, als ob wir gerade vom Herz zum Stern gewechselt hätten.
Dann fügen wir einen 'click'-Event-Listener zum _SHAPE-Element hinzu und programmieren, was in dieser Situation passiert – wir ändern das Vorzeichen der Richtungsvariable (dir) und ändern die Attribute der Form, sodass wir von einem goldenen Stern zu einem karminroten Herz wechseln oder umgekehrt
let dir = -1;
(function init() {
/* same as before */
_SHAPE.addEventListener('click', e => {
dir *= -1;
for(let p in O)
_SHAPE.setAttribute(p, O[p].afn(O[p][dir > 0 ? 'fin' : 'ini']));
}, false);
})();
Und wir wechseln nun bei jedem Klick zwischen den beiden Formen
Siehe den Pen von thebabydino (@thebabydino) auf CodePen.
Morphing von einer Form zur anderen
Was wir uns jedoch wirklich wünschen, ist keine abrupte Änderung von einer Form zur anderen, sondern eine allmähliche. Daher verwenden wir die Interpolationstechniken, die im vorherigen Artikel erklärt wurden, um dies zu erreichen.
Wir legen zunächst eine Gesamtzahl von Bildern für unsere Übergänge fest (NF) und wählen die Art der Timing-Funktionen, die wir verwenden möchten – eine ease-in-out-Funktion für den Übergang der path-Form von Stern zu Herz, eine bounce-ini-fin-Funktion für den Rotationswinkel und eine ease-out-Funktion für die fill. Wir nehmen nur diese auf, obwohl wir später andere hinzufügen könnten, falls wir unsere Meinung ändern und andere Optionen erkunden möchten.
/* same as before */
const NF = 50,
TFN = {
'ease-out': function(k) {
return 1 - Math.pow(1 - k, 1.675)
},
'ease-in-out': function(k) {
return .5*(Math.sin((k - .5)*Math.PI) + 1)
},
'bounce-ini-fin': function(k, s = -.65*Math.PI, e = -s) {
return (Math.sin(k*(e - s) + s) - Math.sin(s))/(Math.sin(e) - Math.sin(s))
}
};
Dann geben wir an, welche dieser Timing-Funktionen wir für jede Eigenschaft verwenden, die wir animieren
(function init() {
/* same as before */
O.d = {
/* same as before */
tfn: 'ease-in-out'
};
O.transform = {
/* same as before */
tfn: 'bounce-ini-fin'
};
O.fill = {
/* same as before */
tfn: 'ease-out'
};
/* same as before */
})();
Wir fahren fort, Variablen für die Anforderungs-ID (rID) und den aktuellen Frame (cf) hinzuzufügen, eine Funktion update(), die wir zuerst bei Klick aufrufen, dann bei jeder Aktualisierung der Anzeige, bis der Übergang abgeschlossen ist, und eine Funktion stopAni(), um diese Animationsschleife zu verlassen. Innerhalb der Funktion update() aktualisieren wir… nun ja, den aktuellen Frame cf, berechnen einen Fortschritt k und entscheiden, ob wir das Ende des Übergangs erreicht haben und die Animationsschleife verlassen müssen oder ob wir weitermachen.
Wir fügen auch eine Multiplikatorvariable m hinzu, die wir verwenden, damit wir die Timing-Funktionen nicht umkehren, wenn wir vom Endzustand (Herz) zurück zum Anfangszustand (Stern) gehen.
let rID = null, cf = 0, m;
function stopAni() {
cancelAnimationFrame(rID);
rID = null;
};
function update() {
cf += dir;
let k = cf/NF;
if(!(cf%NF)) {
stopAni();
return
}
rID = requestAnimationFrame(update)
};
Dann müssen wir ändern, was bei Klick passiert
addEventListener('click', e => {
if(rID) stopAni();
dir *= -1;
m = .5*(1 - dir);
update();
}, false);
Innerhalb der Funktion update() möchten wir die Attribute, die wir animieren, auf Zwischenwerte setzen (abhängig vom Fortschritt k). Wie im vorherigen Artikel gesehen, ist es gut, die Bereiche zwischen den End- und Anfangswerten von Anfang an vorab zu berechnen, noch bevor der Listener gesetzt wird. Das ist also unser nächster Schritt: eine Funktion erstellen, die den Bereich zwischen Zahlen berechnet, sei es als solche oder in Arrays, egal wie tief sie sind, und dann diese Funktion verwenden, um die Bereiche für die Eigenschaften festzulegen, die wir animieren wollen.
function range(ini, fin) {
return typeof ini == 'number' ?
fin - ini :
ini.map((c, i) => range(ini[i], fin[i]))
};
(function init() {
/* same as before */
for(let p in O) {
O[p].rng = range(O[p].ini, O[p].fin);
_SHAPE.setAttribute(p, O[p].afn(O[p].ini));
}
/* same as before */
})();
Jetzt müssen wir nur noch den Interpolationsteil in der update()-Funktion schreiben. Mithilfe einer Schleife gehen wir alle Attribute durch, die wir von einem Endzustand zum anderen glatt ändern wollen. Innerhalb dieser Schleife setzen wir ihren aktuellen Wert auf den Wert, den wir als Ergebnis einer Interpolationsfunktion erhalten, die vom Anfangswert(en), Bereich(en) des aktuellen Attributs (ini und rng), der verwendeten Timing-Funktion (tfn) und dem Fortschritt (k) abhängt.
function update() {
/* same as before */
for(let p in O) {
let c = O[p];
_SHAPE.setAttribute(p, c.afn(int(c.ini, c.rng, TFN[c.tfn], k)));
}
/* same as before */
};
Der letzte Schritt ist das Schreiben dieser Interpolationsfunktion. Sie ähnelt ziemlich der, die uns die Bereichswerte liefert
function int(ini, rng, tfn, k) {
return typeof ini == 'number' ?
Math.round(ini + (m + dir*tfn(m + dir*k))*rng) :
ini.map((c, i) => int(ini[i], rng[i], tfn, k))
};
Das gibt uns schließlich eine Form, die bei Klick von einem Stern zu einem Herz morpht und bei einem zweiten Klick zurück zu einem Stern!
Siehe den Pen von thebabydino (@thebabydino) auf CodePen.
Es ist fast das, was wir wollten – es gibt noch ein winziges Problem. Bei zyklischen Werten wie Winkelwerten wollen wir uns beim zweiten Klick nicht um einen halben Kreis zurückbewegen. Stattdessen wollen wir uns weiterhin in die gleiche Richtung um einen weiteren halben Kreis bewegen. Wenn wir diesen halben Kreis nach dem zweiten Klick mit dem nach dem ersten Klick zurückgelegten addieren, erhalten wir einen vollen Kreis, so dass wir wieder am Anfang sind.
Wir setzen dies in Code um, indem wir eine optionale Kontinuitätseigenschaft hinzufügen und die Update- und Interpolationsfunktionen etwas anpassen
function int(ini, rng, tfn, k, cnt) {
return typeof ini == 'number' ?
Math.round(ini + cnt*(m + dir*tfn(m + dir*k))*rng) :
ini.map((c, i) => int(ini[i], rng[i], tfn, k, cnt))
};
function update() {
/* same as before */
for(let p in O) {
let c = O[p];
_SHAPE.setAttribute(p, c.afn(int(c.ini, c.rng, TFN[c.tfn], k, c.cnt ? dir : 1)));
}
/* same as before */
};
(function init() {
/* same as before */
O.transform = {
ini: -180,
fin: 0,
afn: (ang) => fnStr('rotate', ang),
tfn: 'bounce-ini-fin',
cnt: 1
};
/* same as before */
})();
Wir haben nun das Ergebnis, nach dem wir gesucht haben: eine Form, die sich von einem goldenen Stern in ein karminrotes Herz verwandelt und sich bei jeder Bewegung zwischen den Zuständen um eine halbe Umdrehung im Uhrzeigersinn dreht
Siehe den Pen von thebabydino (@thebabydino) auf CodePen.
Sehr coole Animation. Fast schon die Geometrie zu lernen und das dämonische Ritual durchzuführen wert
Die Animation ist cool… aber ich bin am meisten von den geometrischen Diagrammen in diesem Artikel beeindruckt. Wowza. Gut gemacht.
Man hat mir gesagt, es gäbe keine Mathematik… :)
Toller Artikel! Endlich ein Artikel, der kein jQuery- oder Angular-How-to ist. Heutzutage liegt der Fokus oft so sehr auf Abstraktion und Werkzeugen, dass wir selten herausgefordert werden.
Absolut! Während AngularJS und jQuery in manchen Situationen nützlich sind, ist es am besten, so etwas mit VanillaJS oder gar keinem JS zu erstellen. Ich würde es hassen, einen Aufruf an ein ganzes Framework oder eine Bibliothek hinzuzufügen, wenn dies das Einzige wäre, wofür ich ein Skript bräuchte.
Gute Arbeit, Ana Tudor!
Das ist wirklich cool.
Oder nehmen Sie einfach Greensock und kondensieren Sie es auf 2 Zeilen…
Demo-Link oder ich rufe Blödsinn! :)
Haben Sie ein Greensock-Beispiel? Ich bin interessiert.
Schauen Sie mal
https://greensock.com/morphSVG
Wenn Sie das lieber tun, in Ordnung, Ihre Wahl, aber ich habe die letzten paar Artikel(1) für Leute geschrieben, die lieber ein Gehirn benutzen und verstehen wollen, was im Hintergrund passiert, anstatt 5 HTTP-Anfragen zu stellen und über 450 KB JS-Bibliotheken einzubinden (denn genau das tut das erste Beispiel im obigen Link), um einen Effekt zu erzielen, den sie mit unter 1 % des Codes hätten erzielen können.
Besonders in der aktuellen Landschaft, wo sich alle über überladene Seiten beschweren.
(1) Denn am Ende des Tages war der vorherige Artikel derjenige, der sich tatsächlich damit befasste, wie man den Übergangsteil durchführt, während dieser hier in erster Linie erklärt, wie man die Geometrie der Start- und Endformen erhält, wobei ein Morph-Plugin nicht hilft. Das Morph-Plugin muss immer noch mit diesen Start- und Endpfaddaten gefüttert werden.
Ich möchte nur sagen, dass ich Anas kürzere Version sehr viel mehr schätze als ein Greensock-Beispiel. Erstens muss ich niemanden bezahlen, um die gleiche Methode wie Ana zu verwenden, und zweitens ist Anas Version tatsächlich kürzer.
Während Sie in einer Greensock-Version nur zwei Codezeilen auf Ihrer Seite verwenden, verwendet die Greensock-Version insgesamt weit mehr Codezeilen als die VanillaJS-Version, da sie ein ganzes Greensock-Plugin über HTTP-Anfragen für einen beträchtlichen Teil zusätzlicher Bibliotheken einbindet.
Wenn das Codieren einer längeren und langsameren Version (auf Ihrer Seite) die Zeit jedes einzelnen Betrachters dieser Version spart, ist die längere, langsamere VanillaJS-Version absolut lohnenswert.
Toller Artikel (ich ♥ die Geometrie), aber bitte verwenden Sie dies nicht in der Produktion. Die Verwendung einer Bibliothek wie Greensock ist die richtige Lösung, es sei denn, Sie haben viel Zeit zu verschwenden.
Wenn Sie Greensock nur für diese Animation einbinden, brauchen Sie diese Animation wahrscheinlich nicht. Wenn Ihre Website viele Animationen hat, verwenden Sie bereits Greensock (oder eine andere Bibliothek), so dass keine zusätzlichen Kosten entstehen.
Selbst wenn Sie die ganze Mathematik mögen und jetzt Zeit zu verschwenden haben, sollten Sie sie trotzdem nicht in der Produktion verwenden, denn irgendwann in der Zukunft werden Sie (oder wer auch immer Ihren Code pflegt (sehr wahrscheinlich wird er die Mathematik nicht so mögen wie Sie)) eine Million Dinge zu erledigen haben und irgendein $%#*-Typ aus dem Marketing wird auf die Idee kommen, dass sie die Krümmung der Formen in dieser Animation A/B-testen sollten, um zu sehen, ob sie den Umsatz steigern kann, und Sie müssen die Mathematik mehrmals durchführen und sie bis zum Ende der Stunde erledigt haben, weil es "nur eine kleine Änderung" ist.
Nochmal ein toller Artikel und das Konzept ist definitiv lernenswert, aber es sei denn, Sie wollen jemandem in Zukunft den Tag verderben, verwenden Sie in der Produktion eine Bibliothek.
Seien Sie vorsichtig.
Ich hasse Kommentare wie diese wirklich, das ist ein Ort zum Lernen, ich habe den Artikel geliebt, es ist mir egal, ob man es mit X-Bibliothek in 3 Zeichen machen kann, der große Wert dieses Artikels liegt im Lernen, der Mühe, die in die Erklärung jedes Details gesteckt wurde. Toller Artikel.
Eine Menge der angesagtesten High-End-Plugin-Animationen mag eine wunderschöne Website ergeben, aber wenn die Leute verärgert sind, dass die Website zu lange zum Laden braucht, werden sie höchstwahrscheinlich einfach den Browser-Tab schließen und sich vornehmen, diese Seite nie wieder zu besuchen.
Animationen sind großartig, aber wie alles sollten sie in Maßen eingesetzt werden. Wenn Sie eine einfache Animation mit HTML/CSS/VanillaJS erstellen können und sie blitzschnell lädt und einen Zweck erfüllt, machen Sie diese Animation. Noch besser, machen Sie es nur mit HTML/CSS. Je weniger JS Sie verwenden, desto einfacher ist es, Probleme mit widersprüchlichen Skripten zu vermeiden.
Ich sage nicht, dass Greensock niemals verwendet werden sollte, aber ich sehe ehrlich gesagt keinen Sinn darin, es nur zu verwenden, um langes Codieren zu vermeiden (das kommentiert werden kann) und Mathematik (die für jeden, der sie separat dokumentiert hat, wie in Anas Beitrag, leicht zu verstehen sein sollte).
Ich sehe Greensock als etwas, das für Apps oder Websites verwendet werden sollte, die rein zur Animation als Inhalt eingerichtet sind, oder möglicherweise für Websites wie CodePen, wo eine große Anzahl von Benutzern Greensock-Funktionalität für die von ihnen geposteten Snippets benötigt. Aber es gibt vieles da draußen, das in keine der beiden Situationen passt.
Ich persönlich bin stolz darauf, mich an ein absolutes Minimum an HTTP-Anfragen, gestrafften und minifizierten Code zu halten und so wenige Sprachen wie möglich zu verwenden, um minimale Designs zu erstellen, die, wenn möglich, in 10ms oder weniger laden. Meine Benutzer scheinen meine zusätzliche Arbeit, die dazu dient, Websites so schnell wie möglich laden zu lassen, zu schätzen, und ich schätze Leute wie Ana, die eine gründliche Anleitung erstellen können, wie man Dinge tut, ohne sich für jedes kleine Stück Funktionalität auf sperrige Bibliotheken und Frameworks zu verlassen, was wiederum schnelle Ladezeiten ermöglicht.
Toller Artikel. Danke, dass Sie all die Details durch Ihre wunderbaren Zeichnungen geteilt haben.
Danke!
Gut gemacht, ich stimme zu, die Geometrie in den Diagrammen ist erstaunlich. Ich könnte wieder anfangen, Mathematik zu lernen.
Ich weiß nicht wirklich, was Sie damit meinen, aber die Änderung der Form in etwas anderes wäre die Aufgabe von jemandem mit künstlerischem Sinn, nicht die Aufgabe eines Technikers.
Die Änderung einer Form ein wenig tut auch Greensock nicht. In allen von Ihnen gesehenen Beispielen füttern Sie Greensock mit einer Form, von der morphen, und einer Form, zu der morphen. Greensock generiert diese Formen nicht. Die Formen werden von jemandem mit genügend künstlerischem Sinn erstellt.
Dasselbe gilt für die einfache JS-Technik, die ich im vorherigen Artikel erklärt habe. In diesem Artikel habe ich nur eine Demo erklärt, bei der ich sie verwendet habe. Ich hatte eine Idee, wie man geometrisch einen Stern und ein Herz erzeugt und habe sie umgesetzt.
Die von mir erklärte Morphing-Technik funktioniert jedoch für jede andere Form gleich, geometrisch erzeugt oder nicht. In dieser Demo zum Beispiel habe ich einfach zwei SVG-Formen von Wikipedia genommen, sie ein wenig angepasst und als Start- und Endformen verwendet. Es war keine Geometrie-Berechnung erforderlich. Alles, was ich getan habe, war, einige Linien mit null Länge einzufügen, damit ich dieselbe Kurve-zu-Kurve-, Linie-zu-Linie-Sequenz in den Pfaddaten hätte. Wenn jemand eine andere Krümmung für eine dieser Kurven möchte, in Ordnung, geben Sie mir die neue Pfaddaten mit der neuen Krümmung, ich werde sie einfach anstelle der vorherigen einfügen. Das ändert nichts an meinem JS.
Wenn Sie eine Animation hätten, die einfach zwei einfache SVGs nimmt und sie morphingt (höchstwahrscheinlich mit einer Bibliothek wie Greensock), dann hätten Sie Recht. Die Erstellung der SVGs wäre wahrscheinlich die Aufgabe eines Designers.
Ich kenne keinen nicht-technischen Designer, der die Form des Sterns mit dem Code in diesem Artikel ändern könnte.
Ich habe "die Form ändern" nur als etwas verwendet, das in Zukunft möglicherweise geändert werden muss. Die Mathematik in diesem Artikel ist wahnsinnig unterhaltsam, aber nichts, was die meisten Leute täglich verwenden. Was bedeutet, dass zu einem späteren Zeitpunkt (wenn dies in der Produktion verwendet wird) diese Animation geändert werden muss, und die Person, die sie ändert (auch wenn es die Person ist, die sie erstellt hat), wird wahrscheinlich diese Mathematik wieder lernen müssen.
Moral von dieser Geschichte: Alles Code wird irgendwann zu Legacy, und für die meisten Softwareprojekte ist die Zeit der Entwickler die teuerste Kosten.
Ich kenne keinen nicht-technischen Designer, der die Form des Sterns mit dem Code in diesem Artikel ändern könnte.
Mein Punkt war nur, dass die Mathematik in diesem Artikel wahnsinnig Spaß macht, aber nichts, was die meisten Leute täglich verwenden, und da aller Code irgendwann zu Legacy wird, wird jemand die Mathematik neu lernen müssen, um ihn zu ändern.
Keine nicht-technische Person müsste den Code anfassen. Sie müssten nur die Form bearbeiten / eine andere Form in einem Grafikeditor erstellen, mit Griffen und so weiter ziehen und dann das neue SVG-Ergebnis weitergeben.
Welches SVG auch immer verwendet wird, beeinflusst den Morphing-Code nicht (was, nochmals, Thema des vorherigen Artikels ist, nicht dieses). Die Tatsache, dass ich hier die Form mit JS generiert habe, ist für den Morphing-Teil absolut irrelevant (und wieder, Morphing ist das, was Greensock tut, nicht das Zeichnen der Form).
Ich kann den gesamten Generierungscode herausnehmen und nur die Pfaddaten für den Stern in ein
ini-Attribut und die Pfaddaten für das Herz in einfin-Attribut des Pfadelements einfügen, das JS so anpassen, dass es die Koordinaten von diesen Attributen statt von den Generierungsfunktionen nimmt, und das war's.Was ich im oben verlinkten Demo genau getan habe. Die Notiz und die Neun sind nicht geometrisch erzeugt, ich habe sie einfach von Wikipedia genommen.