How I Live-Coded My Most-Hearted Pen

Avatar of Ana Tudor
Ana Tudor am

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

Der folgende Beitrag ist ein Gastbeitrag von Ana Tudor. Wenn Sie Anas Arbeit kennen, wissen Sie vielleicht, dass sie Mathematik und Code kombiniert, um Kunst zu schaffen. Die fertigen Stücke sehen aus, als würden sie ewig dauern. Aber wie ich mit eigenen Augen miterleben konnte, kann Ana durchdenken, was nötig ist, um so etwas zu bauen, und das unglaublich schnell. Hier erklärt sie uns den gesamten Denkprozess in einem Schritt-für-Schritt-Tutorial.

Mitte August beschloss ich, zu versuchen, ein schönes GIF, das ich auf 12gon gefunden hatte, zu reproduzieren.

Das ursprüngliche GIF

Ich dachte, ich würde es live coden, damit die Leute zuschauen können, mit dem Professor Mode von CodePen. 30 Minuten später war dies das Ergebnis

Siehe den Pen Möbius 6hedrons (reines CSS) von Ana Tudor (@thebabydino) auf CodePen.

Schauen wir uns an, wie das alles funktioniert. Es ist überraschend einfach!

3D-Koordinatensystem

Wir werden in 3D arbeiten.

Drei Dimensionen: x, y und z.

Die x-Achse verläuft von links (ihr -) nach rechts (ihr +). Die y-Achse verläuft von oben (ihr -) nach unten (ihr +). Die z-Achse verläuft von hinten des Bildschirms (ihr -) nach vorne, auf uns zu (ihr +). Der Schnittpunkt dieser drei Achsen ist der Ursprung des Koordinatensystems. Die xy-Ebene (in der Abbildung blau dargestellt) ist die vertikale Ebene des Bildschirms. Die yz-Ebene ist die vertikale Ebene (grün dargestellt), die den Bildschirm in einen linken und einen rechten Teil teilt. Die zx-Ebene ist die horizontale Ebene (rot dargestellt), die den Bildschirm in einen oberen und einen unteren Teil teilt.

Wichtiges Konzept: Jedes HTML-Element hat ein lokales 3D-Koordinatensystem, dessen Ursprung sich anfangs bei 50% 50% 0 des Elements befindet (50% horizontal, 50% vertikal und in der Ebene des Elements, da alle HTML-Elemente flach sind und alle ihre Punkte in derselben Ebene liegen). Dies kann mit der Eigenschaft transform-origin geändert werden, aber keine Sorge, wir werden dies hier nicht tun müssen.

Grundlegende Einrichtung

Die Position eines Elements in 3D ist immer relativ zum 3D-Koordinatensystem seines Elternteils, daher lassen wir den den gesamten Viewport abdecken und positionieren alle seine Nachkommen absolut bei 50% 50% ihrer jeweiligen Elternteile. Wir setzen auch transform-style: preserve-3d auf den Nachkommen des Körpers, weil wir die Verschachtelung von 3D-transformierten Elementen zulassen wollen (die Balken werden in 3D transformiert und auch ihre Kinder, die Balkenflächen).

body {
  height: 100vh;
  perspective: 40em;
  background: #000;
}

body * {
  position: absolute;
  top: 50%; left: 50%;
  transform-style: preserve-3d;
}

Da dies eine 3D-Demo ist und der den gesamten Viewport abdeckt, und daher unsere Szene sein wird, haben wir ihm auch eine perspective zugewiesen. Dies bewirkt, dass alles, was uns näher ist, größer erscheint als alles, was weit weg ist. Je kleiner der perspective-Wert ist, desto größer ist der Unterschied zwischen dem, was vorne und dem, was hinten ist. Die folgende Demo zeigt, wie sich das Ändern des perspective-Wertes auf der Szene auswirkt, wenn Objekte in der Szene, in diesem Fall zwei Würfel, unterschiedlich gerendert werden. Für jeden der Würfel zeigt die Demo auch die xy-Ebene seines lokalen Koordinatensystems (in blau).

Siehe den Pen was das Ändern der Perspektive auf der Szene bewirkt von Ana Tudor (@thebabydino) auf CodePen.

Anfangsdaten

Lassen Sie uns nun einige Daten aus dem Bild sammeln. Etwas schwierig, da es sich bewegt und uns schwindelig macht, also teilen wir es in Frames auf. Ich habe eine Aversion gegen Dinge, die ich installieren muss, also benutze ich einen Online-Splitter für solche Dinge, aber Sie können verwenden, was immer Ihnen am liebsten ist. Das Aufteilen des GIFs verrät mir, dass es 43 Frames gibt und die Verzögerung zwischen ihnen 0,04s beträgt, was die Dauer der Animation auf etwa 1,75s bringt.

