Try it!







Note: No JavaScript is used on this page, only pure CSS.

transform: matrix3d(
, , , ,
, , , ,
, , , ,
, , ,
);

...welcome to the desert of the real

What?

CSS is candy: unwrap it if you want to get to the good stuff. I would say "figure out how it works under the hood," but we generally don't eat engines (and I do eat candy), and for me, eating is more fun than mechanical repair. If you want to fully enjoy 3D in CSS, you can use properties such as rotate() and translate() which are each a separate matrix under the hood — and we'll walk through how to do that and show examples — but these are wrapped candies...still tasty, but something's not quite right. Eventually, you're going to want to unwrap matrix3d() itself, because it allows you to do stuff that is not possible with these simpler tools, such as simultaneous tranformations and translations in several dimensions applied as a whole instead of separately and sequentially based on which property you list first (that is, you can do all your work in a single matrix instead of two or more).

Wait. Backup. What? If that last part doesn't make any sense, it may be because there is not a lot of information out there for people who are trying to learn 3D in CSS. Matrix3d() itself is crazy confusing even before we address how it is different than other ways of doing 3D. It might be confusing, but we don't want to eat the wrapper. We also probably want to avoid a trial and error approach to this unwrapping. Before we get to matrix3d(), though, we need to make sure we can do simplified 3D first, and then we'll move on to the good stuff inside the matrix.

Baby Steps

There are a couple of baby steps we'll have to take every time we set out to do 3D in CSS, no matter what. First: perspective. Second, transforms. Third, nesting. Fourth, flattening. That last one we don't want to happen (flattening), so that step is going to consist of a list of thou shalt not's, along with why thou shalt not. But first, the first...

1. Perspective

In order to apply 3D to any element in html, we are first going to have to apply a perspective to an ascendent. A child is a descendent, and a parent is an ascendent. A grandchild is a descendent, a grandparent an ascendent. Take the following html:

<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>3D CSS</title>
</head>
<body>
  <div class="platform">
    <div class="matrix3D"></div>
  </div>
</body>
</html>

