So erstellen Sie ein reines CSS-3D-Paket Toggle | CSS-Tricks

Wissen Sie, wie Sie Kartons bekommen, die ganz flach kommen? Sie falten sie zusammen und kleben sie zu einer nützlichen Schachtel. Wenn es dann an der Zeit ist, sie zu recyceln, schneidest du sie wieder auseinander, um sie zu glätten. Vor kurzem hat mich jemand bezüglich dieses Konzepts als 3D-Animation angesprochen und ich dachte, es wäre ein interessantes Tutorial, es vollständig in CSS zu machen, also sind wir hier!

Wie könnte diese Animation aussehen? Wie könnten wir diese Verpackungszeitachse erstellen? Könnte die Größe flexibel sein? Lassen Sie uns eine reine CSS-Paketumschaltung vornehmen.

Hier ist, worauf wir hinarbeiten. Tippen Sie auf , um den Karton zu verpacken und auszupacken.

Wo soll man anfangen?

Wo fängt man mit so etwas überhaupt an? Planen Sie am besten im Voraus. Wir wissen, dass wir eine Vorlage für unser Paket haben werden. Und das muss in drei Dimensionen gefaltet werden. Wenn die Arbeit mit 3D in CSS für Sie neu ist, empfehle ich Ihnen diesen Artikel, um Ihnen den Einstieg zu erleichtern.

Wenn Sie mit 3D-CSS vertraut sind, könnte es verlockend sein, einen Quader zu konstruieren und von dort aus fortzufahren. Aber das wird einige Probleme aufwerfen. Wir müssen uns überlegen, wie ein Paket von 2D zu 3D übergeht.

Beginnen wir mit der Erstellung einer Vorlage. Wir müssen unser Markup im Voraus planen und darüber nachdenken, wie unsere Verpackungsanimation funktionieren soll. Beginnen wir mit etwas HTML.

<div class="scene">
  <div class="package__wrapper">
    <div class="package">
      <div class="package__side package__side--main">
        <div class="package__flap package__flap--top"></div>
        <div class="package__flap package__flap--bottom"></div>
        <div class="package__side package__side--tabbed">
          <div class="package__flap package__flap--top"></div>
          <div class="package__flap package__flap--bottom"></div>
        </div>
        <div class="package__side package__side--extra">
          <div class="package__flap package__flap--top"></div>
          <div class="package__flap package__flap--bottom"></div>
          <div class="package__side package__side--flipped">
            <div class="package__flap package__flap--top"></div>
            <div class="package__flap package__flap--bottom"></div>
          </div>
        </div>
      </div>
    </div>
  </div>
</div>

Mixins sind eine gute Idee

Da tut sich einiges. Es ist viel divS. Ich verwende Pug oft gerne zum Generieren von Markups, damit ich Dinge in wiederverwendbare Blöcke aufteilen kann. Zum Beispiel hat jede Seite zwei Klappen. Wir können ein Pug-Mixin für die Seiten erstellen und Attribute verwenden, um einen Modifikatorklassennamen anzuwenden, um das Schreiben all dieses Markups viel einfacher zu machen.

mixin flaps()
  .package__flap.package__flap--top
  .package__flap.package__flap--bottom
      
mixin side()
  .package__side(class=`package__side--${attributes.class || 'side'}`)
    +flaps()
    if block
      block

.scene
  .package__wrapper
    .package
      +side()(class="main")
        +side()(class="tabbed")
        +side()(class="extra")
          +side()(class="flipped")

Wir verwenden zwei Mixins. Man erstellt die Klappen für jede Seite der Schachtel. Der andere erstellt die Seiten der Box. Hinweis im side Mixin, das wir verwenden block. Hier werden Kinder der Mixin-Nutzung gerendert, was besonders nützlich ist, da wir einige der Seiten verschachteln müssen, um uns später das Leben zu erleichtern.

Unser generiertes Markup:

