Der Zustand responsiver 3D-Formen

Avatar of Ana Tudor
Ana Tudor am

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

Wie einige vielleicht wissen, habe ich schon immer 3D-Geometrie geliebt. Das hat mich dazu gebracht, mit CSS 3D-Transformationen zu spielen, um verschiedene geometrische Formen zu erstellen. Ich habe eine riesige Sammlung solcher Demos erstellt, die Sie sich auf CodePen ansehen können.

Aus diesem Grund werde ich oft gefragt, ob es möglich sei, responsive 3D-Formen zu erstellen, die zum Beispiel %-Werte anstelle der em-Werte verwenden, die meine Demos normalerweise nutzen. Die Antwort ist etwas komplexer als ein Ja/Nein, daher dachte ich, es wäre eine gute Idee, sie in einem Artikel festzuhalten. Los geht's!

Responsive 3D-Formen mit %-Werten

Das ist möglich, oder? Es ist möglich, aber es verkompliziert die Dinge. Um das zu veranschaulichen, nehmen wir ein Beispiel. Nehmen wir an, wir wollen einen Würfel erstellen.

Bevor wir damit beginnen, zwei Dinge.

  1. Wenn Sie eine Auffrischung benötigen, wie das Erstellen eines einfachen, nicht-responsiven rechteckigen Quaders mit CSS 3D-Transformationen funktioniert, können Sie sich diesen älteren Artikel bis zum Teil der Balkenverteilung ansehen. Er erklärt (sogar im Detail) eine Reihe von Konzepten im Zusammenhang damit, wie CSS-Transformationen funktionieren, den Prozess des Aufbaus des Quaders mit sauberem und kompaktem Code, und ich werde all diese Dinge hier nicht wiederholen.
  2. Fassen wir zusammen, was %-Werte für einige CSS-Eigenschaften bedeuten, die wir möglicherweise auf ein Element anwenden möchten.

%-Werte für verschiedene CSS-Eigenschaften

Für width, padding und margin bezieht sich ein %-Wert auf die width des Elternelements des Elements, auf dem wir diese Eigenschaften festlegen.

Betrachten wir die folgende Situation: Wir haben ein Element, ein Kind von body. Auf diesem Element legen wir %-Werte für width, padding und margin fest.

.boo {
  width: 50%;
  padding: 10%;
  margin: 5%;
}

Wenn wir die Ansicht so skalieren, dass die width des body (des Elternelements unseres Elements) 300px beträgt, dann hat unser Element eine width von 150px (50% von 300px), ein padding von 30px (10% von 300px) und ein margin von 15px (5% von 300px), wie es beim Inspizieren des Elements sichtbar ist.

Inspektion des Elements (siehe Live-Demo).

Damit dies wie beabsichtigt funktioniert, sollte die width des Elternelements nicht von der seiner Kinder abhängen. Dies ist eine Zirkularität, die wir vermeiden sollten. Sie tritt beispielsweise auf, wenn das Elternelement position: absolute hat. In diesem Fall wird die width des Elternelements so berechnet, dass der Inhalt unseres Elements passt, und dann werden die width, padding und margin unseres Elements als %-Werte der width des Elternelements berechnet.

Für height bezieht sich ein %-Wert auf die height des Elternelements des Elements, auf dem wir die height-Eigenschaft festlegen. Betrachten wir unser Element als Kind von body, das wir so gestaltet haben, dass es die gesamte Ansicht in der height einnimmt.

body { height: 100vh; }

.boo { height: 50%; }

Wenn wir die Ansicht so skalieren, dass die height des body (des Elternelements unseres Elements) 300px beträgt, dann ist die Höhe unseres Elements 150px (50% von 300px), wie es beim Inspizieren des Elements sichtbar ist.

Inspektion des Elements (siehe Live-Demo).

Damit dies wie beabsichtigt funktioniert, sollte die height des Elternelements nicht von seinen Kindern abhängen. Dies ist eine weitere Zirkularität, die wir vermeiden sollten. Aber im Gegensatz zum Fall der width befinden wir uns in dieser Situation, wenn wir für das Elternelement keine explizite height festlegen.

Für transform, wenn eine translate() / translateX() / translateY() / translateZ() / translate3d()-Funktion einen %-Wert verwendet, dann bezieht sich dieser Wert auf die Größe des Elements entlang dieser Achse. Beachten Sie, dass dies die Achsen des lokalen Koordinatensystems des Elements sind, das sich zusammen mit dem Element in 3D transformiert. Seine x-Achse zeigt immer nach rechts vom Element, seine y-Achse zeigt immer nach unten vom Element und seine z-Achse zeigt immer von vorne aus dem Element heraus.

Ein Wert von translate(50%) oder translate(50%, 0) oder translateX(50%) oder translate3d(50%, 0, 0) verschiebt das Element entlang der x-Achse um die Hälfte seiner eigenen width. Das bedeutet, dass die vertikale Mittellinie des Elements dort endet, wo ursprünglich sein rechter Rand war. In diesem Live-Test repräsentiert die graue Box das Element in seiner ursprünglichen Position ohne Transformation. Die orange Box repräsentiert das Element mit einer angewandten transform von translateX(50%).

Anwenden einer transform von translateX(50%) auf ein Element.

Ein Wert von translate(0, 50%) oder translateY(50%) oder translate3d(0, 50%, 0) verschiebt das Element entlang der y-Achse um die Hälfte seiner eigenen height. Das bedeutet, dass die horizontale Mittellinie des Elements dort endet, wo ursprünglich sein unterer Rand war. In diesem Live-Test repräsentiert die graue Box das Element in seiner ursprünglichen Position ohne Transformation. Die orange Box repräsentiert das Element mit einer angewandten transform von translateY(50%).

Anwenden einer transform von translateY(50%) auf ein Element.

Was ist mit einem Wert von translateZ(50%) oder translate3d(0, 0, 50%)? Nun, hier gibt es keine Anomalie, es verschiebt das Element entlang der z-Achse um die Hälfte seiner Größe entlang dieser Achse. Aber was ist die Größe des Elements entlang der z-Achse? Wir legen das nirgends fest, das ist sicher, aber alle Elemente sind flach, liegen in einer Ebene und folglich ist ihre Größe entlang ihrer z-Achse immer 0. Das bedeutet, dass eine Verschiebung entlang der z-Achse, die einen %-Wert verwendet, **nichts bewirkt**, da jeder % von 0 immer noch 0 ist. Wenn wir uns den berechneten Wert von translateZ(50%) ansehen, sehen wir, dass er none ist.

