0

I'm learning 3D transforms, playing around with building some interactive 3D models with CSS. Here's a codepen outlining a basic example. In the "sibling" layout, the result is as expected. In the "nested" layout, the Z-transforms don't "stack" as I would've thought: https://codepen.io/jconnorbuilds/pen/wBwwqqb

A (slightly) more complicated example, HTML only:

I started with a simple cube, with each of the faces laid out as siblings.

 <div class="cube">
  <div class="front"></div>
  <div class="back"></div>
  <div class="top"></div>
  <div class="bottom"></div>
  <div class="left"></div>
  <div class="right"></div>
</div>

The problem came up when I tried building on top of the cube. What I tried: I initially spent a lot of time trying to nest elements. What I expected: Elements would stack on top of each other in 3D space. What happened: Each nested element would seem to take the correct X and Y rotations from the parent, but would stay flat in the Z direction, relative to the parent.

My HTML would look something like this:

<div class="scene">
  <div class="cube__front"></div>
  <div class="cube__back"></div>
  <div class="cube__top">
    <div class="smaller-cube__front"></div>
    <div class="smaller-cube__back"></div>
    <!-- etc. etc. -->
  </div>
  <div class="cube__bottom"></div>
  <div class="cube__left"></div>
  <div class="cube__right"></div>
</div>

I was able to get the effect I was looking for by organizing all of the elements as siblings to each other, but is there any way to do it with the nested approach I tried initially? Example of a working structure:

<div class="scene">
  <div class="cube__front"></div>
  <div class="cube__back"></div>
  <div class="cube__top"></div>
  <div class="cube__bottom"></div>
  <div class="cube__left"></div>
  <div class="cube__right"></div>

  <!-- smaller cube to go on top of the main cube -->
  <div class="smaller-cube__front"></div>
  <div class="smaller-cube__back"></div>
  <!-- more smaller-cube faces -->
</div>

1 Answer 1

0

That's what transform-style: preserve-3d is for - it allows 3D transformed elements to have 3D transformed children. You don't need it on the scene, but you need it on 3D-transformed elements that you want to have 3D-transformed children. The problem is that setting properties like opacity break the 3D effect - you can only set them on elements with no children. Try removing opacity: .8 from .scene * (you can include the alpha in the background, like background: rgba(from orange r g b/ .8)) and you'll see the correct 3D effect.

transform-style: preserve-3d creates a 3D rendering context, so that elements inside are arranged in the 3D order set by their 3D transforms and not in DOM order. Which is why you should put all your cube faces inside a cube element, not directly inside the scene and also set this property on the cube itself, not just on its faces. A basic cube should start out like this:

.scene, [class*='cube'] { display: grid }

.scene { perspective: 50em }

[class*='cube'] {
  grid-area: 1/ 1; /* stack all cube elements */
  place-self: center /* in the middle along both axes */
}

.cube {
  --r: 5em; /* cube inradius (of sphere tightly fit inside) */
  transform-style: preserve-3d;
  animation: r 8s linear infinite
}

@keyframes r { to { rotate: y 1turn } }

.cube__face {
  padding: var(--r); /* give faces non-zero size */
  transform: 
    /* rotate by 90deg around an axis given by vector --v */
    rotate3d(var(--v, 0, 0, 1), 90deg) 
    /* change direction face is facing based on parity --p */
    scalez(calc(2*var(--p, 0) - 1)) 
    translatez(var(--r));
  box-shadow: inset 0 0 2px 2px; /* make something visible */
  
  /* change parity flag for even faces */
  &:nth-child(2n) { --p: 1 }
  
  /* change --v to have rotation around y (2nd) axis for faces >= 3 */
  &:nth-child(n + 3) { --v: 0, 1, 0 }
  /* override it for faces >= 5 to have rotation around x (1st) axis */
  &:nth-child(n + 5) { --v: 1, 0, 0 }
}

/* prettifying & layout */
html, body { display: grid }
html { min-height: 100% }
<div class='scene'>
  <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>
</div>

You probably want something like this, I'm guessing?

Conceptually, the way you're approaching it doesn't make much sense. A face is a 2D shape. 2D shapes can be put together to form 3D shapes, but you cannot put 3D shapes inside 2D ones. You could still structure it that way if you just want to write less code.

A structure that would make more sense would involve having 7 separate cubes (a middle one and 6 outer ones placed on faces) or at least directions inside the cube - imagine them as vectors pointing out of each cube face. Along each of these, you could have a face of the middle cube, as well as a cube placed right outside that face.

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.