Go back to home page of Unsolicited Advice from Tiffany B. Brown

Why doesn't my scale3d() / scaleZ() transform work?

One of the trickier parts of 3D transforms is understanding how the scaleZ() and scale3d() functions work. When applied to an element, they will often appear to have no effect. But then at other times, or in other combinations, they will. Confusing right?

So what's going on? Well, it has to do with transformation matrices, these bits of mathematical goodness that allow us to change where points are plotted within a coordinate system — in this case, your document viewport.

In May of 2013, I wrote an article for Dev.Opera about transform matrices. If you haven't read it yet, please do. It lays the groundwork for what I'm about to discuss here.

Coordinate systems

If you've ever taken algebra or geometry, you've probably learned about coordinate systems. You probably remember that the x-axis is horizontal, the y-axis is vertical, and the z-axis is perpendicular to them both.

If we move the coordinate system to our document viewport, our x axis goes from left-to-right on screen and the y axis goes from the top to the bottom. The z-axis sits perpendicular to the screen and viewer, but the screen itself is the 0 point of the z axis. Hold on to that point. We'll come back to it.

Coordinate vectors and matrix multiplication

Let's imagine that you have an object whose top left corner sits 100 pixels from the left edge of the document viewport, and 200 pixels from its top. This means its coordinates are (100,200,0) in a three dimensional system. It exists on the same plane as the screen, which as mentioned above, has a z-coordinate of zero.

Now let's say we wanted to scale our object along the z axis by a factor of three — either scale3d(1,1,3) or scaleZ(3). That, by the way, is equivalent to matrix3d(1,0,0,0,0,1,0,0,0,0,3,0,0,0,0,1);. We need to multiply our coordinate vector by our matrix as show in figure 1.

[1 0 0 0] [100] [100] [0 1 0 0] • [200] = [200] [0 0 3 0] [ 0] [ 0] [0 0 0 1] [ 1] [ 1]
Figure 1: Multiplying a 3d scaling matrix by a coordinate vector.

Notice anything weird about the product? It looks just like our coordinate vector. Because our z-coordinate value hasn't changed, our transform doesn't appear to be rendered on screen.

In other words, in order for a scaling transform along the z-axis to work, the matrix product of the transform must result in a z coordinate with a non-zero value.

Let's run the math on an example using transform: rotateY(20deg) scale3d(1,1,10). First we need to multiply our rotation matrix by our scaling matrix, as shown in figure 2.

    [1 0  0 0]   [ 0.9397 0 -0.3420 0]   [0.9397 0 -0.3420 0]
    [0 1  0 0] • [ 0      1  0      0] = [0      1  0      0]
    [0 0 10 0]   [-0.3420 0  0.9397 0]   [3.420  0  9.397  0]
    [0 0  0 1]   [ 0      0  0      1]   [0      0  0      1]
Figure 2: Multiplying a 3d scaling matrix transform by a rotation along the Y axis.

This gives us a compound transform that we can then multiply by our coordinate vector from above ([100,200,0,1]) as we've done in figure 3.

    [0.9397 0 -0.3420 0]     [100]   [ 94]
    [0      1  0      0]  •  [200] = [200]
    [3.420  0  9.397  0]     [  0]   [ 34]
    [0      0  0      1]     [  1]   [  1]
Figure 3: Multiplying the matrix for a three-dimensional rotation and scaling transform by a coordinate vector.

Our product, [94,200,34,1] translates to a three dimensional coordinates of (94,200,34), and boom, our transform works as expected.