CSS Perspective

Chapter 41 28 mins

Learning outcomes:

  1. What is perspective
  2. The perspective() transform function
  3. The perspective property
  4. The perspective-origin property

Introduction

CSS 3D transformations put forward amazing new kinds of functions to perform different kinds of transformations to HTML elements. But before these functions could make for a visual effect, it's important for us to first understand the idea of perspective.

Essentially, some transformation functions only work on an element when it has a given perspective styling applied to it.

In this chapter, we shall discover how to work with perspective in CSS, and that how it's an integral prerequisite of applying 3D transformations to given elements.

We'll be going over the perspective() function provided to the transform property, and the perspective and perspective-origin properties to take more control over the application of perspective to an element.

What is perspective?

If we head over to find the definition of the word 'perspective' on Google, here's what we get:

The art of representing three-dimensional objects on a two-dimensional surface so as to give the right impression of their height, width, depth, and position in relation to each other.

Similarly, on Wikipedia, we have the following:

Linear perspective is an approximate representation, generally on a flat surface, of an image as it is seen by the eye. Perspective drawing is useful for representing a three-dimensional scene in a two-dimensional medium, like paper.

One thing to take from both these definitions is that perspective is all about projecting 3D objects onto a 2D surface so as to give that 2D surface the virtuality of a 3D space.

Perspective is the means of creating a 3D scene in a 2D medium.

Perspective basically adds depth to objects in 2D settings.

Likewise, without perspective, any discussion on 3D rendering on the web, which is merely also a 2D medium, is pointless, as you can clearly reason.

The official W3C spec, CSS Transforms Module Level 2, has a good preliminary discussion on the topic of perspective and projection. It's worth checking out.

Perspective simply is about the way an object is projected onto a drawing surface as it's viewed from a particular position. In this discussion, we'll refer to this position as the perspective view point.

There are a multitude of ways to visualize this, and we'll go with the simplest approach.

Imagine we have a plain square object, on a drawing surface (shown with a light grey background), as follows:

A square object
A square object

If we 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 us:

A square object translated backwards in the z-axis.
A square object translated backwards in the z-axis.

And the very fact that we make the square smaller to indicate that it has gone (virtually) backwards is an outcome 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:

Side view of the square object
Side view of the square object

Let's also include a depiction of the eye on the diagram, referred to as the 'view point':

Side view of the square object, with the view point
Side view of the square object, with the view point

Imagine that light travels from the object and projects it onto the drawing surface where the rays meet the drawing surface.

In the case above, since the square is already on the screen, it's projection is identical to its actual image itself. So far, so good.

Now, let's try to move the square away from the viewer, as shown below:

The square object moved backwards
The square object moved backwards

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

The square object moved backwards
The square object moved backwards

Once again, if we imagine light travelling from the object to the user's eye and to the drawing surface, where the rays intersect with the drawing surface, that's our projection of the object.

Simple.

There is concept in the illustration above worth noting, as we'll be using it in the following discussion.

That is the distance between the drawing surface 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:

d - the distance between the view point and the drawing surface (or the z=0 plane)
d - the distance between the view point and the drawing surface (or the z=0 plane)

As we shall learn later on in this chapter, the perspective() function and the perspective property are both used to specify this distance.

Demonstrating perspective using 3D objects

Even though we demonstrated perspective here using a 2D object, we could do so using a 3D object as well. But that would obviously require a more intricate illustration and also a whole virtual 3D space modeled to be able to visualize the effect of perspective in that illustration clearly.

So far the sake of brevity, we have omitted including such an example. The concept is nonetheless still the same.

And to add to this fact, individual elements on webpages are all 2D objects; likewise, this argument becomes even more practical.

The perspective() function

The perspective() transform function is used to establish perspective in the projection of an object on the screen.

perspective() specifies the distance between the view point and the z=0 plane, which is merely the drawing surface.

It essentiall specifies the d that we showcased in the illustration above.

The larger the distance, the more elaborate the projection. Similarly, the smaller the distance, the more subtle the projection. (Remember this.)

Here's the syntax of the perspective() function:

perspective(<length [0,∞]> | none)

It can either have a <length> value in the range 0 to ∞ (infinity), for e.g. 30px, or 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 transformation function.

Going with the square object example we discussed in the previous section, let's translate it backwards, away from the viewer. This translation will occur on the z-axis which runs from the viewer to the screen and beyond.

The application of perspective will result in the square shrinking in size with the translation — without it, the translation won't have any visual effect whatsoever.

In the upcoming chapters, we shall look at other ways to witness the effect of perspective on an element, such as via rotation using the rotateX() function.

Anyways, coming back to the discussion, let's see perspective() in action, along with the translateZ() function, on a square object.

