DEV Community

Cover image for 3D Christmas Gift Box with HTML + CSS
Johnny Fekete
Johnny Fekete

Posted on

3D Christmas Gift Box with HTML + CSS

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%
  );
}
Enter fullscreen mode Exit fullscreen mode

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%
  );
}
Enter fullscreen mode Exit fullscreen mode

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);
}
Enter fullscreen mode Exit fullscreen mode

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);
}
Enter fullscreen mode Exit fullscreen mode

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);
}
Enter fullscreen mode Exit fullscreen mode

Two ribbon ends

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?

Multiple ribbons

So that is exactly what I did!

A 40px wide ribbon requires 40 divs, each transposed with 1 extra pixel by the Z-axis.
Also, to have the borders with dark red, the first and last 3 divs 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>
Enter fullscreen mode Exit fullscreen mode

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);
}
Enter fullscreen mode Exit fullscreen mode

(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 divs 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)
  }
}
Enter fullscreen mode Exit fullscreen mode

(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;
}
Enter fullscreen mode Exit fullscreen mode

The CSS Christmas Calendar

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)