paint-brush
How to Create a 3D Art Model with CSS3 [Step-by-Step Guide]by@adaorachi
3,411 reads
3,411 reads

How to Create a 3D Art Model with CSS3 [Step-by-Step Guide]

by MaryAnn Chukwuka December 14th, 2019
Read on Terminal Reader
Read this story w/o Javascript
tldt arrow

Too Long; Didn't Read

In this post, we will create a 3D animated wooden-house model with CSS. The project has been developed and tested primarily in Chrome and so, supports all properties used. To do this, we’ll need to use an HTML element as the parent element and give it the needed properties that tell the browser to expect 3D content within it. The objects positioned in this model are normal HTML elements. They have widths, heights, and are rectangular. The HTML elements are placed within the 3D scene using the.transform.property to move them in 3-dimensional spaces and also, the.border and.border are applied to the objects to create the illusion of depth.

Company Mentioned

Mention Thumbnail
featured image - How to Create a 3D Art Model with CSS3 [Step-by-Step Guide]
MaryAnn Chukwuka  HackerNoon profile picture

Have you ever come across a rather complex 3D creation while surfing the internet and your curiosity leads you to click on it to see if it has been rendered in Flash? These days, the features that come with modern browsers give you the power to create amazing projects without needing an external animating tool.

In fact, there are a handful of techniques: JavaScript, Canvas, which can be used to get this done. But in this article, I would like to introduce how to manage animations and transformations to the elements of a website using only CSS features.

CSS also for 3D?

Yes, even for 3D. In this post, we will create a 3D animated wooden-house model with CSS. 

Take a look at the DEMO here.

As a result, we will get a scene that is tilted at a 45-degree and viewed at a 2000-pixels viewpoint using the

perspective
property.

The objects positioned in this model are normal HTML elements. They have widths, heights, and are rectangular. This means that as you build a 3D object, you place each rectangle in place. The HTML elements are placed within the 3D scene using the

transform
property to move them in 3-dimensional spaces and also, the
border
and
gradient
properties are applied to the objects to create the illusion of depth.

CSS Transform

The

transform
property uses a value of
translate3d
with three dimensions. It is used to translate the element by a vector [tx, ty, tz], where tx is the translation along the x-axis, ty for the y-axis, and tz for the z-axis. Positive translation values will move the element along the positive direction of the axis, and negative values will move it in the opposite direction. Read more here.

Also, note that

translate3d
is one of the values that can be applied to the
transform
property. There are also
rotate
,
skew
,
scale
,
matrics
values and we will be using a combination of all or some in this tutorial. You should also note that the ordering of these values matter. When an element is applied more than one value, it starts its transformation from the last applied value to the first in that order. So, you can have similar values of transformation for two elements but they will be positioned differently if their orders are not the same.

In particular, this is the scene that will be created: The first is its static view while the other is its animated view.

A quick note

This project has been developed and tested primarily in Chrome and so, supports all properties used. Hence, I’ve removed the prefixed versions of rules in the following CSS. I would recommend either using compilers like LESS or SASS to manage these for you. Otherwise, be aware that most browser prefixes will need to be applied. Full versions of the CSS and SASS can be found on this pen, including the HTML.

Let’s get started!

We need to set a scene in which we can build our 3D creation. To do this, we’ll need to use an HTML element as the parent element and give it the needed properties that tell the browser to expect 3D content within it. Let’s start with some HTML:

<article class="scene">...</article>

In this case, the scene container is an

article
tag. You can use any other semantic tag and still achieve the same result. But I decided to use the
article
tag because in HTML5,
article
represents a standalone piece of content that could be reproduced elsewhere and still make sense.

The first property to apply is perspective. This property takes a value in pixels and represents the depth of the 3D scene. The

perspective
property defines how far the object is away from the viewer. So, a lower value will result in a more intensive 3D effect than a higher value.

For this view to feel like a wide scenery and with our model structure seated fully, we will set the perspective value quite high at 2,000 pixels. We could also establish the scene viewing angle by adjusting the

perspective-origin
property. This will determine whether we’re looking down at the object or from the side. The
perspective-origin
property takes two values, a horizontal and a vertical offset. And in our case, we want it centered with a
50% 50%
value and which is the default, so we don’t need to specify it.

article.scene {
   transform: translate(0, 50px);
   perspective: 2000px;
}

Let’s Build!

With the scene set, we can start putting together our 3D masterpiece. When beginning to build 3D objects with HTML and CSS, it’s worth taking a moment to understand how these properties work. Before we start building, we will create a house section inside our article tag, which will contain the different features of our house model as a set of

div
elements and form the main parts of our model.

<section class="house"> 
   <div class="deck"></div> 
   <div class="walls"></div> 
   <div class="roof"></div> 
   <div class="staircase"></div> 
   <div class="wedges"></div> 
   <div class="person"></div> 
