3D Christmas Gift Box with HTML + CSS
Tags: dohackathon, tutorial, css, html
Published: 2021-01-07 18:41:58

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](https://csschristmascalendar.com), where I posted a new CSS art every day until Christmas.

{% codepen https://codepen.io/twhite96/pen/NWRxmwG %}

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

{% codepen https://codepen.io/twhite96/pen/RwGKvJY %}

As you can see, the sides already have the ribbon on them.  
I achieved it with a linear gradient background:

```css
.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.

```css
.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`).

```css
.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.


```css
.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:  

{% codepen https://codepen.io/johnnyfekete/pen/XWjMEJR %}

## 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:

```css
.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);
}
```

![Two ribbon ends](https://dev-to-uploads.s3.amazonaws.com/i/1gd76qhgqd6x60azhvpy.png)

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](https://dev-to-uploads.s3.amazonaws.com/i/5wsgtpuihu12l26fjfo0.png)

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:

```html
<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:

```css
.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).

{% codepen https://codepen.io/johnnyfekete/pen/ZEpeoMw %}

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:

```css
@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:

```css
.gift-box {
  animation: rotation-3d 20s infinite linear;
}
```

## The CSS Christmas Calendar

![CSS Christmas Calendar](https://dev-to-uploads.s3.amazonaws.com/i/f5h0yx5efjpehlb1gd5l.jpg)

This rotating 3D Christmas gift is part of my Holiday project, the [CSS Christmas Calendar](https://csschristmascalendar.com). 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](https://codepen.io/collection/nZPerV) is available on [Codepen](https://codepen.io/johnnyfekete/full/eYdNvVL).

Check it if you want to see other Christmassy CSS magic!