What exactly is perspective in CSS 3D transforms?
Clarify all your confusion regarding the perspective() transform function and the perspective property in CSS.
3D transforms in CSS enable you to achieve some astoundingly awesome 3D effects using nothing else but a couple of transform functions (yeah, no JavaScript). But before using these functions, it's imperative for you to first understand the idea of perspective.
In this article, I'll discuss with you what exactly is perspective in general, and how the perspective() transform function and the perspective property in CSS work. Without further ado, let's begin.
What is perspective?
If you spend a few minutes in looking up the definition of the term perspective, you'll find one thing common — it refers to the representation of a 3D object on a 2D surface (e.g. a computer screen).
Here's what Wikipedia has to say about perspective:
Perspective is an approximate representation, generally on a flat surface, of an object as it is seen by the eye. Perspective is useful for representing a three-dimensional scene in a two-dimensional medium, like paper.
So, let's first settle on the fact that perspective is to project a 3D object on a 2D surface in a way that it virtually gives the impression of being present in a 3D space. Perspective basically adds depth to objects in a 2D setting.
Taking this idea further, perspective is to project an object onto a drawing surface as it's viewed from a particular position. In this discussion, I'll refer to this position as the perspective view point. There are a multitude of ways to visualize this, and I'll go with the simplest approach.
Imagine you have a plain square object (yellow box) on a drawing surface (light gray background), as follows:

If you move this square backwards into the z-axis — the axis running from the viewer's eye towards the screen and beyond — it should appear what? Smaller or bigger? Think on it for a while.
Well, it should appear smaller, as it's going away from you:

And the very fact that the square looks smaller to indicate that it has gone (virtually) backwards is a consequence of the idea of perspective. Let's see how.
Shown below is the side view of the square along with the surface where it's drawn:

Let's also include a depiction of the eye on the diagram — the perspective view point, so to speak:

Imagine that light travels from the object and projects it onto the drawing surface (i.e. the screen) where the rays meet the drawing surface. In the case above, since the square is already on the surface, its projection is identical to its actual image. So far, so good.
Now, let's try moving the square backwards, as shown below:

If this square has to be now shown on the drawing surface, it'll obviously be smaller, as you learned before, but how exactly it becomes smaller is illustrated below:

Once again, if you imagine light traveling from the object to the drawing surface and finally to the user's eye, that gives you the projection of the object on the drawing surface — that is, it appears smaller. It's pretty simple.
A concept worth noting in this illustration, which I'll be referring to frequently in the discussion that follows, is the distance between the drawing surface (or the z=0 plane) and the view point. It's referred to as d by the CSS Transforms Module Level 2 spec.
Here's what d is in the illustration above:

The perspective() function
The perspective() transform function in CSS is used to establish perspective in the projection of an element on the screen.
It specifies the distance between the view point and the z=0 plane (which is merely the drawing surface). Does that ring a bell? Well, perspective() specifies the distance d as discussed above.
The larger the distance, the more elaborate the projection. Similarly, the smaller the distance, the more subtle the projection. (Remember this rule.)
Here's the syntax of perspective():
perspective(<length [0,∞]> | none)It can either be a <length> value in the range 0 to ∞ (infinity), for e.g. 30px, or it can be the keyword value none.
Let's see an example. But wait...
On its own, the application of perspective has NO effect on an element unless we transform the element via a 3D transform function. Going with the same square object example discussed above, let's translate it backwards, away from the viewer (on the z-axis) with the help of the translateZ() transform function.
rotateX() function.First things first, let's create a square:
<div class="square"></div>.square {
width: 100px;
height: 100px;
background-color: gold;
}Now, let's move it backwards by 500px after applying a perspective on the transformation:
.square {
width: 100px;
height: 100px;
background-color: gold;
transform: perspective(100px) translateZ(-500px);
}translateZ() moves the element towards you whereas a negative value, as given above, moves it away from you.For now, I've used a fairly small value for the distance d and that's why the translation is quite prominent. If you increase the distance, the transformation effect becomes less pronounced.
This makes perfect sense because if you're viewing an object from a long distance (i.e. a large value of d), any movements to the object won't be as noticeable as when you're much closer to the object.
In the following code, the distance is increased from 100px to 1000px and that results in a less pronounced translation:
.square {
width: 100px;
height: 100px;
background-color: gold;
transform: perspective(1000px) translateZ(-500px);
}The order of perspective() matters!
A crucial thing to note here is that the order of the perspective() function matters. If perspective() comes after the translateZ() function in this example, for instance, it would have NO effect.
Let me demonstrate this:
.square {
/* Order reversed; now perspective() comes later. */
transform: translateZ(-500px) perspective(100px);
}Notice the output — it's the exact same as without the transformation. The reason isn't because the transform property isn't being applied but rather because translateZ() doesn't have any perspective to produce a visible translation effect.
And why is that so? Simply because perspective() comes after the translateZ() function.
perspective() is reached, the translation has already been done (which didn't produce any effect).Therefore, whenever working with perspective(), double-check that you're not calling it at the very end!
The perspective property
In addition to the perspective() function, CSS also offers you a perspective property. The question is: What's the point of this property when there already is a function?
Well to answer this question, perspective is different from perspective() in a subtle way. That is, where perspective() applies perspective to the element on which it's set, the perspective property applies perspective to all the children of the given element, excluding the element itself.
Technically speaking, perspective is more powerful than the perspective() function. How?
- It creates a single 3D space that is shared by multiple elements.
- It can be used alongside the
perspective-originproperty to further improvise the positioning of the view point of a perspective projection on the xy plane.
More or less, perspective could be thought of as manually going over each and every child of the element on which it's applied and then calling the perspective() function on it.
perspective does NOT apply perspective to the element on which it is set. I'm stressing on it so much because it's easy enough to forget this.The syntax of the perspective property is akin to the function:
perspective: <length [0,∞]> | noneLet's consider some examples.
First, let's replicate the same .square example as we saw above with the perspective() function, this time using the perspective property:
body {
perspective: 100px;
}Since perspective acts on children only, it has been set on the parent of .square — that is, the <body> element. Thus, it applies to the children of <body>.
Here's the output produced so far:
Obviously, nothing happens since there isn't any transformation yet. Let's bring that now:
body {
perspective: 100px;
}
.square {
transform: translateZ(-500px);
}Voila! There's the perspective magic again.
However, notice how the effect produced by the translation now is different from what we had before. This is because the positioning of the perspective view point is different in this example.
When we use perspective(), the view point is positioned right at the center of the underlying element. Similarly, when we use perspective, the view point is positioned yet again at the center of the underlying element, but as we learned before, this time the perspective is not applied to the element itself, but is rather applied to its children.
This simply means that, for the children, the perspective view point might NOT be at their own center positions.
Henceforth, in the example above, the perspective view point for the square is not at its own center but rather at the center of the <body> element. And with this new perspective view point, when the translation occurs, i.e. when the square is moved backwards, the projection becomes smaller (as before) and moves rightwards towards the perspective view point.
Let's see what happens if we shift perspective from <body> to .square:
.square {
perspective: 100px; /* This has no effect */
transform: translateZ(-500px);
}See? All the 3D drama goes away by virtue of the very fact that perspective has now been set on .square directly and, therefore, doesn't apply to the element itself (but rather its children).
Shared 3D space
The perspective property creates a 3D space in a given element which is effectively shared by all of its children. This means that they all have a common perspective view point.
In effect, this means that if we have a collection of multiple elements inside a container that has perspective set on it, and those elements all have the same transformation applied to them, each element would produce a different transformation.
perspective() function, however, this isn't the case; each element, instead, has its own perspective view point which is at its center.Consider the following code, where we have a .container element with multiple .squares.
<div class="container">
<div class="square"></div>
<div class="square"></div>
<div class="square"></div>
</div>.container {
background-color: #f1f1f1;
}
.square {
width: 100px;
height: 100px;
background-color: gold;
display: inline-block;
}Now let's give each .square the same transformation, and also set perspective on the container:
.container {
perspective: 100px;
}
.square {
transform: translateZ(-500px);
}Now, even though all the squares have the exact same transformation applied, the actual rendered effect is different for each one.
Speaking more precisely, the first square travels leftwards the most from its initial position, the second one travels a little less, and the third one travels the least. And that's because the view point for each and every square's transformation is the same — the center of .container element.
Had it been as follows, where each .square element gets a direct perspective via the perspective() function, the scenario would've been different, as illustrated below:
.square {
transform: perspective(100px) translateZ(-500px);
}This time, the perspective view point for the transformation of each and every square is NOT a single, shared point. Rather, each element's perspective view point is at its own center.
In short, if you wish to get a shared 3D transformation effect on a list of sibling elements, then perspective (property) is what you need. Otherwise, if you don't want a combined 3D effect, you have to manually apply perspective() to each sibling element.