<div class="scene">
  <div class="package__wrapper">
    <div class="package">
      <div class="package__side package__side--main">
        <div class="package__flap package__flap--top"></div>
        <div class="package__flap package__flap--bottom"></div>
        <div class="package__side package__side--tabbed">
          <div class="package__flap package__flap--top"></div>
          <div class="package__flap package__flap--bottom"></div>
        </div>
        <div class="package__side package__side--extra">
          <div class="package__flap package__flap--top"></div>
          <div class="package__flap package__flap--bottom"></div>
          <div class="package__side package__side--flipped">
            <div class="package__flap package__flap--top"></div>
            <div class="package__flap package__flap--bottom"></div>
          </div>
        </div>
      </div>
    </div>
  </div>
</div>

Verschachtelung der Seiten

Das Verschachteln der Seiten erleichtert das Zusammenfalten unseres Pakets. So wie jede Seite zwei Klappen hat. Die Kinder einer Seite können die Transformation der Seiten erben und dann ihre eigene anwenden. Wenn wir mit einem Quader beginnen würden, wäre es schwierig, dies zu nutzen.

Screenshot mit HTML-Markup auf der linken Seite und einem Rendering des aufgeklappten Kartons auf der rechten Seite. Das Markup zeigt, dass eine der Seiten des Kartons ein übergeordneter Container ist, der die breite Seite des Kartons festlegt und untergeordnete Elemente für die entsprechenden oberen und unteren Klappen enthält. Orangefarbene Pfeile verbinden jedes Element mit dem visuellen Rendering, um zu zeigen, welche Teile der Box in HTML dem visuellen Rendering entsprechen.

Sehen Sie sich diese Demo an, die zwischen verschachtelten und nicht verschachtelten Elementen wechselt, um den Unterschied in der Aktion zu sehen.

Jede Kiste hat ein transform-origin mit in die untere rechte Ecke setzen 100% 100%. Wenn Sie den Schalter „Transformieren“ aktivieren, wird jedes Kästchen gedreht 90deg. Aber sehen Sie, wie sich das verhält transform ändert sich, wenn wir die Elemente verschachteln.

Wir wechseln zwischen den beiden Markup-Versionen, ändern aber nichts anderes.

Verschachtelt:

<div class="boxes boxes--nested">
  <div class="box">
    <div class="box">
      <div class="box">
        <div class="box"></div>
      </div>
    </div>
  </div>
</div>

Nicht verschachtelt:

<div class="boxes">
  <div class="box"></div>
  <div class="box"></div>
  <div class="box"></div>
  <div class="box"></div>
</div>

Alle Dinge verwandeln

Nachdem wir einige Stile auf unseren HTML-Code angewendet haben, haben wir unsere Paketvorlage.

Die Stile geben die verschiedenen Farben an und positionieren die Seiten zum Paket. Jede Seite erhält eine Position, die relativ zur „Hauptseite“ ist. (Sie werden gleich sehen, warum all diese Verschachtelungen nützlich sind.)

Es gibt einige Dinge zu beachten. Ähnlich wie bei der Arbeit mit Quadern verwenden wir --height, --width, und --depth Variablen für die Dimensionierung. Dies wird es einfacher machen, unsere Paketgröße auf der ganzen Linie zu ändern.

.package {
  height: calc(var(--height, 20) * 1vmin);
  width: calc(var(--width, 20) * 1vmin);
}

Warum Größen so definieren? Wir verwenden eine einheitenlose Standardgröße von 20, eine Idee, die ich von Lea Verou mitgenommen habe 2016 CSS ConfAsia-Vortrag (beginnt um 52:44). Wenn wir benutzerdefinierte Eigenschaften als “Daten” anstelle von “Werten” verwenden, können wir mit ihnen tun, was wir wollen calc(). Darüber hinaus muss sich JavaScript nicht um Werteinheiten kümmern und wir können in Pixel, einen Prozentsatz usw. ändern, ohne an anderer Stelle Änderungen vornehmen zu müssen. Sie könnten dies in einen Koeffizienten im umwandeln --root, aber es könnte auch schnell übertrieben werden.

