Erstellung des DigitalOcean-Logos in 3D mit CSS

Avatar of Jhey Tompkins
Jhey Tompkins on

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

Hallo zusammen! Es sei denn, Sie haben unter einem Stein gelebt (und vielleicht sogar dann), haben Sie zweifellos die Nachricht gehört, dass CSS-Tricks von DigitalOcean übernommen wurde. Herzlichen Glückwunsch an alle! 🥳

Als kleines Hurra zur Erinnerung an den Anlass wollte ich das DigitalOcean-Logo in CSS erstellen. Das habe ich getan, aber dann ging ich noch einen Schritt weiter mit etwas 3D und Parallax. Das macht auch einen ziemlich guten Artikel, denn die Art und Weise, wie ich das Logo erstellt habe, verwendet verschiedene Teile aus früheren Artikeln, die ich geschrieben habe. Diese coole kleine Demo bringt viele dieser Konzepte zusammen.

Also, tauchen wir gleich ein!

Wir werden das DigitalOcean-Logo "nachzeichnen", indem wir eine SVG-Version davon von simpleicons.org beziehen.

<svg role="img" viewbox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
  <title>DigitalOcean</title>
  <path d="M12.04 0C5.408-.02.005 5.37.005 11.992h4.638c0-4.923 4.882-8.731 10.064-6.855a6.95 6.95 0 014.147 4.148c1.889 5.177-1.924 10.055-6.84 10.064v-4.61H7.391v4.623h4.61V24c7.86 0 13.967-7.588 11.397-15.83-1.115-3.59-3.985-6.446-7.575-7.575A12.8 12.8 0 0012.039 0zM7.39 19.362H3.828v3.564H7.39zm-3.563 0v-2.978H.85v2.978z"></path>
</svg>

Da wir bedenken, dass wir dies in 3D machen, können wir unser SVG in ein .scene-Element einpacken. Dann können wir die Nachzeichnungstechnik aus meinem Artikel "Advice for Advanced CSS Illustrations" verwenden. Wir verwenden Pug, so dass wir seine Mixins nutzen und die Menge an Markup reduzieren können, die wir für den 3D-Teil schreiben müssen.

- const SIZE = 40
.scene
  svg(role='img' viewbox='0 0 24 24' xmlns='http://www.w3.org/2000/svg')
    title DigitalOcean
    path(d='M12.04 0C5.408-.02.005 5.37.005 11.992h4.638c0-4.923 4.882-8.731 10.064-6.855a6.95 6.95 0 014.147 4.148c1.889 5.177-1.924 10.055-6.84 10.064v-4.61H7.391v4.623h4.61V24c7.86 0 13.967-7.588 11.397-15.83-1.115-3.59-3.985-6.446-7.575-7.575A12.8 12.8 0 0012.039 0zM7.39 19.362H3.828v3.564H7.39zm-3.563 0v-2.978H.85v2.978z')
  .logo(style=`--size: ${SIZE}`)
    .logo__arc.logo__arc--inner
    .logo__arc.logo__arc--outer
    .logo__square.logo__square--one
    .logo__square.logo__square--two
    .logo__square.logo__square--three

Die Idee ist, diese Elemente so zu stylen, dass sie unser Logo überlappen. Wir müssen den "Arc"-Teil des Logos nicht erstellen, da wir vorausschauend denken, denn wir werden dieses Logo in 3D erstellen und den Arc mit zwei Zylinderformen erzeugen können. Das bedeutet, dass wir vorerst nur die enthaltenden Elemente für jeden Zylinder, den inneren Arc und den äußeren Arc benötigen.

Schauen Sie sich diese Demo an, die die verschiedenen Teile des DigitalOcean-Logos darstellt. Wenn Sie "Explode" und Hover-Elemente umschalten, können Sie sehen, woraus das Logo besteht.

Wenn wir ein flaches DigitalOcean-Logo wollten, könnten wir eine CSS-Maske mit einem konischen Gradienten verwenden. Dann bräuchten wir nur ein "Arc"-Element mit einem soliden Rand.