If we are trying to apply 3D to the matrix3D element, we need to apply perspective to its immediate parent (the platform element), or to the body, or to the html element (all of which are ascendents). It is often best to use an element set aside for just this purpose (we'll see why in just a moment), and I've created the platform element here for that job. I always call it platform in all of my work so that I know what it means in my code (many use a div with a class of "scene"). In this case, our CSS might be something like the following.

.platform {
  perspective: 1000px;
}

We could leave it at that and move on, but I often like to make sure the vanishing point is centered vertically and horizontally in the layout. To do this, I put the platform on the ultimate diet (a kind of death therapy) to make it heightless and widthless, absolutely positioned in the center. This is why we said above that it is a good idea to set your perspective on an element used just for this purpose, because these additional changes make it mostly useless for anything else (it now lacks width and height and is positioned in the middle of the layout). Additionally, we can set the perspective-origin to be sure we are truly centering the perspective for descendents within this element (in case we want width and height on this one). We have now centered our invisible self, and are ready for the downward dog... (Huh?)

.platform {
  perspective: 1000px;
  perspective-origin: center center;
  width: 0;
  height: 0;
  position: absolute;
  left: 50%;
  top: 50%;
}

2. Transforms

Now we can finally do some fun stuff to our matrix3D element and retain 3D-ish-ness (is that a word?). Without the perspective above, we'd be stuck in flatland (which is a good book, but we are looking to transcend it...maybe to sphereland). Because we have already defined a perspective, any rotation or translation (movement back and forth) can now be 3D (so long as we preserve-3d). So let's apply a transformation in 3 dimensions:

.matrix3D {
  position: absolute;
  transform: rotateX(90deg);
}
.platform *, .platform *:before, .platform *:after {
  transform-style: preserve-3d;
  transform-origin: center center;
}

The second rule above should be placed after all other transforms in the CSS to overwrite the general transform rules with a preservation of the 3D and centering. This allows us not to have to specify this every time we want something to be 3D: now we can just transform any descendent of the platform (including pseudo-elements like :before or :after) and we are working in 3 dimensions instead of 2.

The transform above applies a rotation along the x-axis of 90 degrees. What in the world does that mean? The x-axis is horizontal, and could be imagined as a straight line from left to right. If we use that line as the rotation center, we end up moving the top and bottom and the center remains stationary. Like horizontal blinds that are adjusted to let the light in, they rotate one way or the other around an invisible horizontal line.

3. Nesting

Now, every element we nest inside of the matrix3D html element is potentially 3D as well (if we apply a position of relative, absolute, or fixed to it), because we applied the general transform-style and transform-origin rules above in step 2. Any transforms we apply to the new inner element will start at the new position of the ascendent, so that any child of matrix3D above will start out rotated along the x-axis 90 degrees: if we don't want the descdent (named "kiddo" below) rotated like that, we'll have to add a rule in the css to the descendent transform: rotateX(-90deg);

<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>3D CSS</title>
<style>
.platform {
  perspective: 1000px;
  perspective-origin: center center;
  width: 0;
  height: 0;
  position: absolute;
  left: 50%;
  top: 50%;
}
.matrix3D {
  position: absolute;
  transform: rotateX(90deg);
}
.kiddo {
  position: absolute;
  transform: rotateX(-90deg);
}
.platform *, .platform  *:before, .platform *:after {
  transform-style: preserve-3d;
  transform-origin: center center;
}
</style>
</head>
<body>

  <div class="platform">
    <div class="matrix3D">
      <div class="kiddo"></div>
    </div>
  </div>

</body>
</html>

As it is, the code above will not look like anything: we've got no background colors, no content (such as text or images), and no specified widths or heights for the matrix3D or kiddo elements. Let's add some some styles and animation so we can actually see what we're working with:

<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>3D CSS</title>
<style>
html, body {
  margin: 0;
  padding: 0;
  height: 100vh;
  background-color: #333;
}
.platform {
  perspective: 1000px;
  perspective-origin: center;
  width: 0;
  height: 0;
  position: absolute;
  left: 50%;
  top: 50%;
}
.matrix3D {
  position: absolute;
  transform: rotateX(90deg);
  background: rgba(0, 0, 0, 0.6);
  width: 200px;
  height: 200px;
  top: -100px;
  left: -100px;
  animation: spinMatrix 7s linear infinite;
}
.kiddo {
  position: absolute;
  transform: rotateX(-90deg) rotateY(45deg);
  background: rgba(0, 255, 120, 0.6);
  width: 200px;
  height: 200px;
}
@keyframes spinMatrix {
  0% { transform: rotateX(0deg); }
  100% { transform: rotateX(-360deg); }
}
.platform *, .platform  *:before, .platform *:after {
  transform-style: preserve-3d;
  transform-origin: center center;
}
</style>
</head>
<body>

  <div class="platform">
    <div class="matrix3D">
      <div class="kiddo"></div>
    </div>
  </div>

</body>
</html>

Note that in the above example, we kept the general transform-style and transform-origin rule as the last in the CSS to make sure they are applied. I've also added a rotateY(45deg) to the transform rule for the kiddo element to show how these transformations add up (rotating on both x and y axes). And with semi-transparency (ala the alpha channel in rgba), we can see through each element to the parts behind. At this point, we might be tempted to try to add CSS dropshadows or filters like blur to our elements to get things to the next level. Let's try:



Whoops!...broke the 3D!...choking on candy!...can't breathe!...mpferflgggg...



Sorry. Passed out on my keyboard there. Keep reading for a CSS Heimlich Maneuver.

4. Flattening (Thou Shalt Not)

Here is where the good news ends, and people start telling you what you can and cannot do. To avoid flattening your elements (i.e., removing their 3D-ish-ness):

  1. Thou shalt not add a CSS filter to an element that is 3D and has descendents that you still want to be 3D.
  2. Thou shalt not add drop shadows and fancy stuff to an element with a 3D descendent.
  3. Thou shalt not set overflow to hidden.
  4. Thou shalt not clip-path.

The reason to avoid the above is because even if you set your elements to transform-style: preserve-3d, the browser will reset them to transform-style: flat — against your direct orders! What a rebel! Well, I say "Fight back!" You can return in kind and get that browser to follow your every whim (almost) by applying these effects to the lowest nodes (lowest descendent elements) of your 3D objects, or you can also do the 3D to an invisible skeleton, that you build your backgrounds, sizes, effects, etc. onto as skin (you can use the pseudo-elements ::before and ::after for this). So use your 3D on invisible stuff, and your visible stuff on lowest children and pseudo-skins.

Let's say you want a wall with a door in it, cut out with clipping-paths: we can set the height and width of the wall and position it on a "skeleton" wall element, let's call it #northWall, and then we can add a pseudo-element to the wall, and make it 100% height and width, and clip-path the door out of a background color applied to the pseudo-element.

<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>3D CSS</title>
<style>
html, body {
  margin: 0;
  padding: 0;
  height: 100vh;
  background-color: #333;
}
.platform {
  perspective: 1000px;
  perspective-origin: center;
  width: 0;
  height: 0;
  position: absolute;
  left: 50%;
  top: 50%;
}
.matrix3D {
  position: absolute;
  transform: rotateX(90deg);
  background: rgba(0, 0, 0, 0.6);
  width: 200px;
  height: 200px;
  top: -100px;
  left: -100px;
  animation: spinMatrix 7s linear infinite;
}
.platform *, .platform  *:before, .platform *:after {
  transform-style: preserve-3d;
  transform-origin: center center;
}
</style>
</head>
<body>

  <div class="platform">
    <div class="matrix3D">

    </div>
  </div>

</body>
</html>

Enter the Matrix3D()

Matrix3d() is a 4x4 grid of numbers: a matrix, rows and columns. If we are going to play with the matrix, we'll need to understand what a matrix is, how to do mathy stuff with it, and what each row and column means in matrix3d(). As we saw in the example at the top of the page, the last row provides the translations to apply to x, y, z by pixel values, as well as the scale with 100% written as 1, half as 2, and 200% as 0.5. That much seems pretty straightforward, but we'll deal with it all later. For now, lets look at a regular box in CSS matrix3d():

	1, 0, 0, 0,
	0, 1, 0, 0,
	0, 0, 1, 0,
	0, 0, 0, 1

What does this mean? Why is it so cryptic, and why are we using 0's and 1's? (Are we talking binary — Yes? No?) A matrix of this form, 4 x 4 with mostly 0s and then 1s in a diagonal from top left to bottom right, is called an Identity Matrix, the matrix equivalent of the number "1". That is, if we multiply another matrix by this one, we'll end up with the same thing (like when we multiply a number by 1). And multiplying matrixes is how we do stuff in 3D, so let's do the math.

Mathy Cheat Sheets

(rad = radians; 6.28rad in 360deg, 1 rad = 57.3deg)

x y z W
X scaleX(*), skewY(rad), 0, 0,
Y skewX(rad), scaleY(*), 0, 0,
Z 0, 0, scaleZ(*), 0,
T translateX(px), translateY(px), translateZ(px), -translateZ(ratio)

RotateX

x y z W
X 1, 0, 0, 0,
Y 0, cos(rad), sin(rad), 0,
Z 0, -sin(rad), cos(rad), 0,
T 0, 0, 0, 1

RotateY

x y z W
X cos(rad), 0, sin(rad), 0,
Y 0, 1, 0, 0,
Z -sin(rad), 0, cos(rad), 0,
T 0, 0, 0, 1

RotateZ

x y z W
X cos(rad), sin(rad), 0, 0,
Y -sin(rad), cos(rad), 0, 0,
Z 0, 0, 1, 0,
T 0, 0, 0, 1