3D Cubes for AliceJS, Part 1

Introduction

I get a lot of questions regarding how a new AliceJS effect is born. Most people are interested particularly about how you make code reusable. Many know how to write an app, but few think of how to write a reusable library or framework. I have seen many examples about how to set-up a 3D Cube, but pretty much all of them take a pure content approach: you code your content, your settings, your CSS all in one big ball of code. In this 3-part article series, I’ll explore how you generalize the concept of a 3D Cube and how you approach it from an API point of view. For simplicity, this series will specify styles for a WebKit browser only.

In this installment we will take the first step to understand the DOM and CSS requirements to create a Cube. We’ll learn about setting up a container, positioning elements in 3D, and a few gotchas to actually see something cool.

First steps

A 3D Cube is something that scares a lot of people, but fundamentally, it’s really simple. You need six DOM elements (<SPAN>’s or <DIV>’s for example) that you then style with CSS 3D properties to set-up in space. There are a few tricks that you need to understand, to get started.

<div id="Container1" class="Container3D">
  <div id="CUBE1" class="Cube">
    <span id="CUBE1a"> </span>
    <span id="CUBE1b"> </span>
    <span id="CUBE1c"> </span>
    <span id="CUBE1d"> </span>
    <span id="CUBE1e"> </span>
    <span id="CUBE1f"> </span>
  </div>
</div>

The way CSS 3D works, you need to set-up your DOM first, and you need a few layers. First, you have to set-up a scene (here the container), then your object (a cube), then the pieces of that object (the cube’s 6 faces). Next, you have to set-it all up in CSS. Let’s start with the container.

.Container3D {
      -webkit-perspective: 1000px;
}

The best way I have come to think of the container is to think of my camera in a 3D space: I can set up how far on the Z-axis the camera is from the origin of the coordinate system (0,0,0). The -webkit-perspective property set in the code above positions the camera at (0,0,1000) and that will affect how acute the perspective is. Do note that this is an approximation as the camera is effectively still at the same distance: setting the value to 500px will not make the object look twice as big (because it’s twice as close), but make the perspective more skewed.

.Cube {
      width: 100%;
      height: 100%;
      position: absolute;
      -webkit-transform-style: preserve-3d;
}

Inside the container is our cube. This is the DOM node which we will rotate around to show all the cube’s faces. Setting the width and height properties to 100% ensures that the cube expands to take all the space it needs. You see, all the pieces are ultimately independent, and sizing the cube that way ensures it all fits. Next, you want to fix the cube within the container, and so, position is set to absolute. Then, because the Cube is what we’ll animate in a 3D space, we want to make sure that all that beautiful perspective we set-up in the container looks good. That’s what preserve-3d does.

.Cube span {
      position: absolute;
      width: 198px;
      height: 198px;
      border: 2px solid black;
      background-color: #EEEEEE;
}

Finally, we want to style the individual faces. The position property is the most important here as it will cause all the <SPAN> elements to be laid out on top of one another. They all start from the same place in space, which will be a lot simpler to rearrange into a cube later. All other properties are really just general styling so we can see the faces of the cube.

Launch the file toto1a.html (see the bottom of this article for resources) in a CSS-3D capable WebKit browser (such as BlackBerry 7, PlayBook 1 or 2, or the new BlackBerry 10, but Chrome or Safari would also do). Disappointed? Doesn’t look impressive? Read on.

Putting it in 3D

The main issue is that all the faces are basically on top of each other. All you see is just the last one in the DOM. We have to do some rearranging. So think about it… you have 6 cards laid on top of one another on a table. Visualize the movements you have to make to re-arrange them so that they now fit as a cube. We make the assumption that all faces have the same dimensions. That’s a pretty safe one since we are trying to do a Cube here. In the styling previously, we set each face to be 200px in width and height.

  • So, for the first face, we need to simply bring forward (Z-axis) by half the size, or 100px
  • The second face (going horizontally to the right) needs to be rotated 90 degrees to the right on the Y-axis, and then moved 100px.
  • The third face needs to be flipped 180 degrees against the Y-axis and then pushed backward (Z-axis) by 100px
  • Etc… Take a deck of cards, and do it for yourself so you understand the geometry you are building.

So, what does it look in CSS?

#CUBE1a {
  -webkit-transform: rotate3d(0, 0, 0,   0deg) translate3d(0px, 0px, 100px);
}
#CUBE1b {
  -webkit-transform: rotate3d(0, 1, 0,  90deg) translate3d(0px, 0px, 100px);
}
#CUBE1c {
  -webkit-transform: rotate3d(0, 1, 0, 180deg) translate3d(0px, 0px, 100px);
}
#CUBE1d {
  -webkit-transform: rotate3d(0, 1, 0, -90deg) translate3d(0px, 0px, 100px);
}
#CUBE1e {
  -webkit-transform: rotate3d(1, 0, 0,  90deg) translate3d(0px, 0px, 100px);
}
#CUBE1f {
  -webkit-transform: rotate3d(1, 0, 0, -90deg) translate3d(0px, 0px, 100px);
}