Konsequenzen der Funktionsweise von %-Werten

Die erste ist, dass wir mit %-Werten keine Elemente erstellen können, deren width von der height der Ansicht abhängt, und das Erstellen von Elementen, deren height von der width der Ansicht abhängt, ist nicht so einfach.

Wir wollten einen Würfel erstellen, also sehen wir, wie wir ein Quadrat erstellen können, dessen Kantenlänge 20% der width der Ansicht beträgt. Zuerst stellen wir sicher, dass unser Quadratelement sowohl width als auch height gleich 0 hat. Wir können dies explizit einstellen oder unser Quadrat absolut positionieren, was wir in diesem Fall tun, da es später beim Erstellen des Würfels nützlich ist. Dann stellen wir das padding auf diesem Quadratelement auf die Hälfte des gewünschten %-Wertes für die Kantenlänge des Würfels 20%/2 = 10% ein – das bewirkt, dass jeder padding-Wert (padding-top, padding-right, padding-bottom und padding-left) 10% der width der Ansicht beträgt.

$edge-len: 20%;

.square {
  position: absolute;
  padding: .5*$edge-len;
}

Das Zusammenzählen von padding-left von 10% und padding-right von 10% ergibt horizontal 20% der width der Ansicht über das Quadrat. Das Zusammenzählen von padding-top von 10% und padding-bottom von 10% ergibt vertikal 20% der width der Ansicht über das Quadrat. Das Ergebnis ist ein Quadrat, das sich mit der width der Ansicht skaliert.

Wir haben ein Quadrat, das sich mit der width der Ansicht skaliert (siehe Live-Test).

Das sieht gut aus, aber wir müssen bedenken, dass wir die width und height unseres Elements auf null gesetzt haben, was bedeutet, dass wir darauf nicht box-sizing: border-box anwenden können und das Hinzufügen eines border, das nicht die gesamte vom Quadrat auf dem Bildschirm eingenommene Fläche ausmacht, erfordert in diesem Fall entweder eine Emulation mit einem eingesetzten box-shadow oder die Subtraktion der border-width vom padding mit calc().

$edge-len: 20%;
$bw: .5em; // border width