Die Klappen für jede Seite brauchen auch eine Größe, die noch kleiner ist als die Seiten, zu denen sie gehören. Dies ist so, dass wir eine kleine Lücke sehen können, wie wir es im wirklichen Leben tun würden. Außerdem müssen die Klappen an zwei Seiten etwas tiefer sitzen. Dies ist so, dass wir, wenn wir sie zusammenfalten, nicht z-index Kämpfe zwischen ihnen.

.package__flap {
  width: 99.5%;
  height: 49.5%;
  background: var(--flap-bg, var(--face-4));
  position: absolute;
  left: 50%;
  transform: translate(-50%, 0);
}
.package__flap--top {
  transform-origin: 50% 100%;
  bottom: 100%;
}
.package__flap--bottom {
  top: 100%;
  transform-origin: 50% 0%;
}
.package__side--extra > .package__flap--bottom,
.package__side--tabbed > .package__flap--bottom {
  top: 99%;
}
.package__side--extra > .package__flap--top,
.package__side--tabbed > .package__flap--top {
  bottom: 99%;
}

Wir fangen auch an, darüber nachzudenken transform-origin für die einzelnen Stücke. Eine obere Klappe dreht sich von ihrer unteren Kante und eine untere Klappe dreht sich von ihrer oberen Kante.

Wir können ein Pseudo-Element für die Registerkarte auf der rechten Seite verwenden. Wir benutzen clip-path um die gewünschte Form zu erhalten.

.package__side--tabbed:after {
  content: '';
  position: absolute;
  left: 99.5%;
  height: 100%;
  width: 10%;
  background: var(--face-3);
  clip-path: polygon(0 0%, 100% 20%, 100% 80%, 0 100%);
  -webkit-clip-path: polygon(0 0%, 100% 20%, 100% 80%, 0 100%);
  transform-origin: 0% 50%;
}

Beginnen wir mit der Arbeit mit unserer Vorlage auf einer 3D-Ebene. Wir können beginnen, indem wir die .scene auf der X- und Y-Achse.

.scene {
  transform: rotateX(-24deg) rotateY(-32deg) rotateX(90deg);
}

Auffalten

Wir sind bereit, unsere Vorlage zusammenzufalten! Unsere Vorlage wird basierend auf einer benutzerdefinierten Eigenschaft gefaltet, --packaged. Wenn der Wert ist 1, dann können wir die Vorlage zusammenklappen. Lassen Sie uns zum Beispiel einige der Seiten und den Pseudoelement-Tab falten.

.package__side--tabbed,
.package__side--tabbed:after {
  transform: rotateY(calc(var(--packaged, 0) * -90deg)); 
}
.package__side--extra {
  transform: rotateY(calc(var(--packaged, 0) * 90deg));
}

Oder wir könnten eine Regel für alle Seiten schreiben, die nicht die „Haupt“-Seite sind.

.package__side:not(.package__side--main),
.package__side:not(.package__side--main):after {
  transform: rotateY(calc((var(--packaged, 0) * var(--rotation, 90)) * 1deg));
}
.package__side--tabbed { --rotation: -90; }

Und das würde alle Seiten abdecken.

Erinnern Sie sich, als ich sagte, dass die verschachtelten Seiten es uns ermöglichen, die Transformation eines Elternteils zu erben? Wenn wir unsere Demo aktualisieren, damit wir den Wert von ändern können --packaged, können wir sehen, wie sich der Wert auf die Transformationen auswirkt. Versuchen Sie, die --packaged Wert zwischen 1 und 0 und du wirst genau sehen was ich meine.

Da wir nun eine Möglichkeit haben, den Faltstatus unserer Vorlage umzuschalten, können wir mit der Arbeit an einer Bewegung beginnen. Unsere vorherige Demo wechselt zwischen den beiden Zuständen. können wir gebrauchen transition dafür. Der schnellste Weg? Füge hinzu ein transition zum transform von jedem Kind in der .scene.

.scene *,
.scene *::after {
  transition: transform calc(var(--speed, 0.2) * 1s);
}

Mehrstufige Übergänge!