What initially tricked me is that i thought the rotations would be for the object itself, and not the axis. When you combine transforms like this, a rotation essentially rotates the axis and origins, not the element in a fixed coordinate system. That’s why in all cases, we always go forward on the z-axis by 100px. You do not need to do +100px, or -100px, or change axis to go up or down, because the rotation always puts things in the right place. Run the toto1b.html file (see the bottom of this article for resources). Hmmm… Still disappointed?

It’s trivial to fix. If you look at the CSS for the faces, you’ll notice that we set-up a background color. Each face as a result is opaque. That’s easy to change with the opacity property. The Style definition for the faces is now:

.Cube span {
      position: absolute;
      width: 198px;
      height: 198px;
      border: 2px solid black;
      background-color: #EEEEEE;
      opacity: 0.6; /*** NEW ***/
}

You can load the toto1c.html file (see the bottom of this article for resources) and see the results.

OK, so we are getting somewhere… You can see the outline of the cube faces in the background. The Cube in and of itself still doesn’t look sexy. What can we do? Let’s rotate the cube a bit down and to the left. The key here is to understand that since all the pieces are all pre-arranged, the simplest thing is do not rotate those around, but rotate the cube itself. The updated style is then:

.Cube {
      width: 100%;
      height: 100%;
      position: absolute;
      -webkit-transform-style: preserve-3d;
      -webkit-transform: rotateX(-25deg) rotateY(25deg); /*** NEW ***/
}

And there we are. We now have a proper cube that looks good. Check out the toto1d.html file (see the bottom of this article for resources) for the final results.

Conclusion

So why not stop here? The key thing is that it’s not really reusable:

  • We have a lot of CSS to set-up with some weird unintuitive 3D coordinates (it took me a while to understand that a rotation affected the system of coordinates, and not the individual element)
  • The initial DOM structure for the cube is more complex than needed (the need for the top level container is not obvious for most developers), and we need to set up ids and classes etc…
  • The rotation down and left of 45 degrees is hard coded
  • We hard coded the cube’s size

The way I see it, there is simply way too much CSS and DOM that I have to set up to make this work. I love CSS, but i don’t like writing CSS. In the next installment, we’ll start simplifying things up with some JavaScript so that we have less DOM and CSS to write as an application developer. We’ll start tucking away under a framework API all the heavy lifting of putting a Cube together. See you then.

Resources

Toto1a.html

<!DOCTYPE html>
<html>
<head>
<title>Cube Tutorial</title>
</head>
<body>

<STYLE>
.Container3D {
      -webkit-perspective: 1000px;
}
.Cube {
      width: 100%;
      height: 100%;
      position: absolute;
      -webkit-transform-style: preserve-3d;
}

.Cube span {
      position: absolute;
      width: 198px;
      height: 198px;
      border: 2px solid black;
      background-color: #EEEEEE;
 }
</STYLE>

<BR><BR><BR><BR><BR><BR><BR><BR>
<center>
<div class="Container3D" id="Container1">
  <div class="Cube" id="CUBE1">
    <span id="CUBE1a"> </span>
    <span id="CUBE1b"> </span>
    <span id="CUBE1c"> </span>
    <span id="CUBE1d"> </span>
    <span id="CUBE1e"> </span>
    <span id="CUBE1f"> </span>
  </div>
</div>
</center>

</body>
</html>

Toto1b.html

<!DOCTYPE html>
<html>
<head>
<title>Cube Tutorial</title>
</head>
<body>

<STYLE>
.Container3D {
      -webkit-perspective: 1000px;
}
.Cube {
      width: 100%;
      height: 100%;
      position: absolute;
      -webkit-transform-style: preserve-3d;
}

.Cube span {
      position: absolute;
      width: 198px;
      height: 198px;
      border: 2px solid black;
      background-color: #EEEEEE;
}
#CUBE1a {
  -webkit-transform: rotate3d(0, 0, 0,   0deg) translate3d(0px, 0px, 100px);
}
#CUBE1b {
  -webkit-transform: rotate3d(0, 1, 0,  90deg) translate3d(0px, 0px, 100px);
}
#CUBE1c {
  -webkit-transform: rotate3d(0, 1, 0, 180deg) translate3d(0px, 0px, 100px);
}
#CUBE1d {
  -webkit-transform: rotate3d(0, 1, 0, -90deg) translate3d(0px, 0px, 100px);
}
#CUBE1e {
  -webkit-transform: rotate3d(1, 0, 0,  90deg) translate3d(0px, 0px, 100px);
}
#CUBE1f {
  -webkit-transform: rotate3d(1, 0, 0, -90deg) translate3d(0px, 0px, 100px);
}

