CSS is such a versatile language.
You can use it to design web layouts, interactive buttons, style your website's text... or create a 3D Christmas gift box.
In this tutorial, I'll demonstrate how I made one for my CSS Christmas Calendar, where I posted a new CSS art every day until Christmas.
Anatomy of the box
To start creating the object, I had to analyze the different parts:
- a box, which consists of 5 sides (4 sides and the bottom)
- a lid, which is similar except it doesn't have a bottom but a top part
- the loops of the ribbon (left and right)
- the two ends of the ribbon
Note: In the following examples I simplified the colors and sizes, in the final rotating box I used CSS variables to store them.
The 3D box
Although CSS is primarily used to style elements in 2 dimensions (X and Y axes), it actually knows about the third one (Z-axis, the depth).
To create a 3D box, I had to design the 5 sides and move/rotate them in the 3D space to get to their desired place.
As you can see, the sides already have the ribbon on them.
I achieved it with a linear gradient background:
.side {
background: linear-gradient(to right,
red 40%,
white 40%,
white 60%,
red 60%
);
}
So the trick is to have a horizontal gradient (from left to right) with sharp edges at 40% and 60%.
For the lid, the concept is very similar, but there are 2 gradients: one horizontal and one vertical.
However, if both would start with red, the upper one would cover the other. So I used transparent instead for one of the directions, and also a slightly darker color for the ribbon, to help distinguish them from each other.
.lid {
background: linear-gradient(to right,
transparent 40%,
lightblue 40%,
lightblue 60%,
transparent 60%
),
linear-gradient(to bottom,
red 40%,
white 40%,
white 60%,
red 60%
);
}
The next step is to rotate the sides to their correct place.
First, a container div
needs to get the perspective
property. This defines how far the box is away from the user. I set it to 400px, which gives a realistic depth to it.
To move the sides to the right place, I used the transform
property. Not only can it transform elements in 2D, but it can move and rotate in the 3D space as well (with rotateZ
, translateZ
).
.gift-box .gift-box-lid__side--top {
transform: rotateX(90deg)
translateZ(120px)
translateX(-10px)
translateY(0);
}
After the sides were in their place, the whole box was moved to its correct place.
.gift-box {
transform: translateZ(-100px)
rotateY(50deg)
rotateX(-15deg)
rotateZ(-30deg);
}
I did it with the box and the lid too, you can try it yourself, and see how it'd look with a different perspective or rotation:
The ribbon
The ribbon consists of two parts: the two ends with a V-cut, and the two loops.
The end is actually a simple div
, with 2 gradient backgrounds overlapping:
.ribbon-end {
position: absolute;
width: 40px;
height: 110px;
background:
linear-gradient(45deg, white 72%, transparent 72%),
linear-gradient(-45deg, white 72%, transparent 72%);
border: 2px solid darkred;
border-top: none;
border-bottom: none;
transform: rotateX(-70deg)
translateX(80px)
translateY(-80px)
translateZ(-60px);
}
With the transform
property I could move them to their correct place, just like with the sides of the box.
Rounded shapes in 3D
So far the gift box consists only of rectangles.
But the loops of the ribbon aren't rectangular elements.
CSS doesn't have the functionalities of a proper 3D rendering engine, thus there's a limitation of rounded shapes.
Initially, I was playing with the idea to create 6-10 flat sections for the ribbon and rotate each one, but the result would've looked angular.
Instead, I ended up with a little hack: it's easy to create the shape of the loop with a rounded rectangle and a skew transform.
What if I would simply have many of that rectangle, behind each other?
So that is exactly what I did!
A 40px wide ribbon requires 40 div
s, each transposed with 1 extra pixel by the Z-axis.
Also, to have the borders with dark red, the first and last 3 div
s should have the color of the border, the rest the color of the ribbon.
I didn't want to write 40 CSS selectors for each side of the loops, so used another trick:
You can set CSS variables on the HTML elements by editing their inline style
attribute:
<div class="ribbon" style="--gift-box-position: 0px; border-color: darkred"></div>
<div class="ribbon" style="--gift-box-position: 1px; border-color: white"></div>
The transform
CSS property of the ribbon loop looks like this:
.ribbon {
/* ... styling of the ribbon ... */
transform: rotateX(-5deg)
rotateY(90deg)
/* use a CSS variable for the translate, defined in the inline style */
translateZ(calc(80px - var(--gift-box-position)))
translateX(-30px)
translateY(-110px)
skewY(30deg);
}
(Note: I could set the border color directly in the inline style, but I had to use a CSS variable, otherwise I would've had to list all parameters for the transform also).
If you notice, from certain angles the loop is transparent and you can see the many layers of div
s showing up.
Rotating the box
Just like opacity, color, or position, the transform
property can also be animated in CSS.
To create an infinite animation, I had to set the keyframes:
@keyframes rotation-3d {
from {
transform: translateZ(-100px) rotateY(50deg) rotateX(-5deg) rotateZ(-30deg)
}
to {
transform: translateZ(-100px) rotateY(409deg) rotateX(-5deg) rotateZ(-30deg)
}
}
(Note: all transformed values have to be listed, even if only one changes)
Finally, I applied this animation on the gift box element:
.gift-box {
animation: rotation-3d 20s infinite linear;
}
The CSS Christmas Calendar
This rotating 3D Christmas gift is part of my Holiday project, the CSS Christmas Calendar. I posted a new calendar item every single day from the 1st of December until Christmas Eve.
I tried different techniques each day and learned a lot - and I share the most interesting techniques in this series.
All code is available on Codepen.
Check it if you want to see other Christmassy CSS magic!
Top comments (0)