Am wichtigsten ist, dies ist der erste Frame.

Erster Frame des Inspirations-GIFs

Juhu, ein statisches Bild, auf dem ich die Balken (oder quadratischen Recht Prismen) zählen kann, ohne schwindelig zu werden! Und ja, ich habe sie gezählt, indem ich meinen Finger auf den aktuellen gehalten habe, den ich gerade zählte, und mich daran erinnert habe, wo ich angefangen hatte. Wenn ich richtig gezählt habe, sind es 24 Balken. Und wenn nicht, ist es egal, ich mag 24. Ich habe einen guten Grund, ihn zu mögen. Die Mittelpunkte der Balken sind auf einem Kreis um die y-Achse in der horizontalen zx-Ebene verteilt.

Die Balken sind auf einem Kreis um die Y-Achse in der horizontalen ZX-Ebene verteilt

Um einen Kreis herum gibt es 360°, wie die folgende Demo zeigt

Siehe den Pen voller Kreis – responsive SVG-Erklärung von Ana Tudor (@thebabydino) auf CodePen.

24 ist ein Teiler von 360°, also wenn wir die Balken gleichmäßig auf dem Kreis verteilen wollen, verteilen wir sie im Abstand von 360°/24 = 15°, was eine schöne runde Zahl ist und ich mag runde Zahlen – deshalb habe ich die Dauer der Animation auf etwa 1,75s geschätzt. Nehmen wir an, meine Zählfähigkeiten sind korrekt und belassen die Anzahl der Balken bei 24 und den Basiswinkel dazwischen bei 15°, denn ganze Zahlen sind gut... sie machen unser Leben einfach!

Als nächstes wählen wir die vier Hauptfarbtöne aus dem Bild aus. Ich glaube, ich habe den Entwickler-Tools-Picker dafür verwendet, aber Sie können jedes Werkzeug verwenden, das Sie möchten.

Auswahl der vier Hauptfarbtöne

Die vier Farbtöne, die ich ausgewählt habe, waren für die Endfläche des Balkens vorne (1 in der obigen Abbildung), die Endfläche des Balkens hinten (2), die Seitenfläche des Balkens vorne (3) und die Seitenfläche des Balkens hinten (4).

Nachdem wir Abmessungen und Abstände visuell abgeschätzt haben (ich gebe zu, ich bin nicht gut darin, aber ich schätze, diese Werte funktionieren), legen wir die folgenden Variablen im Sass-Code fest

$n-prisms: 24; // number of bars
$height: 6.25em;               // height of a bar
$base: 1em;                    // base of a bar
$base-c:                       // base shades
    #69f                   // base front (1)
    #7e4b4c;           // base back (2)
$lat-c:                        // lateral shades
    #542252                // lateral front (3)
    #7e301a;               // lateral back (4)
$radius: 1.625*$height;        // radius of circle we distribute the bars on
$base-angle: 360deg/$n-prisms; // base angle between two bars
$t: 1.75s;                     // animation duration

Grundlegende HTML-Struktur

Als Nächstes entscheiden wir uns für die HTML-Struktur. Jeder Balken hat vier Seitenflächen und zwei End- (oder Grund-) Flächen, also insgesamt sechs Flächen pro Balken. Die Balken drehen sich alle um ihre festen Mittelpunkte, die sich auf einem Kreis mit einem bekannten $radius in der horizontalen zx-Ebene befinden. Das bedeutet, dass wir innerhalb der Anordnung der Balken 24 Positionierungselemente haben, die die Balken auf diesem Kreis bewegen, und innerhalb jedes dieser Elemente haben wir einen Balken mit sechs Flächen. Dies ergibt die folgende HTML-Struktur

Aber natürlich werden wir das Positionierungselement nicht so oft kopieren und einfügen. Es gibt intelligentere und kompaktere Möglichkeiten, dies zu schreiben. Zum Beispiel mit Haml oder Slim

.assembly
  - 24.times do
    .positioner
      .prism
        - 6.times do
          .prism__face

Die Balkenflächen

Nachdem wir nun eine HTML-Struktur haben, gehen wir zum Styling über. Wir beginnen mit den Flächen der Balken, da sie die einzigen Elemente mit einem Hintergrund sind und wir immer so schnell wie möglich etwas auf dem Bildschirm sehen wollen (besonders beim Live-Coding!). Wir geben allen Flächen die Abmessungen der Seitenflächen (da wir mehr Seitenflächen als Grundflächen haben, behandeln wir die Grundflächen später als Sonderfall). Dann setzen wir die Ränder auf minus die halbe Abmessung der Flächen, damit ihre 50% 50% Punkte in der Mitte ihrer Container bleiben. Natürlich müssen wir ihnen auch einen Hintergrund geben, damit wir sie sehen können. Wir können jeden der beiden Farbtöne wählen, die wir für die Seitenflächen ausgewählt haben; welcher davon ist egal, wir werden ihn mit der richtigen Mischung aus beiden überschreiben, wenn wir die Balken auf diesem Kreis in der horizontalen zx-Ebene verteilen.