</section>

The elements in the section above will take the form of the deck, the wall-frames and ceiling, the staircase attached at the top-left of the deck, the veranda and the animated persons.

With that in mind, we adjust our house class by rotating the entire scene by 45-degrees. We will have to set that now so as not to give us any issue with positioning our

divs
later. Next, we need to apply a
transform
property and
transform-style
to the house section and also give the
divs
some shared properties.

.house {
  margin: 0 auto;
  position: relative;
  transform-style: preserve-3d;
  transform: rotateY(-45deg);
  width: 460px;
}

.house div {
  position: absolute;
  transform-style: preserve-3d;
}

Each

div
will be positioned absolutely in order to control and fix them easily, and the transform-style property set to
preserve-3d
to instruct the browser that 3D transforms are to be applied in relation to the
perspective
property we set earlier.

With this done, we can create our children elements inside our parent

div
and start positioning them. I should also note that I used the
rem
unit for my positioning. You can use other units you desire but I find
rem
quite handy. The values are not as high as pixels or percent and it takes fewer adjustments to reach your targeted positions.

Creating the deck

The key concept of CSS drawing is to creatively use

border-radius
to create curves and shapes. Then rotate and place them in the right position. And how do I know the exact degrees or pixels to put into my values? Well, I don’t! The trick is to use the browser dev tools to help you with a live adjustment. Then copy the styles into your source file when you get the right value. And the most important thing that would help you with a good judgment of deciding what values to use or values closest to the one you want is to know the directions of the dimensions. With that, you’ll have fewer adjustments to make.

Let’s continue then, shall we?

<div class="deck">
  <div class="covering"></div>
  <div class="left"></div>
  <div class="right"></div>
  <div class="back"></div>
  <div class="front"></div>
</div>
.deck *:not(.covering),
.deck *::before,
.deck *::after {
  border-right: 3px solid #78552c;
  border-top: 2px solid #b5854a;
}

.deck .left {
  width: 33.75rem;
  height: 2.06rem;
  background: #8a693d;
  transform: translate3D(7.4rem, 19.6rem, 38.3rem);
}

.deck .right {
  width: 61.3rem;
  height: 2.9rem;
  background: #4e3b23;
  transform: translate3D(-38.4rem, 29.1rem, -38.4rem);
}

.deck .back {
  width: 53rem;
  height: 2.9rem;
  background: #4e3b23;
  transform: translate3D(-66rem, 29.3rem, -12.8rem) rotateY(90deg);
}

.deck .front {
  width: 25.3rem;
  height: 2.2rem;
  background: #9c7645;
  transform: translate3d(32.1rem, 21.2rem, 9rem) rotateY(90deg);
}

The above rules assign a border-right and left to all sides of the deck except the covering to create a nice depth effect. It also describes a

width
of 33.75rem (540px), a
height
of 2.06rem and a dark-brown
background color
for the left side. The
div
is then positioned at 7.4rem along its x-axis, 19.6rem along its y-axis and 38.3rem at its z-axis.

Next, we create the other sides of the deck. Since it has four corners, I created two more sides — right and back sides and applied similar properties. You may have noticed I used a

rotateY
of 90-degrees on the 
.deck .back
and 
.deck .front
 . We want them to be perpendicular to the 
.deck .right
and 
.deck .left
 . The front side will be a little more complicated since we have to make a dent to fit in our staircase. I created a single
div
for this and also used pseudo-elements 
::before, ::after
for carving out this space.

To complete the deck, we will be creating a covering for it. We will create a large L-shape from a rectangular shape. This is to create room for the stairs which we will be building later. I could have made this shape using the clip-path property but I wanted the shapes used for this project to be built solely with backgrounds and sizes. And again, not all browsers support the clip-path property yet. With this, I use the stacking linear-gradient background below to achieve the L-shape for the covering.