Aber wir falten die Schablone nicht auf einmal zusammen – im wirklichen Leben gibt es eine Sequenz, in der wir eine Seite hochklappen und ihre Klappe zuerst dann zur nächsten übergehen und so weiter. Scoped benutzerdefinierte Eigenschaften sind dafür perfekt.

.scene *,
.scene *::after {
  transition: transform calc(var(--speed, 0.2) * 1s) calc((var(--step, 1) * var(--delay, 0.2)) * 1s);
}

Hier sagen wir das für jeden transition, benutze einen transition-delay von --step multipliziert mit --delay. Der --delay Der Wert ändert sich nicht, aber jedes Element kann definieren, welcher „Schritt“ es in der Sequenz ist. Und dann können wir die Reihenfolge, in der die Dinge passieren, deutlich machen.

.package__side--extra {
  --step: 1;
}
.package__side--tabbed {
  --step: 2;
}
.package__side--flipped,
.package__side--tabbed::after {
  --step: 3;
}

Betrachten Sie die folgende Demo, um eine bessere Vorstellung davon zu bekommen, wie dies funktioniert. Ändern Sie die Schiebereglerwerte, um die Reihenfolge zu aktualisieren, in der die Dinge passieren. Können Sie ändern, welches Auto gewinnt?

Dieselbe Technik ist der Schlüssel zu dem, was wir erreichen wollen. Wir könnten sogar eine vorstellen --initial-delay das fügt allem eine kleine Pause für noch mehr Realismus hinzu.

.race__light--animated,
.race__light--animated:after,
.car {
  animation-delay: calc((var(--step, 0) * var(--delay-step, 0)) * 1s);
}

Wenn wir auf unser Paket zurückblicken, können wir dies weiterverfolgen und einen „Schritt“ auf alle Elemente anwenden, die in Frage kommen transform. Es ist ziemlich ausführlich, aber es macht seinen Job. Alternativ können Sie diese Werte in das Markup einfügen.

.package__side--extra > .package__flap--bottom {
  --step: 4;
}
.package__side--tabbed > .package__flap--bottom {
  --step: 5;
}
.package__side--main > .package__flap--bottom {
  --step: 6;
}
.package__side--flipped > .package__flap--bottom {
  --step: 7;
}
.package__side--extra > .package__flap--top {
  --step: 8;
}
.package__side--tabbed > .package__flap--top {
  --step: 9;
}
.package__side--main > .package__flap--top {
  --step: 10;
}
.package__side--flipped > .package__flap--top {
  --step: 11;
}

Aber es fühlt sich nicht sehr realistisch an.

Vielleicht sollten wir auch die Kiste umdrehen

Wenn ich die Schachtel im wirklichen Leben zusammenfalten würde, würde ich die Schachtel wahrscheinlich hochklappen, bevor ich die oberen Klappen einfalte. Wie könnten wir das tun? Nun, diejenigen mit einem wachsamen Auge haben das vielleicht bemerkt .package__wrapper Element. Wir werden dies verwenden, um das Paket zu verschieben. Dann drehen wir das Paket auf der x-Achse. Dadurch entsteht der Eindruck, das Paket auf die Seite zu drehen.

.package {
  transform-origin: 50% 100%;
  transform: rotateX(calc(var(--packaged, 0) * -90deg));
}
.package__wrapper {
  transform: translate(0, calc(var(--packaged, 0) * -100%));
}

Anpassen der --step Erklärungen gibt uns dementsprechend so etwas.

Aufklappen der Kiste

Wenn Sie zwischen gefaltetem und nicht gefaltetem Zustand wechseln, werden Sie feststellen, dass die Entfaltung nicht richtig aussieht. Die Entfaltungsreihenfolge sollte genau umgekehrt zur Faltungsreihenfolge sein. Wir könnten das umdrehen --step beyogen auf --packaged und die Anzahl der Schritte. Unser neuster Schritt ist 15. Wir können unsere aktualisieren transition dazu:

.scene *,
.scene *:after {
  --no-of-steps: 15;
  --step-delay: calc(var(--step, 1) - ((1 - var(--packaged, 0)) * (var(--step) - ((var(--no-of-steps) + 1) - var(--step)))));
  transition: transform calc(var(--speed, 0.2) * 1s) calc((var(--step-delay) * var(--delay, 0.2)) * 1s);
}

Das ist ziemlich viel calc umzukehren transition-delay. Aber es funktioniert! Wir müssen uns daran erinnern, das zu behalten --no-of-steps Wert aber aktuell!

Wir haben eine andere Möglichkeit. Wenn wir den Weg des „reinen CSS“ fortsetzen, werden wir schließlich die Checkbox-Hack zum Umschalten zwischen den Faltzuständen. Wir könnten zwei Sätze definierter „Schritte“ haben, wobei ein Satz aktiv ist, wenn unser Kontrollkästchen aktiviert ist. Es ist sicherlich eine ausführlichere Lösung. Aber es gibt uns mehr endliche Kontrolle.

/* Folding */
:checked ~ .scene .package__side--extra {
  --step: 1;
}
/* Unfolding */
.package__side--extra {
  --step: 15;
}

Dimensionierung und Zentrierung

Bevor wir die Verwendung von [dat.gui](https://github.com/dataarts/dat.gui) Spielen wir in unserer Demo mit der Größe unseres Pakets. Wir möchten überprüfen, ob unser Paket beim Falten und Wenden zentriert bleibt. In dieser Demo hat das Paket ein größeres --height und das .scene hat einen gestrichelten Rand.

Wir können genauso gut unsere transform um das Paket besser zu zentrieren, wenn wir schon dabei sind:

/* Considers package height by translating on z-axis */
.scene {
  transform: rotateX(calc(var(--rotate-x, -24) * 1deg)) rotateY(calc(var(--rotate-y, -32) * 1deg)) rotateX(90deg) translate3d(0, 0, calc(var(--height, 20) * -0.5vmin));
}
/* Considers package depth by sliding the depth before flipping */
.package__wrapper {
  transform: translate(0, calc((var(--packaged, 0) * var(--depth, 20)) * -1vmin));
}

Dies gibt uns eine zuverlässige Zentrierung in der Szene. Es kommt jedoch alles auf die Vorlieben an!

Hinzufügen im Checkbox-Hack

Jetzt lass uns dat.gui aus dem Weg und machen dieses „reine“ CSS. Dazu müssen wir eine Reihe von Steuerelementen in den HTML-Code einführen. Wir werden ein Kontrollkästchen zum Falten und Entfalten unseres Pakets verwenden. Dann verwenden wir a radio Schaltfläche, um eine Paketgröße auszuwählen.

<input id="package" type="checkbox"/>

<input id="one" type="radio" name="size"/>
<label class="size-label one" for="one">S</label>

<input id="two" type="radio" name="size" checked="checked"/>
<label class="size-label two" for="two">M</label>

<input id="three" type="radio" name="size"/>
<label class="size-label three" for="three">L</label>

<input id="four" type="radio" name="size"/>
<label class="size-label four" for="four">XL</label>

<label class="close" for="package">Close Package</label>
<label class="open" for="package">Open Package</label>

In der abschließenden Demo werden wir die Eingaben ausblenden und die Label-Elemente verwenden. Lassen Sie sie jedoch vorerst alle sichtbar. Der Trick besteht darin, den Geschwister-Kombinator (~) wenn bestimmte Kontrollen erhalten :checked. Wir können dann benutzerdefinierte Eigenschaftswerte für die .scene.

#package:checked ~ .scene {
  --packaged: 1;
}
#one:checked ~ .scene {
  --height: 10;
  --width: 20;
  --depth: 20;
}
#two:checked ~ .scene {
  --height: 20;
  --width: 20;
  --depth: 20;
}
#three:checked ~ .scene {
  --height: 20;
  --width: 30;
  --depth: 20;
}
#four:checked ~ .scene {
  --height: 30;
  --width: 20;
  --depth: 30;
}

Und hier ist die Demo, die funktioniert!

Endschliff