.prism__face {
  margin: -.5*$height (-.5*$base);
  width: $base; height: $height;
  backface-visibility: hidden;
  background: nth($lat-c, 1);
}

Wir haben den Flächen auch backface-visibility: hidden gegeben, damit wir sie nur sehen, wenn wir sie von vorne betrachten und sie unsichtbar sind, wenn wir sie von hinten betrachten.

Siehe den Pen was `backface-visibility` tut von Ana Tudor (@thebabydino) auf CodePen.

Dies ist sehr nützlich, wenn wir überprüfen wollen, ob sie in die richtige Richtung zeigen. Es verhindert auch falsche 3D-Reihenfolgeprobleme und Flimmern in Firefox.

Als Nächstes behandeln wir den Sonderfall der Grundflächen. Wenn wir die Seitenflächen als die ersten vier Flächen betrachten (also Flächen 1, 2, 3 und 4), dann sind die Grundflächen die Flächen 5 und 6, also alle Flächen, deren 1-basierter Index größer oder gleich 5 ist. Dies drücken wir mit Hilfe der nth-child Pseudoklasse aus.

.prism__face:nth-child(n + 5) {
  margin-top: -.5*$base;
  height: $base;
  background: nth($base-c, 1);
}

Sie können im folgenden Pen sehen, was wir bisher haben. Noch nicht viel, aber ein Anfang!

Siehe den Pen Möbius 6hedrons – Schritt 1 von Ana Tudor (@thebabydino) auf CodePen.

Balken erstellen

Der nächste Schritt ist, die Flächen so zu positionieren, dass sie tatsächlich einen Balken in 3D bilden. Um dies zu tun, müssen wir verstehen, wie Rotationen und Translationen funktionieren.

Das Drehen eines Elements um eine Achse mit einem positiven Winkelwert bedeutet eine Drehung im Uhrzeigersinn, wie sie vom + der Achse gesehen wird, um die wir drehen. Eine positive Drehung um die z-Achse ist eine Drehung im Uhrzeigersinn, wie sie vom + dieser Achse aus gesehen wird – der normalen Position unserer Augen vor dem Bildschirm.

Siehe den Pen Rotation um die z-Achse von Ana Tudor (@thebabydino) auf CodePen.

Eine positive Drehung um die y-Achse bedeutet eine Drehung im Uhrzeigersinn, wie sie vom + dieser Achse aus gesehen wird, die sich unten befindet. In diesem Fall sehen wir, wie der linke Teil unseres Elements nach vorne kommt und sein rechter Teil nach hinten in den Bildschirm hineingeht. Wenn wir zum Beispiel ein Element um 90° um die y-Achse drehen, schaut seine Vorderseite nach rechts, während eine Drehung um -90° um dieselbe Achse es nach links schauen lässt.

Siehe den Pen Rotation um die y-Achse von Ana Tudor (@thebabydino) auf CodePen.

Eine positive Drehung um die x-Achse bedeutet eine Drehung im Uhrzeigersinn, wie sie vom + derselben Achse aus gesehen wird – in diesem Fall rechts vom Bildschirm. Wir sehen also, wie der untere Teil des Elements nach oben und vorne kommt, während sein oberer Teil nach unten und nach hinten geht. Nach einer 90°-Drehung um die x-Achse ist unser Element beispielsweise nach oben gerichtet, während eine -90°-Drehung um dieselbe Achse seine Vorderseite nach unten zeigt.

Siehe den Pen Rotation um die x-Achse von Ana Tudor (@thebabydino) auf CodePen.

Eine sehr wichtige Sache, die wir uns merken müssen, ist, dass jede Transformation, die wir auf ein Element anwenden, auch auf sein lokales Koordinatensystem angewendet wird.

Wenn wir beispielsweise ein Element um 90° um seine y-Achse drehen, schaut dieses Element nach der Drehung nicht nur nach rechts, sondern seine z-Achse – die, die sich vor der Drehung auf uns vom Bildschirm aus zuwandte – zeigt jetzt nach rechts. Wenn wir ein Element um 90° um die x-Achse drehen, ist nicht nur das Element nach der Drehung nach oben gerichtet, sondern seine z-Achse zeigt ebenfalls nach oben. Wenn wir es um -90° drehen würden, wäre das Element nach unten gerichtet und seine z-Achse würde ebenfalls nach unten zeigen.