background: linear-gradient(#7a5d37, #7a5d37) calc(100% — 124px) no-repeat, linear-gradient(#7a5d37, #7a5d37) 0 188px/100% no-repeat;

We will also give it a

translate3d
value and
rotate
it negative 90-degrees along it x-axis to make it lap onto the four corners of the deck stands.

The result of all this should be a scene that looks like this:

Erecting the wall frames

The HTML structure for the walls will be a little intricate, but we can always manage it, right? We will be inserting a window div inside the walls

div
for all four sides of the wall. And inside the windows
div
, we will be creating two rim class
divs
for the two windows, and each with a louvre and a lower bar div . And the end, we’ll be adding a door
div
to the front wall.

First, each of the walls will be given a value of

12.5rem
and we will start with the left wall. The left wall should be created first to determine where we want the length of the house to begin and end and also to determine how well to fit and align it to the adjoining walls. We will begin by giving it a
translate3d
,
width
as usual.

To complete this wall, we will give it a brown color; a little darker than the front wall. They are the only two walls that will be exposed to the viewer and we want to make it appear that the light reflects more on the front wall than the left wall.

Next, we will create the right wall by giving it similar properties. For the

transform
property, we will give it a
translate3d(-17.7rem, 6.25rem, -9.06rem)
 . The negative values for the x-axis and z-axis are because the wall will be further away from our view. We will also add a
rotateY
of 180-degrees since we want it to be perpendicularly aligned to the left wall.

To finish up this section, we will create the back and front walls in the same fashion but give them a

rotateY
of 90-degrees for the front wall and 3-degrees off 90 for the back wall. This will form perpendicular walls and hence the four-corner or cuboidal shape of the house.

<div class="front">
  <div class="windows">
    <div class="rim first-rim">
      <div class="louvres"></div>
      <div class="bar"></div>
    </div>
    <div class="rim second-front-rim">
      <div class="louvres"></div>
      <div class="bar"></div>
    </div>
  </div>
  <div class="doors">
    <div class="door-open"></div>
    <div class="door-close">
      <div class="door-knob"></div>
      <div class="door-bars"></div>
    </div>
  </div>
</div>
.right, .left, .back, .front {
    height: 12.5rem;
}
.walls .left {
    width: 22rem;
    background: #c3964f;
    transform: translate3d(18.4rem, 6.2rem, 38.3rem);
}

.walls .right {
    width: 22.2rem;
    background: #8c682f;
    transform: translate3d(18.1rem, 6.2rem, 9.4rem);
}

.walls .back {
    width: 29.2rem;
    background: #9f7636;
    transform: translate3d(2.2rem, 6.3rem, 22.7rem) rotateY(90deg);
}

.walls .front {
    width: 28.75rem;
    background: #c79d5a;
    transform: translate3d(26rem, 6.2rem, 23.9rem) rotateY(90deg);
}

At the end, we will have a nice-looking shape as the one below.

The windows and door

The windows will be created by giving the rim

div
a weight and height of 4.3rem and height: 3.75rem respectively and also move it 30px away from the top of the walls. Then, we add a border of
9px solid #dac29a
for both left and right sides of the rims
div
and a
7px solid #dac29a
to the top and bottom. The offset of 3px on both values is to make up for the width of one to the other since the
perspective
value we gave to its first parent
div
slightly distorted that.

Afterward, we will be adding an extra class-names to the rims

div
. This is because both window rims will be stacked up together, so we need to create a distance between them by applying a
left
value. All first rims will take a
left
value of
40px
, and while the second rims of the left and right walls take a
215px
value, the second rims of the front and back walls will take a left value of
330px
. This is because the front and back walls are longer in width compared to the left and right walls.

Next, we create a 

::before
and 
::after
pseudo-class from the rim
div
. These classes will be used to create the crossed dividers inside the window frames. We will give the 
::before
class a left value of
45%
and the 
::after
class a top value of 45%. Instead of using a border here, I will be using a gradient background color to simulate a fine inward distance. We will also create a louvre
div
which will be used to form the window glasses. We will give it a
background
 , a
border-top
and an
opacity
of 0.7 to give it a glassy look.

.windows .rim {
  top: 30px;
  width: 4.3rem;
  height: 3.75rem;
  border-left: 9px solid #dac29a;
  border-right: 9px solid #dac29a;
  border-top: 7px solid #dac29a;
  border-bottom: 7px solid #dac29a;
}

.windows .rim::before {
  background: linear-gradient(to left, #dac29a, #bda886);
  left: 45%;
  width: 0.62rem;
  height: 3.75rem;
  content: '';
  position: absolute;
}

.windows .rim::after {
  background: linear-gradient(to left, #dac29a, #bda886);
  top: 45%;
  width: 4.3rem;
  height: 0.45rem;
  content: '';
  position: absolute;
}

.windows .first-rim {
  left: 40px;
}

.windows .second-left-rim,
.windows .second-right-rim {
  left: 215px;
}

.windows .second-front-rim,
.windows .second-back-rim {
  left: 330px;
}

.windows .louvres {
  background: linear-gradient(to left, #dac29a, #b5a181);
  border-top: 2px solid #7a5d37;
  width: 4.3rem;
  height: 3.75rem;
  z-index: -1;
  opacity: 0.7;
}

.windows .bar {
  background: linear-gradient(to left, #dac29a, #b5a181);
  border-top: 2px solid #7a5d37;
  width: 6rem;
  height: 0.5rem;
  bottom: -15px;
  right: -15px;
  border-radius: 20px;
}

.doors .door-open {
  width: 5.5rem;
  height: 7.5rem;
  bottom: 0%;
  left: 40%;
  border-right: 3px solid #a58152;
  background: linear-gradient(12deg, #7a5d37 40%, #a07c43 0 70%);
}

.doors .door-close {
  width: 5.5rem;
  height: 7.5rem;
  bottom: 0%;
  left: 40%;
  border-left: 8px solid #dac29a;
  border-right: 8px solid #dac29a;
  border-top: 8px solid #dac29a;
  background: #fff;
}

.doors .door-knob {
  width: 0.5rem;
  height: 0.5rem;
  background: linear-gradient(to top, #dac29a, #dac29a);
  border-radius: 50%;
  top: 50%;
  left: 5px;
  border-left: 2px solid #a28658;
}

.doors .door-bars {
  width: 7rem;
  height: 0.5rem;
  background: linear-gradient(to top, #dac29a, #dac29a);
  border-radius: 20px;
  top: -15px;
  left: -12px;
  border-bottom: 2px solid #7a5d37;
}

Styling the door requires two features. The first door-open

div
is to create what looks like an opening in the front wall, which will come in handy when we animate our person to move in through the door. We will give it a
linear-gradient
to simulate this; sharp contrast colors going from the floor color to the right wall color and tilted at a degree of 12 and also some borders that will make it look like its inset. Secondly, we will create the door by giving the door-close
div
a
background-color
of white and borders that will form the door-frames. We will give the door, a knob and top bar using
background
,
border-radius
and sizes.

Roofing the house structure

<div class="roof">
  <div class="left"></div>
  <div class="right"></div>
  <div class="front"></div>
  <div class="back"></div>
</div>

<div class="porch">
  <div class="bottom"></div>
  <div class="left"></div>
  <div class="right"></div>
  <div class="front"></div>
  <div class="first_bar bar"></div>
  <div class="second_bar bar"></div>
</div>

We will start this section by creating the HTML structure. Creating the upper compartments may look pretty straight-forward but it seemed to me to be the toughest part of this project. This is because I had to make several tilting and positioning points to get the shape I needed. We will be starting this section by creating the HTML

divs
for the roof and porch structures.

The styling for the front side of the roof will be achieved by using borders to create a triangular shape. This could be done by using the clip-path property too but I’m opting for borders instead. First, we will give the

border-left:220px solid transparent
,
border-right:246px solid transparent
and
border-bottom:110px solid #e7b565
 . We will also give it a rotation of 90 degrees along its y-axis which is the same degree as the front wall. Next, we will give it a
translate3d
values. We will do the same to the backside and give it similar properties.

.roof .front {
    border-left: 220px solid transparent;
    border-right: 246px solid transparent;
    border-bottom: 110px solid #c79d5a;
    transform: translate3d(25.6rem, -0.6rem, 23.7rem) rotateY(90deg);
}

.roof .back {
    border-left: 203px solid transparent;
    border-right: 203px solid transparent;
    border-bottom: 98px solid #9f7636;
    transform: translate3d(15.85rem, -0.6rem, 31.7rem) rotateY(90deg);
}

.roof .left {
    width: 24.25rem;
    height: 8.4rem;
    background-image: repeating-linear-gradient(to left, #795f35, #a68349, 10%, #a68349, #795f35 10%);
    border-right: 5px solid #86693b;
    border-bottom: 3px solid #927340;
    transform: translate3d(29.5rem, -1.8rem, 42rem) rotateX(45deg) skewX(-35deg);
}

.roof .right {
    width: 41.25rem;
    height: 12.5rem;
    background-image: repeating-linear-gradient(to left, #8e734b, #65502d, 10%, #65502d, #8e734b 10%);
    border-right: 5px solid #b38f55;
    border-bottom: 3px solid #927340;
    transform: translate3d(-12.3rem, -3.1rem, -0.7rem) rotateX(131deg) skewX(-35deg);
}

The right and left roofs will also be created in the same fashion but with a twist. We will add a

rotateX(45deg)
to the left roof and since the scene we are building is to be viewed at 45-degrees, this effect works to our advantage in this case. Subsequently, a
rotateX(45deg + 90deg)
which is calculated as
rotateX(135deg)
will be given to the right roof. This is to create an opposite tilting of both roofs.

Next, we also add a

skewX
value of negative 35-degrees to both roofs. This is also to make the bottom-end side of the roof planes stick out more than the top-end. The idea behind this is that looking at our view, we should be able to put up a better judgment on the appearance and placing of the structure. We will also be adding a nice repeating-linear-gradient to make them appear ridged-like.

background: repeating-linear-gradient(to left, #8e734b, #65502d, 10%, #7b561a, #655235 10%)

Next, we will be creating a porch that will be placed directly above the front door. The bottom-porch will be styled first to enable proper alignments of other sides of the porch. A

width
and
height
of
2.6rem
and
11rem
will be assigned to it, a
translate3d
and a
rotateX(89deg)
 . We slightly offset the rotate value off 1 degree since we are viewing the scene from a higher viewpoint, an 89-degrees will completely tilt the bottom-side of the porch and still make it visible.

Subsequently, we will also create the left and right side of the porch similarly but completely tilting them and make them steeper. For the front side, we will be creating a triangular shape using borders just like we did for the front and back sides of the roof. We will also adjust the sides and positions until it laps perfectly unto the other sides of the porch.

To complete the porch, we will create two pillars that will hold the porch and stretch from the bottom of the porch to the flooring.

.porch *:not(.front) {
  background: linear-gradient(to top, #795f35, #b18c50);
  border-right: 3px solid #9a7944;
}

.porch .bottom {
  width: 2.6rem;
  height: 11rem;
  transform: translate3d(52rem, 0.4rem, 36rem) rotateX(90deg);
}

.porch .left {
    width: 3rem;
    height: 6rem;
    transform: translate3d(51.1rem, 1.6rem, 38rem) rotateX(63deg);
}

.porch .right {
    width: 3rem;
    height: 5.9rem;
    transform: translate3d(51.3rem, 1.6rem, 32.8rem) rotateX(-63deg);
}

.porch .front {
    border-left: 70px solid transparent;
    border-right: 70px solid transparent;
    border-bottom: 36px solid #866a3c;
    transform: translate3d(58rem, 2.7rem, 44.2rem) rotateY(90deg);
}

.porch [class*='bar'] {
    height: 155px;
    width: 10px;
    border-radius: 0 0 5px 3px;
    background: linear-gradient(to bottom, #795f35, #b18c50);
    border-right: 3px solid #9a7944;
}

.porch .first_bar {
    transform: translate3d(52rem, 6rem, 37rem);
}

.porch .second_bar {
    transform: translate3d(51.8rem, 6rem, 31.5rem);
}

Walking up the staircase

<div class="staircase">
  <div class="vertical-step"></div>
  <div class="horizontal-step"></div>
  <div class="back-cover vertical"></div>
  <div class="bottom-cover horizontal"></div>
  <div class="side-cover"></div>
</div>

Building the stairs was not as difficult as I thought it would. What I had to do was to create div class — 

horizontal-steps
,
vertical-steps
 , make pseudo-elements from them to make-up the three numbers of stairs. Then, I made a back, bottom and side coverings for them too.

For the pseudo-elements, I did not have to use

transform
properties for them, thankfully. This was because I did not need them to be transformed by their z-axes. All I needed to do was to use the positional properties — 
top
 ,
bottom
 ,
left
and
right
to align them properly and since we already have all divs at absolute positioning, it was quite easy to accomplish.

First, we will start with the vertical-step

div
by giving it
width
,
height
and
transform
properties. With the
transform
properties set, we will not need to set the positions of its pseudo-elements because they will automatically inherit them.

All we have to do is to use

top
and
left
properties to push them to the positions we want them to be. A similar procedure will be followed for the horizontal-steps as well.

Below are the CSS rules:

.staircase [class*='vertical'],
.staircase [class*='vertical']::before,
.staircase [class*='vertical']::after {
  background-color: #7a5d37;
  border-right: 3px solid #78552c;
  border-top: 2px solid #b5854a;
}

.staircase .vertical-step {
  width: 5.62rem;
  height: 0.93rem;
  transform: translate3D(46.8rem, 18.3rem, 33.3rem) rotateY(-4deg);
}

.staircase .vertical-step::before {
  width: 6.2rem;
  height: 0.93rem;
  content: '';
  position: absolute;
  top: 24px;
  left: -40px;
}

.staircase .vertical-step::after {
  width: 6.2rem;
  height: 0.93rem;
  content: '';
  position: absolute;
  top: 48px;
  left: -65px;
}

.staircase [class*='horizontal'],
.staircase [class*='horizontal']::before,
.staircase [class*='horizontal']::after {
  background-color: #8a683d;
  border-right: 4px solid #78552c;
}

.staircase .horizontal-step {
  width: 5rem;
  height: 1.3rem;
  width: 80px;
  height: 21px;
  transform: translate3D(47.8rem, 17.5rem, 33.3rem) rotateX(90deg) rotateY(2deg);
}

.staircase .horizontal-step::before {
  width: 5rem;
  height: 1.3rem;
  content: '';
  position: absolute;
  top: 63px;
  left: 35px;
}

.staircase .horizontal-step::after {
  width: 5rem;
  height: 1.3rem;
  content: '';
  position: absolute;
  top: 116px;
  left: 63px;
}

.staircase .vertical-back {
  width: 6.25rem;
  height: 3.13rem;
  content: '';
  position: absolute;
  transform: translate3D(40rem, 20.6rem, 25rem) rotateY(-5deg);
}

.staircase .horizontal-back {
  width: 6.25rem;
  height: 3.55rem;
  content: '';
  position: absolute;
  transform: translate3D(39.2rem, 22.2rem, 27rem) rotateX(82deg) rotateY(1deg) skewX(-25deg);
}

.staircase .side {
  width: 1.25rem;
  height: 2.56rem;
  background: #674e2e;
  transform: translate3D(56.5rem, 16.68rem, 38rem) rotateY(90deg);
}

.staircase .side::before {
  width: 1.63rem;
  height: 1.71rem;
  background: #674e2e;
  content: '';
  position: absolute;
  transform: translate3D(-1.6rem, 0.8rem, 0rem);
}

.staircase .side::after {
  width: 1.31rem;
  height: 0.85rem;
  background: #674e2e;
  content: '';
  position: absolute;
  transform: translate3D(-2.9rem, 1.6rem, 0rem);
}

Subsequently, we create the bottom and back coverings by copying the properties for the horizontal and vertical steps, enlarge their widths and heights and place them accordingly. And for the side covering, I used a

clip-path
when I created it at first but I had wanted clip-path to be out of the scope of this tutorial, I had to recoup another method to achieve that shape.

So, I created a

div
, made pseudo-elements from it again, gave them appropriate shapes and aligned them to the three sides of the stairs each.

Framing the veranda

The HTML structure for the veranda will be pretty lengthy but needful because we will be placing bars and adjoining planks at all sides of the deck. 

<div class="veranda">
  <div class="wedges">
    <div class="wedge1 border-left"></div>
    <div class="wedge2 border-left"></div>
    <div class="wedge3 border-left"></div>
    <div class="wedge4 border-right"></div>
    <div class="wedge5 border-right"></div>
    <div class="wedge6 border-right"></div>
    <div class="wedge7 border-right"></div>
    <div class="wedge8 border-left"></div>
    <div class="wedge9 border-left"></div>
  </div>

  <div class="planks">
    <div class="plank1 border-right"></div>
    <div class="plank2 border-right"></div>
    <div class="plank3 border-left"></div>
    <div class="plank4 border-left"></div>
    <div class="plank5 border-left"></div>
    <div class="plank6 border-right"></div>
    <div class="plank7 border-left"></div>
    <div class="plank8 border-left"></div>
  </div>
</div>

Erecting the veranda was my favorite part of this project. The positionings were quite straight-forward. The wedges lie at every corner of the deck, so once I got a wedge’s position rightly, I can only adjust an axis or two to get the opposite wedge’s position. This also applies to the planks which will be used to join the wedges together.

We will start with the wedges, and first, we will be creating nine wedges which are the total numbers to be fitted at every corner including the edges of the staircase as well. We will assign a general styling of 6px

border-radius
to the top-left and right and a border-top of
1px solid #b5854a
. And after then, we will add extra class names to the wedges and planks — some will take a border-left position while others will take a border-right.

The reason for this is the following. We want every wedge attached to the stairs to have a rotation of 90-degrees while the others remain at 0-degree. The stair wedges will be used to build the stair-rails, and so they will need to be rotated to perfectly form this shape and then only

border-left
properties will be needed while we give a
border-right
class to the others.

A similar procedure will be done for the planks

divs
. We will give them a general styling of
border-top
 ; a
border-right
to the planks directly facing the view and a border-left to the planks facing a different view other than the front. At the end of this, we will be left with nicely drawn wooden bars and planks attaching them and we will do the magic by using the borders properties.

Viewing the images below will give a better visualization. 

After giving them general styling, we can then begin to work on the positioning. Once we place a wedge at the corner, we can use good judgment to decide how far the opposite one can be. We will perform similar actions to the plank

divs
.

And just like how we created the deck, we will give the front and back planks a

rotateY
of 90-degrees, so as to be right-angled with the side planks. We will also give a
rotateY(90deg)
to the planks that form the stair-rails and as well as a
 rotateZ
values to make them slanted.

.veranda [class*='wedge'] {
  border-top-left-radius: 6px;
  border-top-right-radius: 6px;
  border-top: 1px solid #b5854a;
}

.veranda .wedges [class*='border-right'] {
  border-right: 3px solid #8a683d;
}

.veranda .wedges [class*='border-left'] {
  border-left: 3px solid #8a683d;
}

.veranda .wedge1 {
  width: 0.6rem;
  height: 2rem;
  background: linear-gradient(to bottom, #7a5d37, #523e25);
transform: translate3D(57.8rem, 14rem, 46.3rem) rotateY(90deg);
}

.veranda .wedge2 {
  width: 0.7rem;
  height: 2.3rem;
  background: linear-gradient(to bottom, #7a5d37, #523e25);
  transform: translate3D(54rem, 13.3rem, 38rem) rotateY(90deg);
}

.veranda .wedge3 {
  width: 0.8rem;
  height: 2.6rem;
  background: linear-gradient(to bottom, #8a683d, #b78549);
  transform: translate3d(45.8rem, 15rem, 32rem) rotateY(90deg);
}

.veranda .wedge4 {
  width: 1rem;
  height: 2.8rem;
  background: linear-gradient(to bottom, #8a683d, #b78549);
  transform: translate3D(40rem, 16.5rem, 38.5rem);
}

.veranda .wedge5 {
  width: 1.3rem;
  height: 2.8rem;
  background: linear-gradient(to bottom, #8a683d, #b78549);
  transform: translate3D(10rem, 16.5rem, 38.5rem);
}

.veranda .wedge6 {
  width: 1rem;
  height: 2.8rem;
  background: linear-gradient(to bottom, #7a5d37, #523e25);
  transform: translate3D(10rem, 16.5rem, 5.5rem);
}

.veranda .wedge7 {
  width: 1rem;
  height: 3rem;
  background: linear-gradient(to bottom, #7a5d37, #523e25);
  transform: translate3D(48rem, 16.3rem, 5.5rem);
}

.veranda .wedge8 {
  width: 0.7rem;
  height: 2.3rem;
  background: linear-gradient(to bottom, #8a683d, #b78549);
  transform: translate3D(58.5rem, 13.3rem, 38rem) rotateY(90deg);
}

.veranda .wedge9 {
  width: 0.6rem;
  height: 1.7rem;
  background: linear-gradient(to bottom, #8a683d, #b78549);
  transform: translate3D(61.8rem, 14.4rem, 46.5rem) rotateY(90deg);
}

.veranda .planks [class*='border-right'] {
  border-right: 2px solid #78552c;
  border-top: 2px solid #b5854a;
}

.veranda .planks [class*='border-left'] {
  border-left: 3px solid #78552c;
  border-top: 2px solid #b5854a;
}

.veranda .plank1 {
  width: 4.7rem;
  height: 0.7rem;
  background-color: #795b36;
  transform: translate3D(55rem, 13.6rem, 43.7rem) rotateY(90deg) rotateZ(-19deg);
}

.veranda .plank2 {
  width: 1.7rem;
  height: 0.7rem;
  background-color: #806139;
  transform: translate3D(53.5rem, 13.5rem, 39.2rem) rotateZ(-3deg);
}

.veranda .plank3 {
  width: 8.8rem;
  height: 0.7rem;
  background-color: #8a683d;
  transform: translate3D(52rem, 12.7rem, 46.7rem) rotateY(90deg);
}

.veranda .plank4 {
  width: 30rem;
  height: 0.9rem;
  background-color: #8a683d;
  transform: translate3D(12.7rem, 16.5rem, 40rem);
}

.veranda .plank5 {
  width: 34rem;
  height: 1rem;
  background-color: #46341f;
  transform: translate3D(-11rem, 17.7rem, 18.7rem) rotateY(90deg);
}

.veranda .plank6 {
  width: 38rem;
  height: 0.9rem;
  background-color: #46341f;
  transform: translate3D(10rem, 17rem, 5rem);
}

.veranda .plank7 {
  width: 17rem;
  height: 0.8rem;
  background-color: #8a683d;
  transform: translate3D(52rem, 13.2rem, 31.7rem) rotateY(90deg);
}

.veranda .plank8 {
  width: 4.5rem;
  height: 0.7rem;
  background-color: #8a683d;
  transform: translate3D(63rem, 12.5rem, 47.7rem) rotateY(90deg) rotateZ(-19deg);
}


Building our characters

With the structure in place, we need some persons to move in and out of the house to show it is habitable. In the initial draft of this project, creating the characters was not in the picture but afterward, when the idea popped up, I thought it was a good one. Creating the characters and their animations gave the house scene a livelier look. 

To build the characters, we need both persons to walk into the house with their starting point at the foot of the stairs. The first person will be going in through the door and this is where the door-open

div
we created earlier comes in handy. The second person will be moved to the end of the front-side of the house and both persons will return to their starting points and move past that point a little further.

Both character shapes are made up of 2 main parts, the heads and bodies. The legs are added using pseudo-elements on the body. So, both characters take one styling.

<div class="person">
  <div class="person-one">
    <figure class="head"></figure>
    <figure class="body"></figure>
  </div>
  <div class="person-two">
    <figure class="head"></figure>
    <figure class="body"></figure>
  </div>
</div>

Each of the parts is absolutely positioned and

border-radius
is used to create the round shapes. The leg pseudo-elements are described at once then each positioned in separate rules. The CSS rules are as follows:

.person .person-one figure,
.person .person-two figure {
  background-color: black;
  display: block;
  position: absolute;
}

.person .person-one .head,
.person .person-two .head {
  border-radius: 22px;
  width: 20px;
  height: 20px;
  left: 3px;
  top: 0;
}

.person .person-one .body,
.person .person-two .body {
  border-radius: 30px 30px 0 0;
  height: 30px;
  top: 21px;
  width: 26px;
}

.person .person-one .body:before,
.person .person-one .body:after,
.person .person-two .body:before,
.person .person-two .body:after {
  content: "";
  position: absolute;
  background-color: black;
  width: 9px;
  height: 15px;
  top: 30px;
}

.person .person-one .body:before,
.person .person-two .body:before {
  left: 3px;
}

.person .person-one .body:after,
.person .person-two .body:after {
  left: 14px;
}

With the character shape of person-one and person-two specified, we will position them at the starting position. The person-one at the foot of the staircase, and the person-two a little further away from person-one. And we will also want to make person-two appear just right after person-one is being animated or start climbing the stairs. To set this up, we will give a scale of 0 to person-two and delay its animation for 5.5 seconds.

.person .person-one {
  transform: translate3d(935px, 179px, 780px) rotateY(0deg);
  animation: move-person-one 20s 3s infinite;
}

.person .person-two {
  transform: translate3d(935px, 179px, 790px) rotateY(0deg) scale(0);
  animation: move-person-two 15s 5.5s infinite;
}

Keyframe animation

With the characters in place, the scene is ready for some animation.

If you view the demo you’ll see a few animations taking place. Rather than go through all the animations that set up the scene, I’ll focus on the animation of the character walking in and out of the house and also on the opening and closing of the door.

Timing and animating the HTML elements is achieved by using keyframes and then attaching the set of keyframes to an element using the animation property.

The first thing is to animate the first character, to have it walk up the stairs and into the front door, walk into the house and out and approach the stairs again. Here’s a set of keyframes that achieves this:

@keyframes move-person-one {
  0%, 10%, 90% {
    transform: translate3d(935px, 179px, 780px) rotateY(0deg);
  }
  20%, 85% {
    transform: translate3d(870px, 169px, 610px) rotateY(0deg);
  }
  30%, 80% {
    transform: translate3d(870px, 175px, 610px) rotateY(86deg);
  }
  40%, 45%, 70% {
    transform: translate3d(635px, 215px, 415px) rotateY(86deg);
  }
  55%, 50% {
    transform: translate3d(620px, 215px, 405px) rotateY(86deg);
  }
  91% {
    transform: translate3d(945px, 179px, 810px) rotateY(0deg);
  }
  100% {
    transform: translate3d(945px, 179px, 990px) rotateY(0deg);
  }
}

Keyframes are a series of steps, described using percentages. The percentage relates to the animation time, so that if an animation was to last 10 seconds, 10% would be the 1-second mark. 90% would be the 9-second mark.

Next, we animate the door that opens each time the first character goes inside the house and closes when it leaves the house. The timing of when the first character approaches the front door and the time the door opens and vice-versa should be properly calculated. First, we will establish the same duration time of 20 seconds for both the first character and the door.

Afterward, we delay the first-character for 3 seconds and the door for 8 seconds, so this corresponds to the interaction described above. Next, immediately the door animation starts, we will transform its z-axis to 20-degrees and rotate its y-axis at 20-degrees and move the degrees higher at 4 seconds into the animation.

This animates the door by opening it wide enough for the character to pass through and close it at about the 9th second. I repeated the same animation after the 10th second till the end of it.

.doors .door-close {
  animation: door-open 20s 8s infinite;
}

@keyframes door-open {
  0% {
     transform: translate3d(0px, 0px, 20px) rotateY(20deg);
  }
  8%, 50% {
     transform: translate3d(0px, 0px, 53px) rotateY(53deg);
  }
  35%, 70%, 100% {
     transform: translate3d(0px, 0px, 0px) rotateY(0deg);
  }
}

Having done that, let’s set up the corresponding

animation
keyframes for the second character. It will start its movement just like first-character, climbs the stairs and move to to the far end of the house’s front view and make its return.

@keyframes move-person-two {
  0%, 10%, 80% {
    transform: translate3d(935px, 179px, 790px) rotateY(0deg) scale(1);
  }
  20%, 75% {
    transform: translate3d(870px, 169px, 610px) rotateY(0deg);
  }
  30%, 70% {
    transform: translate3d(860px, 169px, 380px) rotateY(0deg);
  }
  35%, 65% {
    transform: translate3d(860px, 169px, 380px) rotateY(89deg);
  }
  40%, 60% {
    transform: translate3d(795px, 169px, 380px) rotateY(89deg);
    opacity: 1;
  }
  41%, 50% {
    opacity: 0;
  }
  81% {
    transform: translate3d(965px, 179px, 820px) rotateY(0deg);
    opacity: 1;
  }
  82% {
    transform: translate3d(995px, 189px, 855px) rotateY(89deg);
  }
  100% {
    transform: translate3d(1200px, 179px, 860px) rotateY(89deg);
  }
}

In this way, the two animations are being applied. The first character taking five more seconds later than the first to go through the animated cycle.

With the following animations and keyframes in place, the final result will be this:

Demo and source code

If you haven’t already, check out the finished result in a modern browser or download the source from Github.