Css attributes

Create a 3D world in pure CSS

Graphic showing 3D cubes with CSS code

Recently I got interested in 3D CSS and wanted to know more about it. i was inspired by Amit Sheen CodePen and I decided to create my own 3D world. It was a great learning experience as I was able to learn about all the properties and have a clear goal in mind which prompted me to find ways to build it.

In this article, we’ll show you how to create a navigable 3D world in first person using pure CSS! Absolutely no JavaScript! You will learn how to place and move objects in 3D space and how to create rudimentary interactivity using the keyboard and mouse.

You might be wondering why just CSS? Simply because limitations are fun and can help you find ways to do things you didn’t expect and push the boundaries of what’s possible. It also helps you understand the topic better. Here is what we will be working towards:

First, we need to create our walls and floor in order to create our 3D room. We are also going to create a scene to contain our entire 3D world:


   

   

   

   

Next, we need to add some styles for our scene:

We must use transformation style to ensure that all nodes inside the scene are correctly transformed in 3D space. Next, we need to add styles to the body:

body {
   background-color: #000;
   min-height: 100vh;
   display: flex;
   justify-content: center;
   align-items: center;
   font-size: 40px;
   perspective: 10em;
   perspective-origin: 50% calc(50% - 2em);
   overflow: hidden;
}

We want our scene to be centered horizontally and vertically. We are going to manipulate the zoom out of our scene using the perspective property. To change where the viewer is looking, we use the perspective-origin property. In this case, we want the viewer to view the scene 50% horizontally and 50% vertically minus 2 em. We use ems as a convenient way to store the dimensions of our objects as they act like variables, and we can adjust the overall size of our scene by just changing the font size.

On to the exciting stuff – we’re going to create our front wall now. We will be using background gradients using the following tool – but feel free to use images instead:

.frontWall {
   position: absolute;
   background-color: #222297;
   background-image: repeating-radial-gradient( circle at 0 0, transparent 0, #222297 1em ), repeating-linear-gradient( #03076055, #030760 );
   width: 20em;
   height: 20em;
   top: -16em;
   left:-10em;
   transform: translateZ(-10em);
  }

Screenshot showing the front wall of the 3D room

We need to make the wall position absolute in order to be able to place it correctly in the scene, all of our walls will be 20×20 em. The transform property allows us to move objects in 3D space, in this case we are moving the z axis – which means the object will move forward, away from the viewer.

As all the walls will have the same background, we can reuse this rule for the other walls. So replace the .frontWall class with .frontWall, .leftWall, .rightwall like this:

.frontWall, .leftWall, .rightwall { /*previous code goes here*/ }

Now let’s make the left wall, we need to place it again so that it fits perfectly into the scene. This time instead of using the z axis to move it forward, we rotate it around the y axis instead, so we override the transform property and rotate it:

.leftWall {
   left:-20em;
   transform: rotateY(-90deg);
}

Time for the right wall, here we rotate along the y axis in the other direction and set the left property to 0:

.rightWall {
   transform: rotateY(90deg);
   left:0;
 }

Now the ground we are going to use a different gradient, but feel free to choose yours or another graphic.

.floor {
   background-color: #000;
   background-image:  linear-gradient(135deg, #ffffff 25%, transparent 25%), linear-gradient(225deg, #ffffff 25%, transparent 25%), linear-gradient(45deg, #ffffff 25%, transparent 25%), linear-gradient(315deg, #ffffff 25%, #000 25%);
   background-position:  1em 0, 1em 0, 0 0, 0 0;
   background-size: 1em 1em;
   background-repeat: repeat;
   width: 20em;
   height: 20em;
   top: 1em;
   z-index: 0;
   position: absolute;
   transform:
     translate(-50%, -50%)
     rotateX(90deg)
     translateZ(-3em);
}

We give the floor the same dimensions as the walls, but this time we move it by -% 50 along the x and y axes, and rotate it 90 degrees along the x axis. Finally, we bring it closer to the viewer by moving the z axis -3em.

Great, let’s take a look at what this should look like:

Screenshot showing the walls and floor of the 3D room

As you can see we’re missing a cap – so we need to update our CSS rules:

From:

.frontWall, .leftWall, .rightWall { /*existing CSS code*/ }

AT:

.ceiling, .frontWall, .leftWall, .rightWall { /*existing CSS code*/ }

Then we need to add another rule for the ceiling and set the left and the top to zero. We rotate the ceiling along its x axis and move it away from the view using the Z translation. Use translate to move the x axis and y axis by -50%. We also gave the ceiling a darker shade than the walls with a background color and a background image.

.ceiling {
   background-color: #0a0a5c;
   background-image: repeating-radial-gradient( circle at 0 0, transparent 0, #222297 17px ), repeating-linear-gradient( #03076055, #030760 );
   top:0;
   left:0;
   transform:
     translate(-50%, -50%)
     rotateX(90deg)
     translateZ(15em);
}

Now to create a door. All you have to do here is use a clipping path and add it to the front wall. Using this technique, you can create doors and open and close them to enter different rooms:

.frontWall {
   clip-path: polygon(0% 0%,
     0% 100%,
     33% 100%,
     33% 39%,
     67% 39%,
     68% 100%,
     33% 100%,
     33% 100%,
     100% 100%,
     100% 0%);
 }

You can create your own clip paths using this fantastic online tool called Clippy. We need so many points to define a door because you have to create the inverse of a rectangle. Using the frame shape is a good place to start.

Screenshot showing the clip path of the Clippy tool

It’s going to get more fun now – our next job will be to create animations and interactions. To do this, we need to use CSS animations. In CSS you have both transitions and animations available. Transitions are useful when you want an element to gradually change its properties to another state and vice versa – such as a door that opens on mouseover or a checked state. Animations are most useful when you want to move an item without relying on a hover or checked states – and they can perform many different operations along the timeline.

Let’s start by defining some CSS variables. Place them at the top of the styles, as they are global variables:

:root {
   --turnSpeed: 40s;
   --speed: 300s;
}

–turnSpeed ​​controls the speed at which you move left and right and –speed controls the speed forwards and backwards. Next, we need to define the animations:

@keyframes sceneRotateLeft {
   to { transform: rotateY(-360deg); }
      }

  @keyframes sceneRotateRight {
   to { transform: rotateY(360deg); }
}

   @keyframes sceneMoveForward {
   to { transform: translateZ(1000em); }
}

@keyframes sceneMoveBack {
   to { transform: translateZ(-1000em); }
}

To define an animation, you must use the @keyframes keyword, followed by the name of the animation. The name of the animation has braces and inside we need to define what our animation does. In this case we are using the “to” selector to assign the transform property, “to” means what the ending keyframe should do – so all other keyframes will gradually manipulate the property up to the ending value .

We use rotateY to rotate the scene left and right providing positive or negative degrees. We need to give translateZ to move forward and backward a lot, because we want to be able to move forward a lot if the user holds the key down (or clicks the navigation arrow that we’re going to set).

Now we need to assign the animations. We need to update the scene class and add the animation property. The animation property allows you to define multiple animations and separate them with a comma. We use our variables that we previously set to control the speed and speed of the turn. The animation playback state property is very powerful; it allows you to pause all animations until they are ready to play and choose which one to play based on an interaction.

.scene {
   animation:
     sceneRotateLeft var(--turnSpeed) infinite linear,
     sceneRotateRight var(--turnSpeed) infinite linear,
     sceneMoveForward var(--speed) infinite linear,
     sceneMoveBack var(--speed) infinite linear;
   animation-play-state: paused, paused, paused, paused;
}

Next is navigation – we have to use anchor elements to allow us to navigate. It is important to place these elements in the same row (sibling) as the scene element. They therefore cannot be children of another element and must appear before the element in the Stage. This is because we want to play an animation based on the interaction with the anchor elements. Let’s create the anchors:






...

To do the interaction, we have to use the : target selector. The: target selector allows us to select an item based on the hash of the URL. For example, if you visit url.html # moveForward, it will target the item with the id “moveForward”. We also need to give the elements a class to make sure the correct animation is played, because the: target selector only allows you to target a specific id. Next, we need to change the state of the animation based on the focus state or the url:

:target.turnLeft ~ .scene, #turnLeft:focus ~ .scene {
   animation-play-state: running, paused, paused, paused;
 }

:target.turnRight ~ .scene, #turnRight:focus ~ .scene {
   animation-play-state: paused, running, paused, paused;
 }

:target.moveBack ~ .scene, #moveBack:focus ~ .scene {
   animation-play-state: paused, paused, paused, running;
 }

   :target.moveForward ~ .scene, #moveForward:focus ~ .scene {
   animation-play-state: paused, paused, running, paused;
 }

#moveForward, #turnLeft, #stop, #turnRight, #moveBack {
   position: absolute;
   left: -5000px;
   top:-5000px;
}

Note that ~ in the selector is the general sibling selector, and it will find any sibling of the selected item. This is why we had to place the controls before the stage and within a sibling. We’re also moving controls off-screen – which will prevent the browser from focusing on them. Access key attributes on anchor elements allow you to assign shortcuts. On a Mac, the combinations are CONTROL + ALT + W to go forward and SHIFT + ALT + W on Windows. The other keys are A to turn left and D to turn right, S to reverse and X to stop.

Finally, we need to add clickable controls. All you have to do is create more anchors that point to the correct target:


  

    

    
    

  

  

    
    
    
  

  

    

    
    

  

We use different arrows and a circle for the stop link. We finish by adding a few styles – and the Hack Safari is used to change the position of the arrows so that they work correctly on this browser.

/*Safari only hack*/
@supports (-webkit-backdrop-filter: blur(1px)) {
  #controls {
    top: 40%;
  }
}

.flex-grid {
   display: flex;
   justify-content: space-between;
   align-items: center;
   justify-content: center;
 }

.col {
   width: 32%;
   text-align: center;
}

#controls a:link, #controls a:visited {
    text-decoration: none;
    color:rgb(250, 255, 0);
    text-shadow: 2px 2px #000;
}

Screenshot of the final result

That’s all the CSS you need to create a basic 3D world in first person. We hope you enjoyed this tutorial and we hope to inspire you to create your own. If you want to see the whole source, you can enter the Github finished file Where see the final result online.

To take it a step further, you can design multiple rooms and connect them together and use doors that open and close. We would love to see your designs, please tweet us and we RT the best.

Back to all articles