First, let's create a square:

<div></div>
div {
   width: 100px;
   height: 100px;
   background-color: blue;
}

Now, let's apply perspective() along with translateZ() on it:

div {
   width: 100px;
   height: 100px;
   background-color: blue;
transform: perspective(1000px) translateZ(-500px); }

The reason why the value provided to perspective() is fairly huge has been explained before.

That is, if we have a small value (which represents the distance between the view point and the z=0 plane), a small transformation would produce a huge effect. Having a huge distance results in a transformation having...you guessed it...a small effect.

This is illustrated as follows:

div {
   width: 100px;
   height: 100px;
   background-color: blue;
transform: perspective(100px) translateZ(-500px); }

We reduced the perspective distance from 1000px to 100px, which resulted in a massive size reduction of the square.

In translations, the effect of the value provided to perspective() won't produce that much of a massive difference; for that you'll need to wait till the the next-to-next chapter, CSS 3D Transformations — Rotations, where we cover 3D rotations.

Another crucial thing to note here is that the order of the perspective() function, in the transform property, matters.

If perspective() comes after the translateZ() function, it would have NO effect.

Let's try doing this for real:

div {
   width: 100px;
   height: 100px;
   background-color: blue;
transform: translateZ(-500px) perspective(1000px); /* Order reversed */ }

See? The square remains as it is even though the transform property does exist on it.

That's because perspective() comes after the translateZ() function.

The perspective property

The perspective property is the same as the perspective() function in what it does. However, there sure is some difference between both these utilities.

The perspective() function, as we saw above, applies perspective to a given element, the one on which it's applied. In contrast,

The perspective property applies perspective to all the children of a given element, excluding the element itself.

Technically speaking, perspective is more powerful than the perspective() function. How?

  • Because it creates a single 3D space that is shared by all the children of an element.
  • Moreover, as we shall learn later on below, perspective can be used alongside the perspective-origin property to further improvise the positioning of the view point of a 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.

Once again, remember that perspective does NOT apply perspective to the element on which it is set.

Here's the syntax of the perspective property, which is, needless to say, akin to the perspective() function:

perspective: <length [0,∞]>

On older browsers, perspective is behind vendor prefixing.

On Webkit-based browsers, it's called, as you can guess, -webkit-perspective. On Firefox, it's called -mox-perspective. There's even a non-standard property for IE; it's called -ms-perspective.

Painful compatibility issues.

The other important points worth noting about the perspective property are as follows:

  • perspective creates a new stacking context.
  • perspective makes the underlying element a containing block for descendant elements, for e.g. ones with position: fixed. In this behavior, therefore, it is analogous to the application of transform to that element.

Let's consider some examples of the perspective property.

First, we'll try to replicate the square example above, albeit this time using the perspective property instead of the perspective() function on the square:

<div></div>
body {
   perspective: 1000px;
}

div {
   width: 100px;
   height: 100px;
   background-color: blue;
}

Since perspective acts on children only, we've set it on the parent of the square <div>, which in our case is the <body> element.

Here's the output produced by this:

Obviously, nothing happens since we haven't applied any transformations yet.

Let's do that now:

body {
   perspective: 1000px;
}

div {
   width: 100px;
   height: 100px;
   background-color: blue;
transform: translateZ(-500px); }

Voila! There's our 3D magic again.

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

Here's how:

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

So what do say, was this easy to understand?

Let's see what happens if we shift perspective from <body> to <div>:

body {
   perspective: 1000px;
}

div {
   width: 100px;
   height: 100px;
   background-color: blue;
perspective: 1000px; transform: translateZ(-500px); }

See? All the 3D drama goes away by virtue of the very fact that perspective applies to children elements only.

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 single perspective view point.

With the perspective() function, however, this isn't the case; each element, instead, has its own perspective view point (which is at its center).

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, each element would produce a different transformation.

Let's see what this means with the help of an example.

Consider the following code, where we have a <section> with multiple <div>s, each <div> being transformed, while the <section> being given a perspective:

<section>
   <div></div>
   <div></div>
   <div></div>
</section>
section {
   background-color: #f1f1f1;
   perspective: 1000px;
}

div {
   width: 100px;
   height: 100px;
   background-color: blue;
   display: inline-block;
}

See the output produced by this code:

As stated earlier, even though all the square <div> elements 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 is shared, being situated at the very center of the <section> element, thanks to the application of perspective on the <section> element.

Had it been as follows, where each <div> gets a direct perspective via the perspective() function, the scenario would've been different:

section {
   background-color: #f1f1f1;
}

div {
   width: 100px;
   height: 100px;
   background-color: blue;
   display: inline-block;
   transform: perspective(1000px) translateZ(-500px);
}