.square {
  position: absolute;
  border: solid $bw currentColor;
  padding: calc(#{.5*$edge-len} - #{$bw});
}

Auch das Erzielen cooler Effekte mit background-clip, wie ein transparenter Raum zwischen dem background und dem border, wird komplizierter. Für einen einfachen, abgesetzten border müssen wir sowohl die border-width als auch die box-shadow-Streuung mit calc() vom padding abziehen. Ganz zu schweigen davon, dass wir auch einen margin benötigen, der der box-shadow-Streuung entspricht, um die Positionierung zu korrigieren.

$edge-len: 20%;
$bw: .5em; // border width
$s: .75em; // shadow spread

.square {
  position: absolute;
  margin: $s;
  border: solid $bw transparent;
  padding: calc(#{.5*$edge-len} - #{$bw + $s});
  box-shadow: 0 0 0 $s currentColor;
  background: #e18728 padding-box;
}

Für einen doppelt abgesetzten border bleibt uns keine andere Wahl, als ein Pseudoelement zu verwenden.

$edge-len: 20%;
$bo: .25em; // outer border width
$so: .5em; // outer gap width
$bi: 1em; // inner border width
$si: .75em; // inner gap width
$inner-len: calc(100% - #{2*($so + $si + $bo + $bi)});

.square {
  position: absolute;
  padding: .5*$edge-len;
  box-shadow: inset 0 0 0 $bo;

  &:before {
    position: absolute;
    border: solid $bi currentColor;
    padding: $si;
    width: $inner-len; height: $inner-len;
    transform: translate(-50%, - 50%);
    background: #e18728 content-box;
    content: '';
  }
}

Die zweite Konsequenz ist, dass das Verschieben eines solchen Elements entlang seiner z-Achse (vorwärts und rückwärts) um einen Betrag, der von mindestens einer seiner ansichtsabhängigen Dimensionen (width und height) abhängt, ebenfalls nicht so einfach ist.

Wir haben bereits festgestellt, dass wir keinen %-Wert verwenden können, um ein Element entlang seiner z-Achse um einen Betrag zu verschieben, der von einer Ansichtsdimension abhängt. Mit Transformationen gibt es jedoch mehr als eine Möglichkeit, ein Element in eine bestimmte Position zu bringen.

Zum Beispiel ist rotate(53deg) translate(5em) äquivalent zu translate(3em, 4em) rotate(53deg), wie in diesem Live-Test zu sehen ist.

Während wir also unser Quadrat nicht um die Hälfte seiner width nach vorne bewegen können, indem wir translateZ() mit einem %-Wert verwenden, können wir eine Transformationskette finden, die keine translateZ()-Funktion verwendet, aber unser Element trotzdem genau dort platziert, wo wir es haben wollen.

Betrachten wir das Quadrat ohne Rand, das wir oben erhalten haben, und nehmen wir an, wir wollen es um die Hälfte seiner Kantenlänge nach vorne bewegen. Wir haben mehr als eine Möglichkeit, dies zu tun.

Zum Beispiel könnten wir damit beginnen, es um -90° um seine y-Achse zu drehen. Dies dreht sowohl unser Quadrat als auch sein Koordinatensystem, sodass es jetzt nach links zeigt und seine x-Achse auf uns zu, aus dem Bildschirm heraus, zeigt.

Als Nächstes verschieben wir es um 50% entlang seiner x-Achse. Die x-Achse zeigt nun auf uns zu, was bedeutet, dass unser Quadrat um die Hälfte seiner Kantenlänge nach vorne bewegt wird. Die vertikale Mittellinie unseres Quadrats befindet sich an der gewünschten Position, aber wir sehen unser Quadrat von rechts und wollen es von vorne sehen.

Deshalb ist unser letzter Schritt, die anfängliche Drehung rückgängig zu machen. Also drehen wir unser Quadrat um 90° um seine y-Achse – das lässt es wieder zu uns zeigen, während seine x-Achse wieder nach rechts zeigt.

Diese drei Schritte führen zur Transformationskette rotateY(-90deg) translateX(50%) rotateY(90deg) und werden durch die folgende Demo veranschaulicht (klicken Sie zum Abspielen)

See the Pen translating a square forward by half its edge length without translateZ – method 1 by Ana Tudor (@thebabydino) on CodePen.

Eine weitere Transformationskette, die dasselbe Ergebnis liefert, ist rotateX(90deg) translateY(50%) rotateX(-90deg). Genau wie die vorherige verwendet sie den Trick, das Element (und zusammen damit sein lokales Koordinatensystem) zu drehen, damit wir eine andere Achse (in diesem Fall y) in die Richtung zeigen lassen, in die die z-Achse vor dieser Drehung zeigte, entlang dieser anderen Achse (y) zu verschieben und dann schließlich die erste Drehung rückgängig zu machen. Wir können dies im folgenden Pen sehen

See the Pen translating a square forward by half its edge length without translateZ – method 2 by Ana Tudor (@thebabydino) on CodePen.

Beachten Sie, dass die beiden vorherigen Demos in Edge nicht funktionieren.

Erstellen eines responsiven Würfels mit %-Werten

Wir beginnen mit der folgenden Struktur

<div class='cube'>
  <div class='cube--face'></div>
  <div class='cube--face'></div>
  <div class='cube--face'></div>
  <div class='cube--face'></div>
  <div class='cube--face'></div>
  <div class='cube--face'></div>
</div>

Wir könnten dies mit einem Präprozessor wie Haml oder Slim oder Ähnlichem vereinfachen.

.cube
  - 6.times do
    .cube__face

Ich tue das, weil ich es nicht mag, dasselbe mehrmals zu schreiben, und mit etwas wie Haml führe ich nicht einmal eine Schleifenvariable ein, die ich sowieso nicht in der Schleife verwende. Es ist hier größtenteils eine Frage der persönlichen Vorliebe, da wir sowieso nicht viel Code haben. Es gibt nur 7 HTML-Elemente: das .cube-Element und seine 6 Flächenkinder1.

Wir nehmen das body-Element als unsere Szene, sodass es die gesamte Ansicht height einnimmt und wir ihm eine perspective geben. Denken Sie daran, dass dies eine willkürliche Entscheidung ist, die nur getroffen wurde, um die Dinge zu vereinfachen. Wir könnten genauso gut jede beliebige Szene nehmen, deren width irgendwie von der width der Ansicht abhängt.

Wir positionieren dann unsere Elemente absolut und stellen sicher, dass der .cube genau in der Mitte der Szene liegt und perspective-style: preserve-3d hat, damit seine Kinder auch in 3D transformiert werden können.

body {
  height: 100vh;
  perspective: 20em;
}

div { position: absolute; }

.cube {
  top: 50%; left: 50%;
  transform-style: preserve-3d;
}

Der nächste Schritt wäre, die Würfelflächen wie zuvor mit padding zu dimensionieren, richtig? Nun, wenn wir das tun, stellen wir fest, dass das padding tatsächlich zu 0px ausgewertet wird!

Wenn wir die berechneten Stile in den Entwicklertools prüfen: unser %-Wert-Padding wird zu 0px ausgewertet!

Das liegt daran, dass sie nicht mehr Kinder des body sind, sondern Kinder des .cube-Elements, das ein absolut positioniertes 0x0-Element ist.

Wir können dies überwinden, indem wir das Cube-Element mit dem padding-Trick dimensionieren und dann seine Kinder gleich groß machen.

$edge-len: 16%;

.cube {
  /* previous styles */
  margin: -.5*$edge-len;
  padding: .5*$edge-len;

  &__face {
    top: 0; right: 0; bottom: 0; left: 0;
    background: orange;
  }
}

Wir haben auch einen negativen margin auf dem .cube-Element hinzugefügt, damit sein Mittelpunkt genau in der Mitte der Szene liegt und nicht seine obere linke Ecke.

See the Pen responsive cube using % values – step 1 by Ana Tudor (@thebabydino) on CodePen.

Das ist ein Anfang. Alle Flächenelemente haben die gewünschte Größe und befinden sich an der gewünschten Stelle. Nun wollen wir sehen, wie wir die 6 Flächenelemente in 3D transformieren können, damit sie einen Würfel bilden.

Wir teilen die 6 Flächen in zwei Kategorien ein: 4 seitliche Flächen und 2 Grundflächen. Wir betrachten die seitlichen Flächen als rechts, hinten, links und vorne und die Grundflächen als oben und unten. Dies ist eine willkürliche Entscheidung. Wir hätten die seitlichen Flächen als unten, vorne, oben und hinten und die Grundflächen als links und rechts nehmen können. Wir nehmen die ersten 4 Flächenelemente in DOM-Reihenfolge als die seitlichen und die anderen als die Grundflächen.

Als Nächstes wählen wir die Achse aus, entlang derer wir die Verschiebungen vornehmen wollen, die Achse, die auf die Position im Würfel (vorne, unten, rechts...) zeigen soll, wo wir unsere Flächenelemente platzieren wollen. Es kann nicht die z-Achse sein, da wir hier mit %-Werten arbeiten müssen, also wählen wir die x-Achse. Denken Sie auch daran, dass dies völlig willkürlich ist – tatsächlich könnten Sie die Verwendung der y-Achse stattdessen als etwas ausprobieren, nachdem Sie dies gelesen haben.

Wir nehmen das erste Flächenelement. Ohne angewendete Transformationen zeigt seine x-Achse nach rechts. Also machen wir es zur rechten Fläche des Würfels. Wir verschieben es um 50% in positiver Richtung entlang seiner x-Achse. Dadurch fällt seine vertikale Mittellinie mit der vertikalen Mittellinie der rechten Fläche des Würfels zusammen. Dann drehen wir es um 90° um seine y-Achse, um es in die gewünschte Position auf dem Würfel zu bringen (nach rechts zeigend).

See the Pen position face element on the right of cube by Ana Tudor (@thebabydino) on CodePen.

Das CSS für dieses erste .cube__face-Element ist

.cube__face:first-child {
  transform: translateX(50%) rotateY(90deg);
}

Zur Konsistenz können wir dies in der folgenden äquivalenten Form schreiben (eine Drehung von um eine beliebige Achse hat keine Wirkung)

.cube__face:nth-child(1) {
  transform: rotateY(0deg) translateX(50%) rotateY(90deg);
}

Wir gehen zur zweiten Fläche über, die wir auf der Rückseite des Würfels platzieren. Das bedeutet, wir drehen sie zuerst um 90° um ihre y-Achse, damit ihre x-Achse in Richtung dieser Fläche zeigt (also nach hinten). Dann verschieben wir sie um 50% in positiver Richtung entlang ihrer x-Achse. Nun fällt ihre vertikale Mittellinie mit der der Rückseite des Würfels zusammen. Der letzte Schritt ist, sie wieder um 90° um ihre y-Achse zu drehen, damit sie sich in der gewünschten Position auf dem Würfel befindet (nach hinten zeigend).

See the Pen position face element on the back of cube by Ana Tudor (@thebabydino) on CodePen.

Wir notieren die transform für diese Fläche

.cube__face:nth-child(2) {
  transform: rotateY(90deg) translateX(50%) rotateY(90deg);
}

Nun ist die dritte Fläche an der Reihe, positioniert zu werden. Diesmal auf der linken Seite des Würfels. Wir beginnen damit, sie um 180° um ihre y-Achse zu drehen, damit wir ihre x-Achse nach links zeigen lassen können, wo wir sie platzieren wollen. Dann verschieben wir sie um 50% in positiver Richtung entlang dieser x-Achse. Dies platziert ihre vertikale Mittellinie auf der der linken Fläche des Würfels. Schließlich drehen wir sie um weitere 90° um ihre y-Achse, damit sie korrekt auf dem Würfel positioniert ist (nach rechts zeigend).

See the Pen position face element on the left of cube by Ana Tudor (@thebabydino) on CodePen.

Die Transformationskette in diesem Fall ist also

.cube__face:nth-child(3) {
  transform: rotateY(180deg) translateX(50%) rotateY(90deg);
}

Nun kommen wir zur letzten seitlichen Fläche! Diese positionieren wir an der Vorderseite des Würfels. Wie bei den drei vorherigen müssen wir sie zuerst um ihre y-Achse drehen, damit ihre x-Achse nach vorne zeigt. Wir haben zuvor gesehen, dass dies durch eine Drehung von -90° um die y-Achse erreicht werden kann. Aber eine Drehung von -90° bringt ein Element in die gleiche Position wie eine Drehung von 270°, also verwenden wir aus Konsistenzgründen hier 270°. Nach der Drehung von 270° um die y-Achse verschieben wir unser Element um 50% in positiver Richtung entlang seiner x-Achse. Und schließlich drehen wir es um weitere 90° um seine y-Achse, damit es nach vorne und nicht nach links zeigt.

See the Pen position face element on the front of cube by Ana Tudor (@thebabydino) on CodePen.

Die Transformationskette für diese letzte seitliche Fläche ist

.cube__face:nth-child(4) {
  transform: rotateY(270deg) translateX(50%) rotateY(90deg);
}

Nun können wir uns den Grundflächen zuwenden. Um die erste Grundfläche an der Unterseite des Würfels zu positionieren, ist der erste Schritt, sie so zu drehen, dass ihre x-Achse in diese Richtung (nach unten) zeigt. Das ist eine Drehung um 90° um ihre z-Achse. Der nächste Schritt ist, sie um 50% in positiver Richtung entlang dieser x-Achse zu verschieben, die nun nach unten zeigt, wodurch ihre Mittellinie auf die untere Fläche des Würfels gebracht wird. Und schließlich drehen wir sie um 90° um ihre y-Achse, damit sie nach unten zeigt.

See the Pen position face element on the bottom of cube by Ana Tudor (@thebabydino) on CodePen.

Das bedeutet, dass das CSS zur Positionierung dieser Fläche lautet

.cube__face(5) {
  transform: rotateZ(90deg) translateX(50%) rotateY(50%);
}

Und wir kommen zur letzten Fläche, die wir oben auf dem Würfel platzieren. Dazu beginnen wir mit einer Drehung, sodass ihre x-Achse nach oben zeigt – das ist eine Drehung um -90° um ihre z-Achse. Anschließend verschieben wir sie um 50% in positiver Richtung entlang dieser x-Achse, die nun nach oben zeigt. So haben wir die Mittellinie der Fläche auf die obere Fläche des Würfels gebracht. Der letzte Schritt ist, sie um 90° um ihre y-Achse zu drehen, damit sie nach oben zeigt.

See the Pen position face element on the top of cube by Ana Tudor (@thebabydino) on CodePen.

.cube__face(6) {
  transform: rotateZ(-90deg) translateX(50%) rotateY(50%);
}

Wenn wir alles zusammenfügen, können wir einige Muster erkennen

.cube__face(1) { /* 1 = 0 + 1 */
  transform: rotateY(0deg) /* 0° = 0*90° */
             translateX(50%) rotateY(50%);
}
.cube__face(2) { /* 2 = 1 + 1 */
  transform: rotateY(90deg) /* 90° = 1*90° */
             translateX(50%) rotateY(50%);
}
.cube__face(3) { /* 3 = 2 + 1 */
  transform: rotateY(90deg) /* 180° = 2*90° */
             translateX(50%) rotateY(50%);
}
.cube__face(4) { /* 4 = 3 + 1 */
  transform: rotateY(270deg) /* 270° = 3*90° */
             translateX(50%) rotateY(50%);
}
.cube__face(5) { /* 5 = 4 + 1 */
  transform: rotateZ(90deg) /* 90° = 1*90° = ((-1)^4)*90° */
             translateX(50%) rotateY(50%);
}
.cube__face(6) { /* 6 = 5 + 1 */
  transform: rotateZ(-90deg) /* -90° = -1*90° = ((-1)^5)*90° */
             translateX(50%) rotateY(50%);
}

Das Erste, was hier auffällt, ist, dass die letzten beiden Transformationsfunktionen in der Kette immer gleich sind. Das Nächste ist, dass wir eine allgemeine Formel für die seitlichen Flächen und die Grundflächen ableiten können. Für die seitlichen Flächen ist die erste Transformationsfunktion rotateY($i*90deg), wobei $i der Flächenindex ist. Für die Grundflächen ist die erste Transformationsfunktion rotate(pow(-1, $i)*90deg). Das bedeutet, wir können alles in einer Schleife zusammenfassen, so:

@for $i from 0 to 6 {
  .cube__face:nth-child(#{$i + 1}) {
    transform:
      if($i < 4, rotateY($i*90deg),
                 rotateZ(pow(-1, $i)*90deg))
      translateX(50%) rotateY(90deg);
  }
}

Das Ergebnis der Hinzufügung des obigen Codes zu dem, was wir zuvor hatten, kann im folgenden Pen gesehen werden

See the Pen responsive cube using % values – step 2 by Ana Tudor (@thebabydino) on CodePen.

Es sieht nicht so aus, als hätte sich etwas geändert, aber wenn wir die Flächen halbtransparent machen, ihnen eine Art Umriss geben und die Drehung des .cube-Elements animieren, wird offensichtlich, dass wir jetzt eine 3D-Form haben. Sogar eine responsive!

Responsiver Würfel, erstellt mit %-Werten (siehe Live-Demo).

Wir könnten dies auch ein wenig anpassen, sodass die Szene nicht mehr der body ist

See the Pen responsive cube using % values – step 4 (scene != body) by Ana Tudor (@thebabydino) on CodePen.

Nun scheint dies nicht zu schlecht zu sein. Für den nicht-responsiven Fall hat die Transformationskette, die zur Positionierung einer Fläche auf dem Würfel benötigt wird, eine Länge von 2 – eine Drehung und ein translateZ()

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

Bei einem responsiven Würfel, der mit %-Werten erstellt wurde, hat sie eine Länge von 3 – eine Drehung und dann ein translateX(50%) und ein rotateY(50%). Das sind insgesamt nur 6 zusätzliche Transformationsfunktionen – damit können wir leben.

Das Problem ist, dass, je mehr wir tun wollen, desto komplexer die Dinge werden.

Ein komplexeres Beispiel

Nehmen wir an, wir wollen so etwas wie einen Zauberwürfel haben. Das ist eine Anordnung von Würfeln, mit 3 entlang jeder Dimension. Das sind insgesamt 3*3*3 = 27 Würfel. Das bedeutet, wir haben die folgende Struktur

.assembly
  - 27.times do
    .cube
      - 6.times do
        .cube__face

Jeder Würfel wird fast wie oben erstellt, mit ein paar Unterschieden.

Der wichtigste ist, dass der Elternteil der Würfel jetzt nicht mehr die Szene ist, sondern das .assembly-Element. Das ist, wie alles andere in der Szene, absolut positioniert, sodass seine Größe standardmäßig 0x0 ist. In diesem Fall müssen wir die .cube-Elemente und ihre Flächen so groß machen wie die Baugruppe.

$edge-len: 8%;

div {
  position: absolute;
  transform-style: preserve-3d;
}

.assembly {
  top: 50%; left: 50%;
  margin: -.5*$edge-len;
  padding: .5*$edge-len;
  transform: rotateX(-30deg) rotateY(30deg);
}

[class*='cube'] {
  top: 0; right: 0; bottom: 0; left: 0;
}

Was wir bisher haben, sind 27 Würfel, die alle in der Mitte der Szene positioniert sind

See the Pen responsive cube assembly using % values – step 0 by Ana Tudor (@thebabydino) on CodePen.

Um etwas zu erstellen, das einem Zauberwürfel ähnelt, müssen wir diese Würfel entlang der drei Raumdimensionen verteilen. Entlang jeder Dimension haben wir 3 Würfel. Der erste wird um seine Kantenlänge in negativer Achsenrichtung verschoben, der zweite bleibt an Ort und Stelle und der dritte wird um seine Kantenlänge in positiver Achsenrichtung verschoben. Das bedeutet, dass die Werte für diese Verschiebungen -100%, 0 und 100% sind. Diese drei Werte können als -1*100%, 0*100% und 1*100% geschrieben werden. Da unsere Indizes entlang jeder Achse 0, 1 und 2 sind, bedeutet dies, dass die Verschiebung entlang jeder Achse der Index minus 1 ist, alles multipliziert mit 100%.

Unser grundlegender Verteilungscode sieht also wie folgt aus

@for $i from 0 to 3 { // along the x axis
  $x: ($i - 1)*100%;

  @for $j from 0 to 3 { // along the y axis
    $y: ($j - 1)*100%;

    @for $k from 0 to 3 { // along the z axis
      $z: ($k - 1)*100%;
      $idx: $i*3*3 + $j*3 + $k + 1;

      .cube:nth-child(#{$idx}) {
        transform: translate3d($x, $y, $z);
      }
    }
  }
}

Das Problem ist, dass dieser Code nichts tut, wenn %-Werte verwendet werden. Wenn wir $z weglassen und nur translate($x, $y) verwenden, können wir die Würfel entlang der ersten beiden Dimensionen verteilt sehen.

Diese Verteilung entlang der x- und y-Achsen sieht in Chrome und Edge perfekt aus. Firefox hat 3D-Reihenfolgenprobleme, aber wir können diese im statischen Fall leicht mit z-index beheben. Update: Dieser 3D-Reihenfolgenfehler wurde jetzt in Firefox 55+ behoben.

Verteilung entlang der x- und y-Achsen, erwartetes Ergebnis und was wir in Chrome und Edge (links) vs. Firefox 54- Ergebnis (rechts) erhalten

Bei der Art und Weise, wie wir unsere Anordnung gedreht haben, zeigt die x-Achse nach hinten, und wir möchten, dass die Würfel hinten hinter denen vorne liegen. Je höher also der $i-Wert ist, desto niedriger sollte der z-index sein – das bedeutet, wir addieren ihn negativ, wenn wir den Wert berechnen. Die y-Achse zeigt nach unten, und wir möchten, dass die Würfel weiter oben über denen weiter unten liegen. Je höher also der $j-Wert ist, desto niedriger sollte der z-index sein – das bedeutet, wir addieren ihn ebenfalls negativ. Die z-Achse zeigt nach rechts, und wir möchten, dass die Würfel links hinter denen rechts liegen. Das bedeutet, je höher der $k-Wert ist, desto höher ist der z-index, also müssen wir ihn positiv addieren. Das ergibt

z-index: $k - $i - $j;

Jetzt sieht es auch in Firefox gut aus

Siehe den Pen responsive cube assembly using % values – step 3 von Ana Tudor (@thebabydino) auf CodePen.

Aber was ist mit der dritten Dimension? Nun, wir können es wie zuvor machen: jede Seite um ihre y-Achse drehen, so dass ihre x-Achse in die Richtung zeigt, in die ihre ursprüngliche z-Achse zeigte, und dann um $z entlang dieser x-Achse verschieben. Das bedeutet, unsere Transformationskette wird

transform: translate($x, $y) rotateY(90deg) translateX($z);

Das erledigt die Aufgabe

Responsive Anordnung von Würfeln (siehe Live-Demo).

Wir haben unsere schöne, responsive Zauberwürfel-Struktur erhalten. Aber das geht auf Kosten von 2 zusätzlichen Transformationsfunktionen pro Würfel. Und wir haben 27 Würfel, also 54 zusätzliche Transformationsfunktionen. Unser CSS ist gerade viel größer geworden.

Responsive 3D-Formen mit Viewport-Einheiten

Das sollte einfacher sein, oder? Ja, aber… abhängig davon, welche Einheiten wir wählen und wie wir unsere 3D-Formen animieren möchten, könnten wir auf Fehler stoßen.

Ich verwende Viewport-Einheiten lieber als %, da sie nicht die gleichen Einschränkungen und Komplikationen mit sich bringen. Ich kann die Formen je nach height des Viewports skalieren, nicht nur nach der width. Noch besser, abhängig von der kleineren Viewport-Dimension! Und das Erstellen von Formen funktioniert genauso wie bei px oder em, es müssen keine zusätzlichen Transformationsfunktionen zur Kette hinzugefügt werden.

Wir müssen uns jedoch einiger Browserprobleme bewusst sein.

Edge unterstützt vmax noch nicht

Das hat mich nicht sehr gestört, da ich selten vmax-große Formen wollte und in den sehr seltenen Fällen, in denen ich es wollte, konnte ich es irgendwie umgehen.

Aktuelle Lösungen

Der erste Workaround, der einem in den Sinn kommt, wenn man ein Quadrat erstellen möchte, dessen Größe vom maximalen Viewport-Maß abhängt, ist, seine width und height auf einen Wert mit vw-Einheiten zu setzen und seine min-width und min-height auf denselben Wert mit vh-Einheiten zu setzen.

.boo {
  width: 20vw; height: 20vw;
  min-width: 20vh; min-height: 20vh;
}

Das funktioniert wie Zauberei

Testen der vmax-Emulation (siehe Live-Demo).

Wir können dies mit Sass wartungsfreundlicher gestalten

$edge-len-landscape: 20vw;
$edge-len-portrait: $edge-len-landscape*1vh/1vw;

.boo {
  width: $edge-len-landscape;
  height: $edge-len-landscape;
  min-width: $edge-len-portrait;
  min-height: $edge-len-portrait;
}

Wenn wir nun ändern möchten, wie stark die Kantenlänge von der maximalen Dimension abhängt, müssen wir nur den Wert von $edge-len-landscape ändern und müssen uns keine Sorgen machen, vielleicht einen Wert irgendwo zu vergessen.

Leider reicht das nicht aus, wenn wir uns in 3D bewegen wollen. Um die Flächen in der Mitte zu positionieren, benötigen wir einen negativen Rand, der von der Kantenlänge abhängt. Um einen Würfel zu erstellen, müssen wir jede Fläche um einen Betrag verschieben, der von der Kantenlänge abhängt. Aber welcher? Die Portrait- oder die Landschaftsversion?

Wir haben zwei Optionen, die heute funktionieren: Wir können entweder eine Orientierungs- (oder Seitenverhältnis-) Media Query verwenden oder wir könnten %-Werte innerhalb der translate()-Funktion verwenden, wie wir es zuvor getan haben.

Gehen wir also zurück zu unserem Würfelbeispiel.

Mit der ersten Methode kombinieren wir die obige Emulation mit den regulären transform-Ketten, die wir im nicht-responsiven Fall anwenden würden, und fügen eine Media Query für den transform-Teil hinzu

$edge-len-landscape: 20vw;
$edge-len-portrait: $edge-len-landscape*1vh/1vw;

.cube__face {
  margin: -.5*$edge-len-landscape;
  width: $edge-len-landscape;
  height: $edge-len-landscape;
  min-width: $edge-len-portrait;
  min-height: $edge-len-portrait;

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

  @media (orientation: portrait) {
    margin: -.5*$edge-len-portrait;

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

Das sieht etwas zu viel aus, daher ist es wahrscheinlich besser, wenn wir einen Mixin verwenden

$edge-len: 20vw;

@mixin faces($l: $edge-len) {
  margin: -.5*$l;
  width: $l; height: $l;

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

.cube__face {
  @include faces();

  @media (orientation: portrait) {
    @include faces($edge-len*1vh/1vw);
  }
}

Das Ergebnis des obigen Codes ist ein responsiver Würfel, der von der maximalen Viewport-Dimension abhängt.

Responsiver Würfel, der von der maximalen Viewport-Dimension abhängt, ohne vmax zu verwenden (siehe Live-Demo).

Mit der zweiten Methode verwenden wir %-wertige Verschiebungen in Kombination mit der vmax-emulierenden Skalierung. Zusätzlich zu den transform-Ketten, die wir beim vollständigen Arbeiten mit Prozenten hatten, müssen wir am Anfang auch ein translate(-50%, -50%) hinzufügen, um den kantenabhängigen negativen margin zu kompensieren, damit unser Quadrat zunächst in der Mitte der Szene positioniert ist.

Wenn das nicht ganz klar ist, bedenken Sie Folgendes: Wir positionieren alle Elemente absolut, wir platzieren den .cube in der Mitte der Szene (top: 50%; left: 50%;) und skalieren dann seine Flächen (mit der vmax-Emulationsmethode). Aber das führt dazu, dass die Ecken unserer Flächen in der Mitte der Szene liegen, was nicht das ist, was wir wollten.

Siehe den Pen absolutely position & relative size #0 von Ana Tudor (@thebabydino) auf CodePen.

Selbst wenn wir nicht wissen, welchen negativen Rand wir anwenden sollen, weil wir nicht wissen, ob wir uns im Hoch- oder Querformat befinden, können wir dies immer noch mit einem translate(-50%, -50%) beheben – dies verschiebt das Element um die Hälfte seiner width (was auch immer das sein mag, wir müssen es nicht wissen) nach links und um die Hälfte seiner height nach oben. So erhalten wir die Anfangsposition unserer Würfelflächen.

Siehe den Pen absolutely position & relative size #1 von Ana Tudor (@thebabydino) auf CodePen.

Jetzt müssen wir die Flächen auf dem .cube verteilen, aber wenn wir einfach die transform-Ketten aus dem Fall "alles mit Prozenten" hinzufügen, überschreiben sie transform: translate(-50%, -50%) und der .cube ist nicht mehr richtig positioniert.

Siehe den Pen cube sized depending on max viewport dimension (no vmax!) – positioning fail von Ana Tudor (@thebabydino) auf CodePen.

Deshalb müssen wir translate(-50%, -50%) vor den anderen transform-Funktionen für jede Fläche verketten

@for $i from 0 to 6 {
  .cube__face:nth-child(#{$i + 1}) {
    transform: translate(-50%, -50%)
      if($i < 4, rotateY($i*90deg),
        rotateZ(pow(-1, $i)*90deg))
      translateX(50%) rotateY(50deg);
  }
}

Dies führt zu dem gewünschten Ergebnis.

Responsiver Würfel, der von der maximalen Viewport-Dimension abhängt, ohne vmax zu verwenden (siehe Live-Demo).

Zukünftige Lösungen

An dieser Stelle sind CSS-Variablen für Edge als „in Entwicklung“ aufgelistet. Sobald sie unterstützt werden, sollten wir sie für einen viel saubereren Workaround als die beiden oben genannten verwenden können. Die grundlegende Idee wäre, eine CSS-Variable für die Kantenlänge zu verwenden. Wir setzen ihren Wert zunächst auf einen vw-Wert, und dann ändert sich dieser Wert in vh innerhalb einer Media Query. Das erledigt die Aufgabe.

.boo {
  --edge-len: 20vw;

  width: var(--edge-len);
  height: var(--edge-len);
}

@media (orientation: portrait) {
  .boo { --edge-len: 20vh; }
}

Für die Erstellung von 3D-Formen wird es etwas kniffliger, da einige Eigenschaften Werte erfordern, die nicht gleich der Kantenlänge sind, sondern daraus berechnet werden. Zum Beispiel müssen wir für unseren Würfel auf den Flächen einen margin einstellen, der der halben Kantenlänge entspricht, und dann müssen wir die Flächen auch um die halbe Kantenlänge verschieben. Lösung? calc() zur Rettung!

.cube__face {
  --edge-len: 20vw;

  margin: calc(-.5*var(--edge-len));
  width: var(--edge-len); height: var(--edge-len);

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

@media (orientation: portrait) {
  .cube__face { --edge-len: 20vh; }
}

Siehe den Pen cube sized depending on max viewport dimension with CSS variables von Ana Tudor (@thebabydino) auf CodePen.

Das funktioniert perfekt in Chrome und Firefox, aber wird es auch in Edge funktionieren, wenn dort Variablen verfügbar sind? Nun, es gibt noch ein Problem in Edge. calc() funktioniert innerhalb von Translate-Funktionen, wenn es für die Translate-Werte entlang der x- und y-Achsen verwendet wird, aber nicht für die entlang der z-Achse. Wenn wir calc() innerhalb von translateZ() oder für den Translate-Wert entlang der z-Achse (das dritte Argument) innerhalb von translate3d() verwenden, dann wird der berechnete Wert für die Transformation in Edge zu none.

Der erste Workaround, der einem in den Sinn kommt, ist, mit einer weiteren CSS-Variablen, --inradius, zu beginnen, aus der wir --edge-len berechnen

.cube__face {
  --inradius: 10vw;
  --edge-len: calc(2*var(--inradius));

  margin: calc(-1*var(--inradius));
  width: var(--edge-len); height: var(--edge-len);

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

@media (orientation: portrait) {
  .cube__face { --inradius: 10vh; }
}

Der Inradius eines Würfels und jeder seiner quadratischen Flächen ist gleich der halben Kantenlänge.

Siehe den Pen cube sized depending on max viewport dimension with CSS variables #2 von Ana Tudor (@thebabydino) auf CodePen.

Das sollte wahrscheinlich in Edge funktionieren, sobald CSS-Variablen dort verfügbar sind, aber wir werden es erst sicher wissen, wenn es soweit ist. Update: Die 3D-Form funktioniert in Edge 15, obwohl die Flächen flackern aus einem Grund, der nichts mit CSS-Variablen zu tun hat.

Verwendung von vmin-Werten innerhalb von Translate-Funktionen schlägt in Edge fehl

Das klingt wirklich unbequem, denn vmin scheint die bevorzugte Einheit zu sein, wenn man responsive 3D-Formen erstellen möchte. Glücklicherweise gibt es dafür eine praktische Lösung: die font-size in vmin setzen und dann mit em arbeiten!

Siehe den Pen is earth flat? (pure CSS 3D) von Ana Tudor (@thebabydino) auf CodePen.

Animation von Verschiebungen, die Viewport-Einheiten verwenden, schlägt in den meisten Browsern nach einer Viewport-Änderung fehl

Das ist kein Problem, wenn wir Verschiebungen, die Viewport-Einheiten verwenden, nicht animieren.

Dies ist jedoch das problematischste Problem für mich, da ich oft Dinge über eine einfache Drehung der 3D-Anordnung hinaus animieren möchte und keine Lösung für dieses Problem finden konnte.

Betrachten Sie das folgende Beispiel: Wir haben ein Rechteck, das wir von links nach rechts auf dem Bildschirm animieren (Live-Demo)

.boo {
  margin-left: -5em;
  width: 10em; height: 7.5em;
  animation: ani 1s ease-in-out infinite alternate;
}

@keyframes ani {
  to { transform: translate(100vw); }
}

Vor der Größenänderung des Viewports sieht alles in allen Browsern gut aus.

Aber wenn wir die width des Viewports vergrößern oder verkleinern, animieren sowohl Chrome als auch Safari weiterhin auf denselben absoluten Wert wie zuvor. Es ist, als wäre der 100vw-Wert durch sein px-Äquivalent ersetzt worden. Das Rechteck wird nicht mehr zum rechten Rand des Viewports animiert, sondern darüber hinaus, wenn wir die width des Viewports verkleinert haben, und bis zu einem Punkt in der Mitte, wenn wir sie vergrößert haben. Und der font-size-Trick, der für das vorherige Edge-Problem verwendet wurde, hilft in diesem Fall nicht.

Edge macht auch etwas Seltsames – nach der Größenänderung wird das Element rechts vom Viewport verschoben, verschwindet dann einfach und erscheint wieder auf dem Bildschirm.

Firefox ist der einzige Browser, der dies richtig macht.

Für ein einfaches 3D-Beispiel nehmen wir eine 3D-Anordnung mit zwei identischen Würfeln an.

<div class='assembly'>
  <div class='cube'>
    <div class='cube__face'></div>
    <!-- 5 more cube faces here -->
  </div>
  <div class='cube'>
    <div class='cube__face'></div>
    <!-- 5 more cube faces here -->
  </div>
</div>

Oder die DRY-Präprozessor-Version

.assembly
  - 2.times do
    .cube
      - 6.times do
        .cube__face

Ihre Kantenlänge ist in vmin-Einheiten angegeben (naja, in em-Einheiten, aber mit font-size auf einen vmin-Wert gesetzt, damit wir den zuvor erwähnten Edge-Bug vermeiden). Wir platzieren die .assembly genau in der Mitte der Szene und verschieben dann die Würfel nach rechts und links um ein Viertel der Viewport-Breite (das sind 25vw). Die grundlegenden Stile, bevor wir überhaupt etwas in 3D tun, würden also etwa so aussehen

$edge-len: 16em;
$offset: 25vw;

body {
  height: 100vh;
  perspective: 32em;
}

div {
  position: absolute;
  transform-style: preserve-3d;
}

.assembly { top: 50%; left: 50%; }

.cube {
  &:nth-child(1) { left: -$offset; }
  &:nth-child(2) { left: $offset; }

  &__face {
    margin: -.5*$edge-len;
    width: $edge-len; height: $edge-len;
    background: url($image); /* so we can see it */
  }
}

Das bisherige Ergebnis ist im folgenden Pen zu sehen

Siehe den Pen colliding cubes – step #0 von Ana Tudor (@thebabydino) auf CodePen.

Wir könnten die Würfelverschiebungen von diesem Punkt an etwas kompakter gestalten, indem wir eine indexbasierte Vorzeichenumschaltung verwenden, damit wir sie in einer Schleife setzen können. -$offset = -1*$offset und $offset = 1*$offset. Außerdem gilt -1 = (-1)^1 = (-1)^(0 + 1) und 1 = (-1)*(-1) = (-1)^2 = (-1)^(1 + 1). Das ergibt

.cube {
  @for $i from 0 to 2 {
    &:nth-child(#{$i + 1}) {
      left: pow(-1, $i + 1)*25vw;
    }
  }
}

Die Flächen sind exakt wie im nicht-responsiven Fall positioniert.

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

Wir haben jetzt die beiden Würfel

Siehe den Pen colliding cubes – step #1 von Ana Tudor (@thebabydino) auf CodePen.

Der nächste Schritt ist die Animation, damit sie kollidieren. Das bedeutet, den ersten nach rechts (in positive Richtung der x-Achse) und den zweiten nach links (in negative Richtung der x-Achse) zu verschieben. Sie um $offset zu verschieben, platziert sie beide genau in der Mitte, überlappend, aber das wollen wir nicht. Wir wollen, dass die rechte Fläche des ersten Würfels die linke Fläche des zweiten Würfels genau in der Mitte berührt. Das bedeutet, dass beide einen halben Kantenlänge vom Mittelpunkt entfernt sein müssen. Es ist, als hätten wir sie um $offset in eine Richtung und dann um die halbe Kantenlänge in die entgegengesetzte Richtung verschoben. Die @keyframes sehen also so aus

.cube {
  animation: move 2s ease-in infinite alternate;

  @for $i from 0 to 2 {
    &:nth-child(#{$i + 1}) {
      left: pow(-1, $i + 1)*25vw;
      animation-name: move#{$i + 1};
    }
  }
}

@keyframes move1 {
  to {
    transform: translateX(calc(1*(#{$offset} - #{.5*$edge-len})));
  }
}
@keyframes move2 {
  to {
    transform: translateX(calc(-1*(#{$offset} - #{.5*$edge-len})));
  }
}

Wir können diesen Code effizienter gestalten, indem wir die @keyframes innerhalb der .cube-Schleife generieren

.cube {
  animation: move 2s ease-in infinite alternate;

  @for $i from 0 to 2 {
    &:nth-child(#{$i + 1}) {
      left: pow(-1, $i + 1)*25vw;
      animation-name: move#{$i + 1};
    }

    @at-root {
      @keyframes move#{$i + 1} {
        to {
          transform: translateX(calc(#{pow(-1, $i)}*(#{$offset} - #{.5*$edge-len})));
        }
      }
    }
  }
}

Das Ergebnis sieht in allen Browsern großartig aus. Das heißt… bis wir die Viewport-Größe ändern. WebKit-Browser aktualisieren den Verschiebungsbetrag in den Keyframes nicht, um ihn an den neuen Viewport anzupassen, und Edge ebenfalls nicht.

Der viewport-relative Verschiebungsbetrag wird nach der Viewport-Größenänderung in WebKit-Browsern und Edge nicht aktualisiert (siehe Live-Demo).

Eine komplexere Situation, in der dies Dinge kaputt macht, ist, wenn man von einer 3D-Form zu einer anderen durch Stutzen morphisiert.

Siehe den Pen tetrahedron truncation sequence (interactive, ~responsive) von Ana Tudor (@thebabydino) auf CodePen.

Das Ändern der Größe des Viewports führt in diesem Fall dazu, dass die dreieckigen Flächen, die sich öffnen, wenn wir mit dem Stutzen des Tetraeders von seinen Ecken beginnen, nicht mehr korrekt positioniert sind, da ihr Platz im 3D-Raum ebenfalls durch eine Verschiebung bestimmt wird, deren Wert von der Kantenlänge des Tetraeders abhängt (die Viewport-Einheiten verwendet, damit sich die gesamte 3D-Form mit dem Viewport skaliert).


1 Ich habe viele Demos und Tutorials gesehen, die .front, .back, .left, .right, .top, .bottom Klassen zu diesen Flächenelementen hinzugefügt haben, und ich persönlich finde das sinnlos, sogar schädlich. Wenn wir den gesamten Würfel in 3D drehen, was wir oft tun wollen, dann ist die .front Fläche aus unserer Sicht nicht mehr vorne und das kann verwirrend sein. Ihnen allen einen anderen Namen zu geben, lenkt irgendwie von der Tatsache ab, dass sie alle ähnliche Entitäten sind; es spielt keine Rolle, welche wir wohin legen – die erste nach DOM-Reihenfolge könnte vorne am Würfel, rechts oder unten liegen, je nachdem, welche generische Verteilungsformel wir wählen. Die Verwendung einer allgemeinen indexabhängigen Formel, mit der wir sie alle in 3D positionieren können, ist ein weiterer sehr guter Grund, diese Klassen abzuschaffen.