Wir müssen also daran denken, dass sich die z-Achse immer von der Vorderseite des Elements weg erstreckt, die y-Achse sich immer nach unten des Elements erstreckt, während sich seine x-Achse immer nach rechts des Elements erstreckt, egal wie wir das Element transformieren.

Siehe den Pen das Drehen eines Elements dreht auch sein Koordinatensystem v2 von Ana Tudor (@thebabydino) auf CodePen.

Das war zu Rotationen, jetzt sehen wir uns Translationen an. Eine Translation eines positiven Wertes entlang einer Achse bewegt die Fläche in Richtung des + dieser Achse.

Zum Beispiel bringt eine Translation eines positiven Wertes entlang der z-Achse (die auf uns zeigt) das Element nach vorne, näher zu uns, während eine Translation eines negativen Wertes entlang derselben Achse das Element nach hinten, von uns weg bewegt.

Siehe den Pen ein Element übersetzen von Ana Tudor (@thebabydino) auf CodePen.

Genau wie bei Rotationen beeinflussen Translationen auch das lokale Koordinatensystem des Elements – Sie können sehen, wie es sich in der obigen Demo mit dem Element bewegt.

Die Tatsache, dass jede Transformation das Koordinatensystem des Elements beeinflusst, bedeutet, dass sie auch die Auswirkung aller nachfolgenden Transformationen beeinflusst, die wir auf dieses Element anwenden.

Wenn wir beispielsweise ein Element in der positiven Richtung der z-Achse verschieben, ohne zuvor eine andere Transformation angewendet zu haben, wird unser Element nach vorne verschoben. Aber wenn wir unser Element um 90° um die y-Achse drehen und es dann entlang der z-Achse in positiver Richtung verschieben, bewegt diese Translation das Element nach rechts vom Bildschirm (aus unserer Sicht vor dem Bildschirm), weil die z-Achse nach der Drehung nach rechts zeigt. Auf die gleiche Weise, wenn wir zuerst das Element um 90° um die x-Achse drehen und es dann entlang der z-Achse in positiver Richtung verschieben, bewegt diese Translation das Element nach oben, weil die z-Achse nach der Drehung nach oben zeigt. Wenn wir es um -90° um die x-Achse drehen und es dann entlang der z-Achse in positiver Richtung verschieben, bewegt diese Translation das Element nach unten, weil die z-Achse nach der Drehung nach unten zeigt.

Siehe den Pen Transformationen von Ana Tudor (@thebabydino) auf CodePen.

Nun wollen wir sehen, wo sich die Flächen anfangs befinden und wohin wir sie bewegen wollen, damit sie tatsächlich einen Balken bilden.

Anfang vs. Ende

Da wir damit begonnen haben, alles absolut in der Ebene des Bildschirms (xy) in der Mitte des Bildschirms zu positionieren, fällt der anfängliche 50% 50%-Punkt der Flächen (bevor wir sie bewegen, damit sie den Balken bilden) mit dem Punkt genau in der Mitte des Balkens zusammen (erstes Panel in der obigen Abbildung). Die Hälfte des Balkens liegt hinter der xy-Ebene (blau, zweites Panel), die andere Hälfte des Balkens liegt davor. Die Hälfte des Balkens liegt links von der yz-Ebene (grün, drittes Panel) und die andere Hälfte liegt rechts. Die Hälfte des Balkens liegt oberhalb der zx-Ebene (rot, viertes Panel) und die andere Hälfte liegt darunter. Der Abstand von diesem Punkt zu den Flächen vorne und hinten beträgt die halbe Basis. Der Abstand zu den Flächen rechts und links beträgt ebenfalls die halbe Basis, während der Abstand zu den Flächen oben und unten die halbe Höhe beträgt.

Wir haben die ersten vier Flächen zu den Seitenflächen und die letzten beiden zu den Grund- (End-) Flächen gemacht. Also positionieren wir die erste Fläche vorne am Balken und gehen mit den nächsten drei herum – die zweite kommt nach rechts, die dritte nach hinten, die vierte nach links. Dann positionieren wir die fünfte Fläche oben und die sechste Fläche unten.

Flächennummerierung

Um die erste Fläche vorne zu positionieren, müssen wir sie nur um die halbe Basis nach vorne verschieben. Das bedeutet eine Translation von .5*$base entlang der z-Achse

.prism__face:nth-child(1) {
  transform: translateZ(.5*$base);
}

Um die zweite Fläche rechts zu positionieren, müssen wir sie zuerst um 90° (einen rechten Winkel) um die y-Achse drehen, damit ihre z-Achse nach rechts zeigt. Dann verschieben wir sie um die halbe Basis in positiver Richtung der z-Achse nach der Drehung (nach rechts vom Bildschirm)