</STYLE>

<BR><BR><BR><BR><BR><BR><BR><BR>
<center>
<div class="Container3D" id="Container1">
  <div class="Cube" id="CUBE1">
    <span id="CUBE1a"> </span>
    <span id="CUBE1b"> </span>
    <span id="CUBE1c"> </span>
    <span id="CUBE1d"> </span>
    <span id="CUBE1e"> </span>
    <span id="CUBE1f"> </span>
  </div>
</div>
</center>
<BR>
<BR>

</body>
</html>

Toto1c.html

<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
<title>Cube Tutorial</title>
</head>
<body>
<STYLE>
.Container3D {
      -webkit-perspective: 1000px;
}
.Cube {
      width: 100%;
      height: 100%;
      position: absolute;
      -webkit-transform-style: preserve-3d;
}

.Cube span {
      position: absolute;
      width: 198px;
      height: 198px;
      border: 2px solid black;
      background-color: #EEEEEE;
/*NEW*/ opacity: 0.6;
}
#CUBE1a {
  -webkit-transform: rotate3d(0, 0, 0,   0deg) translate3d(0px, 0px, 100px);
}
#CUBE1b {
  -webkit-transform: rotate3d(0, 1, 0,  90deg) translate3d(0px, 0px, 100px);
}
#CUBE1c {
  -webkit-transform: rotate3d(0, 1, 0, 180deg) translate3d(0px, 0px, 100px);
}
#CUBE1d {
  -webkit-transform: rotate3d(0, 1, 0, -90deg) translate3d(0px, 0px, 100px);
}
#CUBE1e {
  -webkit-transform: rotate3d(1, 0, 0,  90deg) translate3d(0px, 0px, 100px);
}
#CUBE1f {
  -webkit-transform: rotate3d(1, 0, 0, -90deg) translate3d(0px, 0px, 100px);
}

</STYLE>
<BR><BR><BR><BR><BR><BR><BR><BR>
<center>
<div class="Container3D" id="Container1">
  <div class="Cube" id="CUBE1">
    <span id="CUBE1a"> </span>
    <span id="CUBE1b"> </span>
    <span id="CUBE1c"> </span>
    <span id="CUBE1d"> </span>
    <span id="CUBE1e"> </span>
    <span id="CUBE1f"> </span>
  </div>
</div>
</center>
<BR>
<BR>

</body>
</html>

Toto1d.html

<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
<title>Cube Tutorial</title>
</head>
<body>
<STYLE>
.Container3D {
      -webkit-perspective: 1000px;
}
.Cube {
      width: 100%;
      height: 100%;
      position: absolute;
      -webkit-transform-style: preserve-3d;
/*NEW*/ -webkit-transform: rotateX(-25deg) rotateY(25deg);
}

.Cube span {
      position: absolute;
      width: 198px;
      height: 198px;
      border: 2px solid black;
      background-color: #EEEEEE;
      opacity: 0.6;
}
#CUBE1a {
  -webkit-transform: rotate3d(0, 0, 0,   0deg) translate3d(0px, 0px, 100px);
}
#CUBE1b {
  -webkit-transform: rotate3d(0, 1, 0,  90deg) translate3d(0px, 0px, 100px);
}
#CUBE1c {
  -webkit-transform: rotate3d(0, 1, 0, 180deg) translate3d(0px, 0px, 100px);
}
#CUBE1d {
  -webkit-transform: rotate3d(0, 1, 0, -90deg) translate3d(0px, 0px, 100px);
}
#CUBE1e {
  -webkit-transform: rotate3d(1, 0, 0,  90deg) translate3d(0px, 0px, 100px);
}
#CUBE1f {
  -webkit-transform: rotate3d(1, 0, 0, -90deg) translate3d(0px, 0px, 100px);
}

</STYLE>
<BR><BR><BR><BR><BR><BR><BR><BR>
<center>
<div class="Container3D" id="Container1">
  <div class="Cube" id="CUBE1">
    <span id="CUBE1a"> </span>
    <span id="CUBE1b"> </span>
    <span id="CUBE1c"> </span>
    <span id="CUBE1d"> </span>
    <span id="CUBE1e"> </span>
    <span id="CUBE1f"> </span>
  </div>
</div>
</center>
<BR>
<BR>

</body>
</html>
Advertisements

About ldhasson

Software guy, a film and music nut, blue hair, NYC. All opinions and ramblings are my own and do not necessarily reflect my employer's views.
This entry was posted in HTML5. Bookmark the permalink.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s