htmlcss3dperspective

CSS perspective percentages and improper scaling


I'm trying to make a view of the inside of a room from a first person perspective using CSS and HTML. It's easy enough using the 3D transforms and the perspective attribute — the tutorials on the Internet explain it well, and I grasped it quite quickly.

However, for my purposes, I want the room to scale along with its parent container or the viewport, so it needs to be responsive. For this, I have started using percentages and ommiting the Z transform, which has gone quite well.

This works almost perfectly — the back, left, and right face stay in their place and retain their shape no matter the aspect ratio or size. However, the top and bottom faces seem to only properly display if the aspect ratio is square or portrait. Otherwise, the top and bottom faces are too short, and you can see the white background.

:root {
    
}

body {
    margin: 0 !important;
    overflow: hidden !important;
    padding: 0 !important;
}

div#main {
    height: 100vh;
    width: 100vw;
}

/* Walls with perspective */

div#scene-3d {
    height: 100%;
    perspective: 600px;
    width: 100%;
}

div#room {
    height: 100%;
    position: relative;
    transform-style: preserve-3d;
    width: 100%;
}

div.room-face {
/*  backface-visibility: hidden; */
    height: 100%;
    position: absolute;
    width: 100%;
}

.room-face#face-back {
    background: #333;
    transform: rotateY(90deg) translateX(100%) rotateY(-90deg);
}

.room-face#face-left {
    background: #ddd;
    transform: translateX(-50%) rotateY(90deg) translateX(50%);
}

.room-face#face-right {
    background: #bbb;
    transform: translateX(50%) rotateY(-90deg) translateX(-50%);
}

.room-face#face-top {
    background: #666;
    transform: translateY(-50%) rotateX(-90deg) translateY(50%);
}

.room-face#face-bottom {
    background: #999;
    transform: translateY(50%) rotateX(90deg) translateY(-50%);
}


/***/
<!DOCTYPE html>
<html>
    <head lang="en">
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>Merlin le Roi</title>
        <link rel="stylesheet" href="index.css">
    </head>

    <body>
        <div id="main">
            <div id="scene-3d">
                <div id="room">
                    <div class="room-face" id="face-back">Back</div>
                    <div class="room-face" id="face-right">Right</div>
                    <div class="room-face" id="face-left">Left</div>
                    <div class="room-face" id="face-top">Top</div>
                    <div class="room-face" id="face-bottom">Bottom</div>
                </div>
            </div>
        </div>
    </body>
</html>

What am I missing here? Why do the side faces scale and adjust properly, while the top and bottom ones seem to short?

I tried fiddling around with the transforms to no avail. It seems the only fix for now is to set the height of the top and bottom face to something absurd, like 2000%, but it doesn't seem like a good fix.


Solution

  • You can apply `transform-origin' to the ceiling and floor (this can also be applied to walls), and you also need to give them the proper height. With your permission, I fixed your code a bit:

    body {
      margin: 0;
      overflow: hidden;
      padding: 0;
    }
    
    #main {
      height: 100vh;
      width: 100vw;
    }
    
    /* Walls with perspective */
    
    #scene-3d {
      height: 100%;
      perspective: 600px;
      width: 100%;
    }
    
    #room {
      height: 100%;
      position: relative;
      transform-style: preserve-3d;
      width: 100%;
    }
    
    .room-face {
      height: 100%;
      position: absolute;
      width: 100%;
      /* labels */
      display: grid;
      place-items: center;
      font-size: 136px;
      font-family: monospace;
      /* debugging */
      border: solid 10px red;
      box-sizing: border-box;
    }
    
    #face-back {
      background: rgba(0, 0, 0, .2);
      transform: rotateY(90deg) translateX(100%) rotateY(-90deg);
    }
    
    #face-left {
      background: #ddd;
      transform-origin: left;
      transform: rotateY(90deg);
    }
    
    #face-right {
      background: #bbb;
      transform-origin: right;
      transform: rotateY(-90deg);
      right: 0;
    }
    
    #face-top {
      background: blue;
      transform: rotateX(-90deg);
      transform-origin: top;
      height: 100vw;
    }
    
    #face-bottom {
      background: yellow;
      transform: rotateX(90deg);
      transform-origin: bottom;
      height: 100vw;
      bottom: 0;
    }
    <div id="main">
      <div id="scene-3d">
        <div id="room">
          <div class="room-face" id="face-back">Back</div>
          <div class="room-face" id="face-right">Right</div>
          <div class="room-face" id="face-left">Left</div>
          <div class="room-face" id="face-top">Top</div>
          <div class="room-face" id="face-bottom">Bottom</div>
        </div>
      </div>
    </div>