.prism__face:nth-child(2) {
  transform: rotateY(90deg) translateZ(.5*$base);
}

Um die dritte Fläche hinten zu positionieren, drehen wir sie um 180° (90° mehr als die vorherige Fläche) um die y-Achse, damit ihre z-Achse jetzt nach hinten zeigt. Dann verschieben wir sie um die halbe Basis in dieser neuen positiven Richtung der z-Achse (von uns weg)

.prism__face:nth-child(3) {
  transform: rotateY(180deg) translateZ(.5*$base);
}

Sie fragen sich vielleicht, warum wir diese Fläche nicht einfach um die halbe Basis zurückverschieben – eine translateZ(-.5*$base). Nun, das könnten wir tun, aber dann hätten wir einige Probleme

  • Der Code würde keinem Muster folgen
  • Die Vorderseite der Fläche wäre auf der Innenseite des Balkens
  • Die Fläche wäre von außerhalb des Balkens unsichtbar, da wir backface-visibility: hidden darauf gesetzt haben

Um die vierte Fläche links zu positionieren, drehen wir sie um 270° (90° mehr als die vorherige Fläche) um die y-Achse, wodurch ihre z-Achse jetzt nach links zeigt. Und dann verschieben wir sie nach links (in der neuen positiven Richtung der z-Achse) um die halbe Basis

.prism__face:nth-child(4) {
  transform: rotateY(270deg) translateZ(.5*$base);
}

Um die fünfte Fläche oben zu positionieren, drehen wir sie um 90° um die x-Achse, damit ihre z-Achse nach oben zeigt, dann verschieben wir sie in der neuen positiven Richtung der z-Achse (nach oben) um die halbe Höhe

.prism__face:nth-child(5) {
  transform: rotateX(90deg) translateZ(.5*$height);
}

Um die sechste Fläche unten zu positionieren, drehen wir sie zuerst um -90° um ihre x-Achse, damit ihre z-Achse nach unten zeigt, dann verschieben wir sie nach unten, in Richtung des + der gedrehten z-Achse, um die halbe Höhe

.prism__face:nth-child(6) {
  transform: rotateX(-90deg) translateZ(.5*$height);
}

Die folgende Demo veranschaulicht, wie dies funktioniert

Siehe den Pen Flächen positionieren v#2 von Ana Tudor (@thebabydino) auf CodePen.

In Ordnung, jetzt haben wir ein Prisma. Aber der Code für die Flächen sieht nicht gut aus, er ist viel zu repetitiv. Und wenn wir den Code für die erste Fläche leicht ändern, sehen wir ein Muster für die ersten vier Flächen

.prism__face:nth-child(1) { /* 1 = 0 + 1 */
  transform: rotateY(  0deg) translateZ(.5*$base);  /*   0deg = 0*90deg */
}
.prism__face:nth-child(2) { /* 2 = 1 + 1 */
  transform: rotateY( 90deg) translateZ(.5*$base);  /*  90deg = 1*90deg */
}
.prism__face:nth-child(3) { /* 3 = 2 + 1 */
  transform: rotateY(180deg) translateZ(.5*$base);  /* 180deg = 2*90deg */
}
.prism__face:nth-child(4) { /* 4 = 3 + 1 */
  transform: rotateY(270deg) translateZ(.5*$base);  /* 270deg = 3*90deg */
}

Im Allgemeinen können wir den Code für diese ersten Flächen (die Seitenflächen) schreiben als

.prism__face:nth-child(#{$i + 1}) {
  transform: rotateY($i*90deg) translateZ(.5*$base);
}

... wobei $i 0, 1, 2 oder 3 ist.

Aber was ist mit den letzten beiden Flächen? Nun, wir können auch hier ein Muster erkennen

.prism__face:nth-child(5) { /* 5 = 4 + 1 */
  transform: 
    rotateX( 90deg) translateZ(.5*$height); /*  90deg =  1*90deg = pow(-1, 4)*90deg */
}
.prism__face:nth-child(6) { /* 6 = 5 + 1 */
  transform: 
    rotateX(-90deg) translateZ(.5*$height); /* -90deg = -1*90deg = pow(-1, 5)*90deg */
}

Im Allgemeinen können wir den Code für die letzten Flächen (die Grundflächen) schreiben als

.prism__face:nth-child(#{$i + 1}) {
  transform: rotateX(pow(-1, $i)*90deg) translateZ(.5*$height);
}

... wobei $i 4 oder 5 ist.

Jetzt können wir diese beiden Variationen (die für die Seitenflächen und die für die Grundflächen) mit der Sass-Funktion if() kombinieren