This time, the perspective view point for the transformation of each and every <div> is not a single point. Each element's perspective view point is, instead, at its own center.

To recap it, in comparison, the view point of each <div> element in the previous example is a single, shared point — the center of <section>.

In short, if we wish to get a combined 3D transformation effect on a list of sibling elements (with a common parent), then perspective is what we need.

Otherwise, if we don't want a combined 3D effect, we have to manually apply perspective() to each sibling element, or possibly go with another different approach.

The perspective-origin property

Now, let's talk about the equally important property, often used along with the perspective property — perspective-origin.

The perspective-origin property specifies the location of the perspective view point on the xy plane, which is simply the plane consisting of the x-axis and y-axis.

For example, if the perspective-origin of a square object is at its top-left corner, as follows,

Square object with perspective-origin situated at the top-left
Square object with perspective-origin situated at the top-left

here's how the corresponding perspective view point will look from the side:

Side view of square object with perspective-origin situated at the top-left
Side view of square object with perspective-origin situated at the top-left

See how perspective-origin influences the positioning of the view point — it is in-line with the perspective-origin.

To be precise, perspective-origin positions the view point in the x and y axes — positioning in the z-axis is already covered by the perspective property, as we saw above.

Syntactically, perspective-origin is identical to transform-origin:

perspective-origin: x-position y-position |
                    x-position

It can either have two values, representing the x and y offsets of the view point, respectively, or just one value, representing only the x offset of the viewpoint.

As a matter of fact, in the case of two values, depending on the keywords used, the first value could even represent the y offset while the second one represents the x offset.

Let's consider some examples demonstrating perspective-origin.

First, let's see a simple case using the default value of perspective-origin.

In the following code, we lay out 3 square <div>s inside a <section> element, as before, but this time the <section> has been given display: inline-block so as to fit the squares:

<section>
   <div></div>
   <div></div>
   <div></div>
</section>
section {
   background-color: #eee;
   display: inline-block;
   padding: 20px;
}

div {
   width: 100px;
   height: 100px;
   background-color: blue;
   display: inline-block;
}

By default, when an element is given the perspective property, its perspective-origin gets situated at its very center. (We already saw this in the last example in the previous section above, with the <section> element.)

Hence, in the code above, if we apply perspective to <section> and then translate the <div>s backwards, they all will seem to approach the given perspective-origin point, which is the center.

Let's see this for real:

section {
   background-color: #eee;
   display: inline-block;
   padding: 20px;
perspective: 1000px; } div { width: 100px; height: 100px; background-color: blue; display: inline-block;
transform: translateZ(-500px); }

As is evident in the output shown, the centre of focus is the middle of the <section> element — everything seems to be moving closer to it.

Similarly, if we translate the <div> towards the viewer, with a positive z value, they all will grow in size, but still they'll seem to originate from the center of <section>:

div {
   width: 100px;
   height: 100px;
   background-color: blue;
   display: inline-block;
transform: translateZ(150px); }

Now, let's change this centre of focus via the perspective-origin property.

For starting out, we'll update perspective-origin to the top-left corner of the <section> element; the rest of the code is the same:

section {
   background-color: #eee;
   display: inline-block;
   padding: 20px;
   perspective: 1000px;
perspective-origin: top left } div { width: 100px; height: 100px; background-color: blue; display: inline-block; transform: translateZ(-500px); }

And let's witness the output with this minor change:

Indeed, there's a visible difference! Now, the transformed elements seem to approach the top-left corner of <section>.

If we change gears and translate the <div>s towards the viewer again, here's what we'll get:

div {
   width: 100px;
   height: 100px;
   background-color: blue;
   display: inline-block;
   transform: translateZ(150px);
}

As you can see, the transformed elements again seem to originate from the top-left corner of <section>, and rightly so — that's our new perspective-origin.

The farther the view point, the less pronounced the transformation

Notice one other pretty important thing in both the examples above: the farther away an element is from the perspective-origin point in the xy plane, the less pronounced is its transformation.

This lines up perfectly with our previous statement regarding the distance between the perspective view point and the drawing surface (the z=0 plane). That is, the larger the distance, the less pronounced the transformation.

In short, the larger the distance of the view point from an element — which is affected by perspective in the z-axis, and by perspective-origin in the x and y axes — the smaller is its transformation effect.

This is a vital rule of thumb to keep in mind.

Moving on, just like perspective is vendor-prefixed for compatibility with older browsers, so is perspective-origin.

That is, on Webkit-based browsers, we use it as -webkit-perspective-origin; on Firefox, as -moz-perspective-origin; and on IE, as -ms-perspective-origin.

"I created Codeguage to save you from falling into the same learning conundrums that I fell into."

— Bilal Adnan, Founder of Codeguage