How to create a 3D Art Model with CSS3

MaryAnn Chukwuka
18 min readDec 17, 2019

--

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 pure CSS.

Take a look at the DEMO here.

As a result, we will get a scene that is tilted at 45-degrees and viewed at a 2000-pixels viewpoint using theperspective 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:

Static Scene
Animated Scene

A quick note.

The following 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:

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.

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.

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.

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?

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:

The Deck

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 arotateY 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.

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

The Walls

The windows and door

The windows will be created by giving the rim div a weight and height of 4.3rem and 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 leftvalue 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.

The windows with a closed-door

Next, we create a ::before and ::after pseudo-elements from the rim div. These elements will be used to create the crossed dividers inside the window frames. We will give the ::before element a left value of 45% and the ::after element 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.

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-closediv 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.

The walls with the opened door

Roofing the house structure

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.

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 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%)
The Roof

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 to 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.

The Porch

Walking up the staircase

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:

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.

The Staircase

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.

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 stylings, 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.

The veranda
The veranda with the house

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, move past that point a little further and end the animation cycle.

The characters

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.

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:

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.

Keyframe animation

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

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.

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.

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:

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 time duration 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.

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

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.

--

--

MaryAnn Chukwuka

A Full-stack developer with a passion for creative designs.