Jetzt sind wir an einem Ort, um die Dinge „hübsch“ aussehen zu lassen und einige zusätzliche Akzente zu setzen. Beginnen wir damit, alle Eingänge auszublenden.

input {
  position: fixed;
  top: 0;
  left: 0;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border-width: 0;
}

Wir können die Größenoptionen als abgerundete Schaltflächen gestalten:

.size-label {
  position: fixed;
  top: var(--top);
  right: 1rem;
  z-index: 3;
  font-family: sans-serif;
  font-weight: bold;
  color: #262626;
  height: 44px;
  width: 44px;
  display: grid;
  place-items: center;
  background: #fcfcfc;
  border-radius: 50%;
  cursor: pointer;
  border: 4px solid #8bb1b1;
  transform: translate(0, calc(var(--y, 0) * 1%)) scale(var(--scale, 1));
  transition: transform 0.1s;
}
.size-label:hover {
  --y: -5;
}
.size-label:active {
  --y: 2;
  --scale: 0.9;
}

Wir möchten überall tippen können, um zwischen dem Falten und Entfalten unseres Pakets zu wechseln. So unser .open und .close Etiketten nehmen den gesamten Bildschirm ein. Sie fragen sich, warum wir zwei Labels haben? Es ist ein kleiner Trick. Wenn wir a . verwenden transition-delay und das entsprechende Etikett hochskalieren, können wir beide Etiketten während des Paketübergangs ausblenden. Auf diese Weise bekämpfen wir das Antippen von Spam (auch wenn es einen Benutzer nicht daran hindert, die Leertaste auf einer Tastatur zu drücken).

.close,
.open {
  position: fixed;
  height: 100vh;
  width: 100vw;
  z-index: 2;
  transform: scale(var(--scale, 1)) translate3d(0, 0, 50vmin);
  transition: transform 0s var(--reveal-delay, calc(((var(--no-of-steps, 15) + 1) * var(--delay, 0.2)) * 1s));
}

#package:checked ~ .close,
.open {
  --scale: 0;
  --reveal-delay: 0s;
}
#package:checked ~ .open {
  --scale: 1;
  --reveal-delay: calc(((var(--no-of-steps, 15) + 1) * var(--delay, 0.2)) * 1s);
}

Sehen Sie sich diese Demo an, um zu sehen, wo wir hinzugefügt haben background-color zu beiden .open und .close. Keines der Labels ist während des Übergangs sichtbar.

Wir haben die volle Funktionalität! Aber unser Paket ist im Moment ein wenig enttäuschend. Lassen Sie uns zusätzliche Details hinzufügen, um die Dinge “box”-ähnlicher zu machen, mit Dingen wie Paketband und Verpackungsetiketten.

Kleine Details wie diese sind nur durch unsere Vorstellungskraft begrenzt! Wir können unsere gebrauchen --packaged benutzerdefinierte Eigenschaft, um alles zu beeinflussen. Zum Beispiel die .package__tape wechselt die scaleY verwandeln:

.package__tape {
  transform: translate3d(-50%, var(--offset-y), -2px) scaleX(var(--packaged, 0));
}

Denken Sie daran, dass wir unsere Schritte aktualisieren müssen, wenn wir eine neue Funktion hinzufügen, die sich auf die Sequenz auswirkt. Nicht nur die --step Werte, aber auch die --no-of-steps Wert.

Das ist es!

So erstellen Sie einen reinen CSS-3D-Paketumschalter. Werden Sie das in Ihre Website einfügen? Unwahrscheinlich! Aber es macht Spaß zu sehen, wie Sie diese Dinge mit CSS erreichen können. Benutzerdefinierte Eigenschaften sind so mächtig.

Warum nicht super festlich werden und CSS verschenken!

Bleib fantastisch! ʕ •ᴥ•ʔ

The source: https://nguyendiep.com
Category: Marketing

Thanks for Reading

Enjoyed this post? Share it with your networks.

Get more stuff

Subscribe to our mailing list and get interesting stuff and updates to your email inbox.

Thank you for subscribing.

Something went wrong.

Leave a Feedback!