.prism__face:nth-child(#{$i + 1}) {
  transform: 
    if($i < 4, rotateY($i*90deg), rotateX(pow(-1, $i)*90deg) 
    translateZ(.5*if($i < 4, $base, $height));
}

Oder, wenn wir nicht zwei Ternaries mit derselben Bedingung haben wollen

$j: if($i < 4, 1, 0); // $j is 1 if $i is less than 4 and 0 otherwise
$k: 1 - $j; // $k is 0 if $j is 1 and 1 otherwise

.prism__face:nth-child(#{$i + 1}) {
  transform: 
    rotate3d($k, $j, 0, ($j*$i + $k*pow(-1, $i))*90deg) 
    translateZ(.5*($j*$base + $k*$height));
}

Jetzt müssen wir nur noch eine dieser beiden Versionen für die generische Fläche in eine Schleife packen, die den Code für alle sechs generiert

.prism__face {
  @for $i from 0 to 6 {
    &:nth-child(#{$i + 1}) {
      transform: 
        if($i < 4, rotateY($i*90deg), rotateX(pow(-1, $i)*90deg) 
        translateZ(.5*if($i < 4, $base, $height));
    }
  }
}

In Ordnung, wir sind mit dem Erstellen der Balken fertig! Das Ergebnis sehen wir im folgenden Pen

Siehe den Pen Möbius 6hedrons – Schritt 2 von Ana Tudor (@thebabydino) auf CodePen.

Hmmm, das sieht nicht viel anders aus als das, was wir vorher hatten. Das liegt an unserem Blickwinkel. Hier, wenn wir ihn mit der Mitte des Bildschirms verbinden würden, wäre diese Linie senkrecht zur Vorderseite, die wir am größten sehen (so dass sie alle anderen Flächen verdeckt), während im obigen Demos, die die Verteilung erklären, der Blickwinkel etwas höher und nach rechts liegt, weshalb wir auch die oberen und rechten Flächen dort sehen konnten. Was wir hier haben, **ist** jedoch 3D. Das wird offensichtlich, wenn wir den Balken drehen (was durch Ziehen in der obigen Demo möglich ist) oder wenn wir den Blickwinkel ändern.

Das Ändern des Blickwinkels mit CSS erfolgt über die Eigenschaft perspective-origin. Genau wie die perspective-Eigenschaft wird diese auf das Szenenelement (in unserem Fall den ) gesetzt. Ihr Standardwert ist 50% 50% (genau in der Mitte des Szenenelements). Wenn wir in der folgenden Demo nach oben und unten ziehen, ändern wir den zweiten Wert (den y-Wert) von perspective-origin, und daher werden die oberen oder unteren Werte sichtbar.

Siehe den Pen Möbius 6hedrons – Schritt 2b (perspective-origin) von Ana Tudor (@thebabydino) auf CodePen.

Die Balken verteilen

Dies ist eigentlich sehr ähnlich wie die Verteilung der Seitenflächen am Balken. Wir drehen die Balkenpositionierer um die y-Achse und verschieben sie dann entlang der z-Achse in positiver Richtung. Nur dass wir jetzt keine Schritte von 90° machen. Stattdessen ist unser Schritt $base-angle (den wir zuvor als schöne runde Zahl von 15° berechnet haben), und unsere z-Translationsdistanz ist der Radius des Kreises, auf dem wir die Balken verteilen. Der Code lautet also

.positioner {
  @for $i from 0 to $n-prisms {
    &:nth-child(#{$i + 1}) {
      transform: rotateY($i*$base-angle) translateZ($radius);
    }
  }
}

Diese Demo veranschaulicht, wie die Balkenpositionierung funktioniert

Siehe den Pen Prismen positionieren von Ana Tudor (@thebabydino) auf CodePen.

Dieser Pen zeigt, wo wir mit dem bisher geschriebenen Code stehen – es fängt an, wie etwas auszusehen!

Siehe den Pen Möbius 6hedrons – Schritt 3 von Ana Tudor (@thebabydino) auf CodePen.

Es gibt jedoch ein paar Probleme. Erstens sind die Balken im Original-GIF nicht vertikal. Um sie in die richtige Position zu bringen, müssen wir die Positionierer um die x-Achse drehen – sagen wir um 70°. Unsere generische Transformationskette wird zu

transform: rotateY($i*$base-angle) translateZ($radius) rotateX(70deg);

Wir sehen, dass die Dinge jetzt besser aussehen

Siehe den Pen Möbius 6hedrons – Schritt 3b von Ana Tudor (@thebabydino) auf CodePen.

Aber im Original-GIF sehen wir die Balken ein wenig von oben. Wir haben zwei Möglichkeiten, diesen Effekt zu erzielen.

Die erste wäre, einfach die gesamte Anordnung zu drehen, sodass wir ihre vordere Hälfte etwas nach unten und ihren hinteren Teil etwas nach oben bewegen. Das würde eine Drehung um einen negativen Winkel um die x-Achse bedeuten – sagen wir -30°

.assembly { transform: rotateX(-30deg); }

Unsere zweite Möglichkeit wäre, dem eine perspective-origin hinzuzufügen. Wenn wir die Anordnung leicht von oben sehen, bedeutet das, den y-Komponentenwert von perspective-origin zu verringern – wir müssen ihn niedriger als 50% machen. %-Werte oder andere Werte, die von oben gemessen werden, sind jedoch keine gute Idee, denn wenn wir sie verwenden, hängt die Art und Weise, wie wir die Balken sehen, von der Höhe der Szene ab. Um dies besser zu veranschaulichen, schauen Sie sich die folgende Abbildung an

Alles ist in diesen drei Fällen identisch, außer der Höhe der Szene (Sie können es live in diesem Pen nachschauen. Da die y-Komponente von perspective-origin von oben gemessen wird und jeder Würfel genau in der Mitte seiner Szene positioniert ist, ist unser Blickwinkel für verschiedene Szenenhöhen unterschiedlich. Daher hängt die Art und Weise, wie wir einen Würfel sehen, von der Höhe der Szene ab, in der er sich befindet.

In unserem Fall ist die Szenenhöhe nicht fest, sondern die Höhe des Viewports. Wenn wir den Viewport verkleinern, sehen wir die Balken je nach perspective-origin-Wert, der sich relativ zur Oberseite der Szene bezieht, unterschiedlich.

Die Lösung, die ich in solchen Situationen oft verwende, ist, eine feste Anzahl von px oder em vom anfänglichen 50% Wert innerhalb einer calc() Funktion abzuziehen – so etwas wie hier:

perspective-origin: 50% calc(50% - 32em);

Das Bild unten veranschaulicht, wie die Dinge mit einer solchen Lösung aussehen (und Sie können es live in diesem Stift ausprobieren).

Beachten Sie, dass, wenn die Würfel im obigen Beispiel oder die Baugruppe in der Demo, an der wir gerade arbeiten, nicht bei 50% von oben, sondern bei 30vmax positioniert wären, wir etwas wie calc(30vmax - 32em) hätten. Die wichtigste Erkenntnis hier ist, dass wir einen perspective-origin festlegen müssen, der relativ zum Mittelpunkt des Objekts ist, das wir unabhängig von den Abmessungen der Szene auf die gleiche Weise sehen möchten.

Schauen wir uns nun den ersten Frame des ursprünglichen GIFs noch einmal an

Erster Frame des Inspirations-GIFs

Wir werden nun versuchen, jede dieser beiden Optionen anzupassen, um zu sehen, welche dem obigen Bild näher kommt.

Ist es die Methode der Montage-Rotation?

See the Pen Möbius 6hedrons – step 3c by Ana Tudor (@thebabydino) on CodePen.

Oder die, die den Blickwinkel ändert? Leider funktioniert diese Demo in Firefox nicht richtig.

See the Pen Möbius 6hedrons – step 3d by Ana Tudor (@thebabydino) on CodePen.

Es scheint, dass die erste Option – die Drehung der gesamten Baugruppe – näher kommt, also werden wir uns für diese entscheiden.

See the Pen Möbius 6hedrons – step 3e by Ana Tudor (@thebabydino) on CodePen.

Beachten Sie, dass es in beiden Fällen noch weitere Dinge gibt, die angepasst werden könnten, und es ist möglich, dass eine bestimmte Kombination aus Balkendimensionen, perspective und perspective-origin auf der Szene dem Original näher kommt als einfach nur die Baugruppe zu drehen. Mein Hauptziel beim Live-Coding war es, schnell zu sein, also habe ich mich für das entschieden, was zu diesem Zeitpunkt besser aussah und die geringste Anzahl von Änderungen an den zuvor festgelegten Werten erforderte.

Schattierung der Balken

Es gibt noch ein paar andere Dinge, die noch nicht richtig aussehen.

Erstens sollten die rechte und linke Fläche dunkler sein als die vordere und hintere. Das sollte mit den richtigen nth-child Selektoren einfach zu beheben sein, indem die Helligkeit der ausgewählten Flächen mit einem brightness() Filter verringert wird

.prism__face:nth-child(-n+4):nth-child(even) {
  filter: brightness(.7); /* value < 1 decreases brightness */
}

Zweitens sollten die Balken um den Kreis herum unterschiedliche Schattierungen haben, wobei die seitlichen von lila vorne zu einer Art orange hinten übergehen. Geht man also von bis 180° um den Kreis, gehen die seitlichen Balken von lila zu orange und von 180° bis 360° gehen sie von orange zurück zu lila.

Schattierung um den Kreis

Das klingt nach einer Aufgabe für die Sass mix() Funktion! In unserem Fall würde das Gewicht von 100% (100% der erste Farbton, lila, der Rest bis 100%, also 100% - 100% = 0% der andere Farbton, orange) auf 0% (0% lila, 100% orange) im Intervall [0°, 180°] gehen und dann im Intervall [180°, 360°] von 0% auf 100% ansteigen. Nun, das ist die Kosinusfunktion! So ungefähr…

Wir sehen unten den Graphen der Kosinusfunktion für das Intervall [0°, 360°]. Wenn der Winkel von auf 180° geht, geht der Wert des Kosinus von 1 auf -1. Wenn der Winkel von 180° auf 360° geht, geht der Wert des Kosinus von -1 auf 1.

See the Pen cos(θ) graph by Ana Tudor (@thebabydino) on CodePen.

Wenn wir 1 addieren, verschiebt sich der gesamte Graph um eine Einheit nach oben, sein Maximum ist 2 und sein Minimum 0.

See the Pen 1 + cos(θ) graph by Ana Tudor (@thebabydino) on CodePen.

Als Nächstes, wenn wir alles mit 50% multiplizieren, geht der Graph im Intervall [0°, 180°] von 100% auf 0% und im Intervall [180°, 360°] wieder auf 100%, was genau das ist, was wir wollten.

See the Pen (1 + cos(θ))*50% graph by Ana Tudor (@thebabydino) on CodePen.

Das Gewicht in der mix-Funktion für den Balken $i ist also (1 + cos($i*$base-angle))*50%. Wir erstellen einen einfachen Mixin, um die Dinge zu vereinfachen

@mixin mix-me($c, $k) {
  background: mix(nth($c, 1), nth($c, 2), $k);
}

Und dann verwenden wir ihn innerhalb der Positions-Schleife

.positioner {
  @for $i from 0 to $n-prisms {
    $curr-angle: $i*$base-angle; // save this so we don't compute it twice
    $k: (1 + cos($curr-angle))*50%;
    
    &:nth-child(#{$i + 1}) {
      transform: rotateY($curr-angle) translateZ($radius) rotateX(70deg);
      
      .prism__face {
        @include mix-me($lat-c, $k);
        
        &:nth-child(n + 5) { @include mix-me($base-c, $k); }
      }
    }
  }
}

Das Ergebnis der Änderungen, die wir in diesem Abschnitt vorgenommen haben, kann in diesem Stift gesehen werden

See the Pen Möbius 6hedrons – step 4 (shading) by Ana Tudor (@thebabydino) on CodePen.

Animation der Balken

Dies ist wahrscheinlich der einfachste Teil von allem. Jeder Balken rotiert gegen den Uhrzeigersinn um eine halbe Umdrehung um die x-Achse. Dann verweilt er eine kleine Weile still, und dann wiederholt sich alles. Es erfordert einiges an Feintuning, um einen passenden Prozentsatz zu finden – wir entscheiden uns hier für 75%. Als Timing-Funktion könnten wir das altbewährte ease-in-out verwenden oder eine symmetrische Funktion von easings.net ausprobieren. Meine erste Wahl für symmetrisches Easing ist easeInOutCubic, da es mir in den meisten Fällen am nächsten zur natürlichen Bewegung erscheint.

@keyframes rot {
  75%, 100% { transform: rotateX(-.5turn); }
}

.prism {
  animation: rot $t ease-in-out infinite;
}

Diese Animation kann im folgenden Stift gesehen werden

See the Pen Möbius 6hedrons – step 5 (animation) by Ana Tudor (@thebabydino) on CodePen.

Hier gibt es jedoch ein Problem: Alle Balken rotieren gleichzeitig. Das bedeutet, wir müssen jedem Balken eine andere animation-delay zuweisen. Wir verwenden negative Verzögerungen, damit alle Animationen bereits im 0-Moment gestartet sind.

.positioner {
  @for $i from 0 to $n-prisms {
    &:nth-child(#{$i + 1}) {
      .prism { animation-delay: -$i*$t/$n-prisms; }
    }
  }
}

Und das ergibt das Endergebnis!

Siehe den Pen Möbius 6hedrons (reines CSS) von Ana Tudor (@thebabydino) auf CodePen.

Schlusswort

Jetzt wissen Sie, wie ich etwas erstellt habe, das in nur 30 Minuten unglaublich kompliziert aussieht. Sie wären wahrscheinlich schneller, da ich nie gelernt habe, mit mehr als einem Finger zu tippen!