.logo__arc--outer {
  border: calc(var(--size) * 0.1925vmin) solid #006aff;
  mask: conic-gradient(transparent 0deg 90deg, #000 90deg);
  transform: translate(-50%, -50%) rotate(180deg);
}

Das würde uns das Logo geben. Der "Reveal"-Übergang einer clip-path, die das nachgezeichnete SVG-Bild darunter anzeigt.

Schauen Sie sich meinen Artikel "Advice for Complex CSS Illustrations" an, um Tipps zur Arbeit mit erweiterten Illustrationen in CSS zu erhalten.

Extrusion für 3D

Wir haben den Bauplan für unser DigitalOcean-Logo, also ist es Zeit, es in 3D zu machen. Warum haben wir nicht von Anfang an 3D-Blöcke erstellt? Das Erstellen enthaltender Elemente erleichtert die 3D-Erstellung durch Extrusion.

Wir haben die Erstellung von 3D-Szenen in CSS in meinem Artikel "Learning to Think in Cubes Instead of Boxes" behandelt. Wir werden einige dieser Techniken für das verwenden, was wir hier machen. Beginnen wir mit den Quadraten im Logo. Jedes Quadrat ist ein Quader. Und mit Pug werden wir eine cuboid-Mixin erstellen und verwenden, um sie alle zu generieren.

mixin cuboid()
  .cuboid(class!=attributes.class)
    if block
      block
    - let s = 0
    while s < 6
      .cuboid__side
      - s++

Dann können wir dies in unserem Markup verwenden

.scene
  .logo(style=`--size: ${SIZE}`)
    .logo__arc.logo__arc--inner
    .logo__arc.logo__arc--outer
    .logo__square.logo__square--one
      +cuboid().square-cuboid.square-cuboid--one
    .logo__square.logo__square--two
      +cuboid().square-cuboid.square-cuboid--two
    .logo__square.logo__square--three
      +cuboid().square-cuboid.square-cuboid--three

Als nächstes benötigen wir die Stile, um unsere Quader anzuzeigen. Beachten Sie, dass Quader sechs Seiten haben, daher stylen wir diese mit dem nth-of-type()-Pseudoselektor, während wir die vmin-Längeneinheit nutzen, um die Dinge reaktionsfähig zu halten.

.cuboid {
  width: 100%;
  height: 100%;
  position: relative;
}
.cuboid__side {
  filter: brightness(var(--b, 1));
  position: absolute;
}
.cuboid__side:nth-of-type(1) {
  --b: 1.1;
  height: calc(var(--depth, 20) * 1vmin);
  width: 100%;
  top: 0;
  transform: translate(0, -50%) rotateX(90deg);
}
.cuboid__side:nth-of-type(2) {
  --b: 0.9;
  height: 100%;
  width: calc(var(--depth, 20) * 1vmin);
  top: 50%;
  right: 0;
  transform: translate(50%, -50%) rotateY(90deg);
}
.cuboid__side:nth-of-type(3) {
  --b: 0.5;
  width: 100%;
  height: calc(var(--depth, 20) * 1vmin);
  bottom: 0;
  transform: translate(0%, 50%) rotateX(90deg);
}
.cuboid__side:nth-of-type(4) {
  --b: 1;
  height: 100%;
  width: calc(var(--depth, 20) * 1vmin);
  left: 0;
  top: 50%;
  transform: translate(-50%, -50%) rotateY(90deg);
}
.cuboid__side:nth-of-type(5) {
  --b: 0.8;
  height: 100%;
  width: 100%;
  transform: translate3d(0, 0, calc(var(--depth, 20) * 0.5vmin));
  top: 0;
  left: 0;
}
.cuboid__side:nth-of-type(6) {
  --b: 1.2;
  height: 100%;
  width: 100%;
  transform: translate3d(0, 0, calc(var(--depth, 20) * -0.5vmin)) rotateY(180deg);
  top: 0;
  left: 0;
}

Wir gehen hier anders vor als in früheren Artikeln. Anstatt einem Quader Höhe, Breite und Tiefe zuzuweisen, beschäftigen wir uns nur mit seiner Tiefe. Und anstatt zu versuchen, jede Seite zu färben, können wir filter: brightness nutzen, um das für uns zu erledigen.

Wenn Sie Quader oder andere 3D-Elemente als Kind einer Seite unter Verwendung von filter haben möchten, müssen Sie möglicherweise Dinge verschieben. Eine gefilterte Seite wird alle 3D-Kinder abflachen.

Das DigitalOcean-Logo hat drei Quader, daher haben wir eine Klasse für jeden und stylen sie so

.square-cuboid .cuboid__side {
  background: hsl(var(--hue), 100%, 50%);
}
.square-cuboid--one {
  /* 0.1925? It's a percentage of the --size for that square */
  --depth: calc((var(--size) * 0.1925) * var(--depth-multiplier));
}
.square-cuboid--two {
  --depth: calc((var(--size) * 0.1475) * var(--depth-multiplier));
}
.square-cuboid--three {
  --depth: calc((var(--size) * 0.125) * var(--depth-multiplier));
}

...was uns ungefähr das hier ergibt

Sie können mit dem Tiefenschieberegler experimentieren, um die Quader nach Wunsch zu extrudieren! Für unsere Demo haben wir uns entschieden, die Quader zu echten Würfeln mit gleicher Höhe, Breite und Tiefe zu machen. Die Tiefe des Bogens entspricht dem größten Quader.

Nun zu den Zylindern. Die Idee ist, zwei Enden zu erstellen, die border-radius: 50% verwenden. Dann können wir viele Elemente als Seiten des Zylinders verwenden, um den Effekt zu erzeugen. Der Trick ist, alle Seiten zu positionieren.

Es gibt verschiedene Ansätze, wie wir die Zylinder in CSS erstellen können. Aber für mich, wenn das etwas ist, von dem ich erwarte, es oft zu verwenden, werde ich versuchen, es zukunftssicher zu machen. Das bedeutet, eine Mixin und einige Stile zu erstellen, die ich für andere Demos wiederverwenden kann. Und diese Stile sollten versuchen, Szenarien zu berücksichtigen, die ich kommen sehe. Für einen Zylinder gibt es einige Konfigurationen, die wir in Betracht ziehen könnten

  • radius
  • Seiten
  • wie viele dieser Seiten angezeigt werden
  • ob eine oder beide Enden des Zylinders angezeigt werden sollen

Wenn wir das zusammenfügen, können wir eine Pug-Mixin erstellen, die diese Anforderungen erfüllt

mixin cylinder(radius = 10, sides = 10, cut = [5, 10], top = true, bottom = true)
  - const innerAngle = (((sides - 2) * 180) / sides) * 0.5
  - const cosAngle = Math.cos(innerAngle * (Math.PI / 180))
  - const side =  2 * radius * Math.cos(innerAngle * (Math.PI / 180))
  //- Use the cut to determine how many sides get rendered and from what point
  .cylinder(style=`--side: ${side}; --sides: ${sides}; --radius: ${radius};` class!=attributes.class)
    if top
      .cylinder__end.cylinder__segment.cylinder__end--top
    if bottom
      .cylinder__end.cylinder__segment.cylinder__end--bottom
    - const [start, end] = cut
    - let i = start
    while i < end
      .cylinder__side.cylinder__segment(style=`--index: ${i};`)
      - i++

Sehen Sie, wie //- dem Kommentar im Code vorangestellt ist? Das sagt Pug, dass der Kommentar ignoriert und aus dem kompilierten HTML-Markup weggelassen werden soll.

Warum müssen wir den Radius in den Zylinder übergeben? Nun, leider können wir mit der CSS calc() noch keine Trigonometrie handhaben (aber sie kommt). Und wir müssen Dinge wie die Breite der Zylinderseiten und wie weit sie vom Zentrum entfernt projiziert werden sollen, berechnen. Das Tolle ist, dass wir eine gute Möglichkeit haben, diese Informationen über Inline-Custom-Properties an unsere Stile zu übergeben.

.cylinder(
  style=`
    --side: ${side};
    --sides: ${sides};
    --radius: ${radius};`
  class!=attributes.class
)

Ein beispielhafter Anwendungsfall für unsere Mixin wäre wie folgt

+cylinder(20, 30, [10, 30])

Dies würde einen Zylinder mit einem Radius von 20, 30 Seiten erstellen, wobei nur die Seiten 10 bis 30 gerendert werden.

Dann brauchen wir etwas Styling. Das Stylen der Zylinder für das DigitalOcean-Logo ist zum Glück ziemlich einfach

.cylinder {
  --bg: hsl(var(--hue), 100%, 50%);
  background: rgba(255,43,0,0.5);
  height: 100%;
  width: 100%;
  position: relative;
}
.cylinder__segment {
  filter: brightness(var(--b, 1));
  background: var(--bg, #e61919);
  position: absolute;
  top: 50%;
  left: 50%;
}
.cylinder__end {
  --b: 1.2;
  --end-coefficient: 0.5;
  height: 100%;
  width: 100%;
  border-radius: 50%;
  transform: translate3d(-50%, -50%, calc((var(--depth, 0) * var(--end-coefficient)) * 1vmin));
}
.cylinder__end--bottom {
  --b: 0.8;
  --end-coefficient: -0.5;
}
.cylinder__side {
  --b: 0.9;
  height: calc(var(--depth, 30) * 1vmin);
  width: calc(var(--side) * 1vmin);
  transform: translate(-50%, -50%) rotateX(90deg) rotateY(calc((var(--index, 0) * 360 / var(--sides)) * 1deg)) translate3d(50%, 0, calc(var(--radius) * 1vmin));
}

Die Idee ist, dass wir alle Seiten des Zylinders erstellen und sie in der Mitte des Zylinders platzieren. Dann drehen wir sie auf der Y-Achse und projizieren sie ungefähr um die Distanz des Radius nach außen.

Es ist nicht nötig, die Enden des Zylinders im inneren Teil zu zeigen, da sie bereits verdeckt sind. Aber wir müssen sie für den äußeren Teil zeigen. Unsere Zwei-Zylinder-Mixin-Verwendung sieht dann so aus

.logo(style=`--size: ${SIZE}`)
  .logo__arc.logo__arc--inner
    +cylinder((SIZE * 0.61) * 0.5, 80, [0, 60], false, false).cylinder-arc.cylinder-arc--inner
  .logo__arc.logo__arc--outer
    +cylinder((SIZE * 1) * 0.5, 100, [0, 75], true, true).cylinder-arc.cylinder-arc--outer

Wir kennen den Radius aus dem Durchmesser, den wir beim Nachzeichnen des Logos zuvor verwendet haben. Außerdem können wir die äußeren Zylinderenden verwenden, um die Flächen des DigitalOcean-Logos zu erstellen. Eine Kombination aus border-width und clip-path ist hier hilfreich.

.cylinder-arc--outer .cylinder__end--top,
.cylinder-arc--outer .cylinder__end--bottom {
  /* Based on the percentage of the size needed to cap the arc */
  border-width: calc(var(--size) * 0.1975vmin);
  border-style: solid;
  border-color: hsl(var(--hue), 100%, 50%);
  --clip: polygon(50% 0, 50% 50%, 0 50%, 0 100%, 100% 100%, 100% 0);
  clip-path: var(--clip);
}

Wir sind ziemlich nah dran, wo wir sein wollen!

Es fehlt noch eine Sache: das Kappen des Bogens. Wir müssen einige Enden für den Bogen erstellen, was zwei Elemente erfordert, die wir auf der X- oder Y-Achse positionieren und drehen können

.scene
  .logo(style=`--size: ${SIZE}`)
    .logo__arc.logo__arc--inner
      +cylinder((SIZE * 0.61) * 0.5, 80, [0, 60], false, false).cylinder-arc.cylinder-arc--inner
    .logo__arc.logo__arc--outer
      +cylinder((SIZE * 1) * 0.5, 100, [0, 75], true, true).cylinder-arc.cylinder-arc--outer
    .logo__square.logo__square--one
      +cuboid().square-cuboid.square-cuboid--one
    .logo__square.logo__square--two
      +cuboid().square-cuboid.square-cuboid--two
    .logo__square.logo__square--three
      +cuboid().square-cuboid.square-cuboid--three
    .logo__cap.logo__cap--top
    .logo__cap.logo__cap--bottom

Die Kappenenden des Bogens nehmen Höhe und Breite basierend auf dem Wert von border-width des Endes sowie der Tiefe des Bogens an.

.logo__cap {
  --hue: 10;
  position: absolute;
  height: calc(var(--size) * 0.1925vmin);
  width: calc(var(--size) * 0.1975vmin);
  background: hsl(var(--hue), 100%, 50%);
}
.logo__cap--top {
  top: 50%;
  left: 0;
  transform: translate(0, -50%) rotateX(90deg);
}
.logo__cap--bottom {
  bottom: 0;
  right: 50%;
  transform: translate(50%, 0) rotateY(90deg);
  height: calc(var(--size) * 0.1975vmin);
  width: calc(var(--size) * 0.1925vmin);
}

Wir haben den Bogen gekappt!

Wenn wir alles zusammenfügen, haben wir unser DigitalOcean-Logo. Diese Demo ermöglicht es Ihnen, es in verschiedene Richtungen zu drehen.

Aber es gibt noch einen weiteren Trick, den wir auf Lager haben!

Wir haben unser 3D-DigitalOcean-Logo, aber es wäre schön, wenn es auf irgendeine Weise interaktiv wäre. Im November 2021 haben wir behandelt, wie man einen Parallax-Effekt mit CSS-Custom-Properties erstellt. Lassen Sie uns diese Technik hier verwenden. Die Idee ist, dass sich das Logo dreht und bewegt, indem es dem Mauszeiger des Benutzers folgt.

Wir brauchen einen Schuss JavaScript, damit wir die Custom-Properties aktualisieren können, die wir für einen Koeffizienten benötigen, der die Bewegung des Logos entlang der X- und Y-Achsen in CSS festlegt. Diese Koeffizienten werden aus der Zeigerposition eines Benutzers berechnet. Ich verwende oft GreenSock, damit ich gsap.utils.mapRange verwenden kann. Aber hier ist eine Vanilla-JavaScript-Version davon, die mapRange implementiert

const mapRange = (inputLower, inputUpper, outputLower, outputUpper) => {
  const INPUT_RANGE = inputUpper - inputLower
  const OUTPUT_RANGE = outputUpper - outputLower
  return value => outputLower + (((value - inputLower) / INPUT_RANGE) * OUTPUT_RANGE || 0)
}

const BOUNDS = 100      
const update = ({ x, y }) => {
  const POS_X = mapRange(0, window.innerWidth, -BOUNDS, BOUNDS)(x)
  const POS_Y = mapRange(0, window.innerHeight, -BOUNDS, BOUNDS)(y)
  document.body.style.setProperty('--coefficient-x', POS_X)
  document.body.style.setProperty('--coefficient-y', POS_Y)
}

document.addEventListener('pointermove', update)

Die Magie geschieht in der CSS-Welt. Dies ist einer der Hauptvorteile der Verwendung von Custom-Properties auf diese Weise. JavaScript sagt CSS, was bei der Interaktion passiert. Aber es kümmert sich nicht darum, was CSS damit macht. Das ist eine coole Entkopplung. Ich verwende diesen JavaScript-Snippet aus genau diesem Grund in vielen meiner Demos. Wir können unterschiedliche Erlebnisse schaffen, indem wir einfach das CSS aktualisieren.

Wie machen wir das? Verwenden Sie calc() und Custom-Properties, die direkt für das .scene-Element gescope't sind. Betrachten Sie diese aktualisierten Stile für .scene

.scene {
  --rotation-y: 75deg;
  --rotation-x: -14deg;
  transform: translate3d(0, 0, 100vmin)
    rotateX(-16deg)
    rotateY(28deg)
    rotateX(calc(var(--coefficient-y, 0) * var(--rotation-x, 0deg)))
    rotateY(calc(var(--coefficient-x, 0) * var(--rotation-y, 0deg)));
}

Das lässt die Szene basierend auf der Mausbewegung des Benutzers auf der X- und Y-Achse rotieren. Aber wir können dieses Verhalten anpassen, indem wir die Werte für --rotation-x und --rotation-y ändern.

Jeder Quader bewegt sich auf seine eigene Weise. Sie können sich entweder auf der X-, Y- oder Z-Achse bewegen. Wir müssen jedoch nur ein transform definieren. Dann können wir gescopete Custom-Properties verwenden, um den Rest zu erledigen.

.logo__square {
  transform: translate3d(
    calc(min(0, var(--coefficient-x, 0) * var(--offset-x, 0)) * 1%),
    calc((var(--coefficient-y) * var(--offset-y, 0)) * 1%),
    calc((var(--coefficient-x) * var(--offset-z, 0)) * 1vmin)
  );
}
.logo__square--one {
  --offset-x: 50;
  --offset-y: 10;
  --offset-z: -2;
}
.logo__square--two {
  --offset-x: -35;
  --offset-y: -20;
  --offset-z: 4;
}
.logo__square--three {
  --offset-x: 25;
  --offset-y: 30;
  --offset-z: -6;
}

Das ergibt Ihnen dann so etwas wie dieses

Und wir können diese nach Belieben anpassen, bis wir etwas haben, mit dem wir zufrieden sind!

Hinzufügen einer Intro-Animation zur Mischung

Okay, ich habe ein bisschen geflunkert und habe eine letzte (ich verspreche es!) Möglichkeit, unsere Arbeit zu verbessern. Was wäre, wenn wir eine Art Intro-Animation hätten? Wie wäre es mit einer Welle oder etwas, das darüber fegt und das Logo enthüllt?

Das könnten wir mit den Pseudo-Elementen des body-Elements machen

:root {
  --hue: 215;
  --initial-delay: 1;
  --wave-speed: 2;
}

body:after,
body:before {
  content: '';
  position: absolute;
  height: 100vh;
  width: 100vw;
  background: hsl(var(--hue), 100%, calc(var(--lightness, 50) * 1%));
  transform: translate(100%, 0);
  animation-name: wave;
  animation-duration: calc(var(--wave-speed) * 1s);
  animation-delay: calc(var(--initial-delay) * 1s);
  animation-timing-function: ease-in;
}
body:before {
  --lightness: 85;
  animation-timing-function: ease-out;
}
@keyframes wave {
  from {
    transform: translate(-100%, 0);
  }
}

Nun ist die Idee, dass das DigitalOcean-Logo verborgen bleibt, bis die Welle darüber fegt. Für diesen Effekt werden wir unsere 3D-Elemente von einer Opazität von 0 animieren. Und wir werden alle Seiten unserer 3D-Elemente von einer brightness von 1 animieren, um das Logo zu enthüllen. Da die Wellenfarbe mit der des Logos übereinstimmt, sehen wir kein Ausblenden. Außerdem bedeutet die Verwendung von animation-fill-mode: both, dass unsere Elemente die Stile unserer Keyframes in beide Richtungen erweitern.

Dies erfordert eine Art Animationszeitleiste. Und hier kommen Custom-Properties ins Spiel. Wir können die Dauer unserer Animationen nutzen, um die Verzögerungen anderer zu berechnen. Das haben wir in meinen Artikeln "How to Make a Pure CSS 3D Package Toggle" und "Animated Matryoshka Dolls in CSS" behandelt.

:root {
  --hue: 215;
  --initial-delay: 1;
  --wave-speed: 2;
  --fade-speed: 0.5;
  --filter-speed: 1;
}

.cylinder__segment,
.cuboid__side,
.logo__cap {
  animation-name: fade-in, filter-in;
  animation-duration: calc(var(--fade-speed) * 1s),
    calc(var(--filter-speed) * 1s);
  animation-delay: calc((var(--initial-delay) + var(--wave-speed)) * 0.75s),
    calc((var(--initial-delay) + var(--wave-speed)) * 1.15s);
  animation-fill-mode: both;
}

@keyframes filter-in {
  from {
    filter: brightness(1);
  }
}

@keyframes fade-in {
  from {
    opacity: 0;
  }
}

Wie bekommen wir das Timing richtig hin? Ein bisschen Herumspielen und die Nutzung des "Animationsinspektors" im Chrome DevTool helfen sehr. Versuchen Sie, die Timings in dieser Demo anzupassen

Sie werden feststellen, dass das Fade-Timing unnötig ist, wenn das Logo erscheinen soll, sobald die Welle vorübergezogen ist. In diesem Fall versuchen Sie, das Fade auf 0 zu setzen. Und insbesondere experimentieren Sie mit den filter- und fade-Koeffizienten. Sie beziehen sich auf die 0.75s und 1.15s aus dem obigen Code. Es lohnt sich, Dinge anzupassen und im Chrome Animation Inspector zu spielen, um zu sehen, wie sich die Dinge zeitlich abstimmen.

Das ist alles!

Wenn wir alles zusammenfügen, erhalten wir dieses nette Intro für unser 3D-DigitalOcean-Logo!

Und natürlich ist dies nur eine Möglichkeit, das DigitalOcean-Logo in 3D mit CSS zu erstellen. Wenn Sie andere Möglichkeiten sehen oder etwas, das weiter optimiert werden kann, hinterlassen Sie einen Link zu Ihrer Demo in den Kommentaren!

Herzlichen Glückwunsch noch einmal an das CSS-Tricks-Team und DigitalOcean zu ihrer neuen Partnerschaft. Ich bin gespannt, wohin die Dinge mit der Übernahme gehen werden. Eines ist sicher: CSS-Tricks wird die Community weiterhin inspirieren und fantastische Inhalte produzieren. 😎