This page is Ready to Use

Notice: The WebPlatform project, supported by various stewards between 2012 and 2015, has been discontinued. This site is now available on github.

Manipulating content with CSS3 transforms

By Mike Sierra

Summary

CSS transforms allow you to dynamically manipulate the space in which content elements appear. You can move them around on the screen, shrink or expand them, rotate them, or combine all these effects to produce complex movements. By themselves, transforms produce static visual effects, but you can easily combine them with CSS transitions and keyframe animations to produce vibrant animated interfaces. This tutorial first introduces simple two-dimensional transforms, then shows you how to extend transforms into three-dimensional space. It ends with step-by-step instructions on how to spin a cube in space.

These key points serve as reference:

The transform property

Transforms alter a block element’s coordinates in several ways so that they vary from where they would ordinarily appear. The transform CSS property specifies a handful of transformation functions that can be combined any way you wish: translate(), scale(), rotate(), and skew(). Here is how you specify a combination of most of the two-dimensional transforms discussed below, along with a view of how the effect renders relative to the element’s default position:

div.card {
   -moz-transform    : translate(50%, 10%) rotate(20deg) scale(0.75);
   -o-transform      : translate(50%, 10%) rotate(20deg) scale(0.75);
   -webkit-transform : translate(50%, 10%) rotate(20deg) scale(0.75);
   transform         : translate(50%, 10%) rotate(20deg) scale(0.75);
}

transform combo.png

(View live sample)

Transform properties were implemented recently enough that many browsers only support them with vendor prefixes such as -moz-, -o-, and -webkit- as shown above. Throughout this tutorial, CSS examples only show the un-prefixed property name, but for widest support you should apply all of them.

Functions are separated by spaces, but any additional arguments are separated by commas.

While you can specify any number of these functions, values can’t be inherited from other style sheets. So you can’t apply both of these CSS classes to an element at the same time and expect both effects to apply:

div.spin   { transform: rotate(20deg); }
div.tabled { transform: translate(100%) scale(0.5); }

Wherever you apply a transformation, any function left unspecified is assigned a default value, so these work out the same as the less verbose examples above:

div.spin { transform: rotate(20deg) translate(0,0), scale(1) skew(0,0) }
div.tabled { transform: rotate(0deg) translate(100%,0) scale(0.5) skew(0.0) }

You can also apply transforms directly to an object from JavaScript. Here’s an example in which tilting a mobile handset controls the tilt of a screen element, using the 3D transforms described below:

var gestural = document.querySelector('#gestural');
window.addListener('deviceorientation', orientationHandler);
orientationHandler = function(e) {
   gestural.style.transform = 'rotateX(' + (e.beta * -1) + 'deg) ' + 'rotateY(' + e.gamma + 'deg)';
   gestural.style.WebkitTransform = 'rotateX(' + (e.beta * -1) + 'deg) ' + 'rotateY(' + e.gamma + 'deg)';
}

Several different technologies such as SVG and the canvas element implement transforms, and are all conceptually similar despite slight differences in how they are implemented.

2D transform functions

The following isolates how each 2D function works. The translate() function simply moves an element around on the screen, much the same as when using top and left to position an element. This example moves the card over to the right more than it moves it downward:

transform: translate(50%, 10%);

transform translate.png

(View live sample)

The translate() function accepts up to two x and y values to move to the right and downward. These can specify any CSS measurement, including negative values to move left and upward. Percentages refer to the size of the transformed element. If you specify a single value, it’s interpreted as x and only moves the element horizontally. Otherwise you can specify separate translateX() and translateY() functions. Here’s another way to express the same translation as above:

transform: translateX(50%) translateY(10%);

The scale() function sizes an element in decimal terms relative to a default value of 1. An element whose scale is 2 doubles in size, while a scale of 0 vanishes into a point. A single scale value applies to the whole element, but setting two values lets you scale each axis independently, as you can also get with separate scaleX() and scaleY() functions. The following pairs offer different ways to produce the effects shown below:

/* first card */
transform: scale(0.75);
transform: scale(0.75, 0.75);
/* second card */
transform: scale(0.75, 1.25);
transform: scaleX(0.75) scaleY(1.25);

transform scale.png

(View live sample)

The rotate() function spins an element around its z axis. It accepts a degree (deg) or radian (rad) measurement. (Radians are equivalent to the number of degrees multiplied by π/180.) Radial measurements can wrap around, so the following values are equivalent:

transform: rotate(20deg);
transform: rotate(380deg);  /*   360  + 20 */
transform: rotate(-340deg); /* (-360) + 20 */

transform rotate.png

(View live sample)

The skew() function leans an element over, altering its corner angle relative to the default 90° and transforming the underlying rectangle into a parallelogram. It accepts up to two degree (deg) or radian (rad) measurements. The separate skewX() function tips the side edges of the element, while skewY() tips the top and bottom. Setting a single skew() value affects only the x axis.

transform: skewX(10deg);               /* 1st */
transform: skewY(-30deg);              /* 2nd */
transform: skew(10deg, -30deg);        /* 3rd */
transform: skewX(10deg) skewY(-30deg); /* 3rd, alternate syntax */

transform skew.png

(View live sample)

Skewing along both x and y makes the element appear to move into three-dimensional space, but the transformation actually occurs within a flat plane. Skip below for information about 3D transforms.

Note: Browsers internally represent all of the transformations described above as a single matrix expression, which for 2D transforms consist of six values. Using the web inspector feature, you can view any transformed element’s computed style for an example, and use the the alternative matrix() function to specify those values. Here is how the ace of spades in the example above is represented as a matrix value:

transform: matrix(1, -0.577, 0.176, 1, 0, 0);

Changing the transform origin

By default, transforms originate from the center of the element, or 50% along both x and y. If you scale it down, it shrinks towards the center, or else you rotate it around its center point. The transform-origin property allows you to place this origin point elsewhere, even outside the element, changing how transforms respond, especially in animations. It accepts a pair of x/y measurements.

This shows a series of transforms that rotate around a point near the bottom right corner:

div {
    transform-origin   : 80% 90% ;
}
div:nth-of-type(1) { transform : rotate(10deg) ; }
div:nth-of-type(2) { transform : rotate(20deg) ; }
div:nth-of-type(3) { transform : rotate(30deg) ; }

origin rotate.png

(View live sample)

In this example, placing the origin of a skew transform at the bottom makes it appear to tip over:

div {
   transform          : skewX(15deg);
   transform-origin   : bottom;
}

origin skew.png

(View live sample)

The property accepts the keywords top and left for 0%, bottom and right for 100%, and center for 50% 50%.

In the first of the following examples, placing the origin of a scaleX() transform along one edge makes it appear to pivot along that edge. In the second, placing the origin far outside the element’s boundaries moves it across the screen with no need for the translate() function:

div:first-of-type {
   transform        : scale(0.35, 1);
   transform-origin : 0%;
   transform-origin : left; /* same as 0% */
}
div:last-of-type {
   transform-origin : 210% -20%;
   transform        : scale(0.5);
}

origin scale.png

(View live sample)

You need some perspective

You really do. Though they often take on the illusion of three dimensions, 2D transforms are strictly superficial, inhabiting the plane of the display screen. Three dimensional transforms allow ordinary web content, which is still inherently flat, to shift onto other planes. But before applying the 3D transform functions discussed below, you need to position the scene relative to the viewer.

Applying perspective to an an element indicates the viewer’s perceived distance to any transformed descendent elements. The perspective property sets a number of CSS pixels along the z axis that leads from the screen to the viewer. Since ordinary web content is inherently flat, all measurements along the z axis must specify absolute units such as pixels, rather than relative percentages that make sense along x and y.

The default perspective value of none indicates an infinite perspective, which appears flat. A value in the range of 1000 to 2000 mimics the distance at which people typically view their screen. Perspective values of less than 1000 appear increasingly distorted, but may be desirable for immersive virtual reality applications in which the viewer appears to mingle among displaying elements.

The first scene in the example below shows a 3D rotation that appears flat before applying perspective, while the second shows a distance of 1000. The ancestor element’s border reveals how the descendant’s plane has shifted.

.parent {
    border             : thin solid #000;
    perspective          : 1000px;
    perspective-origin   : 50% 50%;
}
.child {
    transform          : rotateY(45deg);
}

transform perspective.png

(View live sample)

While perspective affects the perceived distance to an object along the z axis, the perspective-origin property allows you to shift the perceived location along x and y from which it is viewed. The second scene reflects the default center value in which the viewpoint is centered straight out from the screen. Altering these values positions the viewer diagonally from the scene. The third scene shows the same rotation, but with the viewpoint shifted to the right:

perspective-origin-x : 500px;

Note that since percentages refer to the size of the transformed element, pixel units may be easier to use. Fixed x/y values for perspective-origin along with z values for perspective allow you to maintain a Cartesian coordinate system.

Keep in mind several things to help understand how perspective corresponds to how output actually renders:

  • Don’t confuse the perspective point with the scene’s three vanishing points. Changing the single perspective point from which you observe the scene may affect where these vanishing points appear to fall, but they are not the same thing.

  • The repositioned perspective point affects the appearance of the transformed element, not the ancestor that specifies the perspective.

  • Unlike real life, changing the distance to the scene does not change its perceived size. Imagine walking towards a large, recessed bookcase. As you approach it, it appears larger, and its interior angles appear more dramatically skewed towards a vanishing point. Conversely, when you use CSS to reduce the value of perspective to approach such a scene, the angles still change, but the overall size appears oddly constant. (The 3D translation functions discussed below allow you to make the distance appear more realistic.)

  • CSS transforms don’t allow the viewer to look away from the scene, no matter from where the viewpoint is positioned.

  • From the perspective point, you’re actually looking at the element’s transform-origin point, which you may set to fall well outside the rendering element.

  • The perspective property doesn’t allow negative values, so while you may start to simulate traveling around the scene by offsetting the perspective-origin while reducing the perspective value, you can’t actually get to the other side.

3D transforms

To extend into three-dimensional space, enhanced functions allow rotations around x and y, and translations into z space.

The rotateX() and rotateY() functions spin elements around their horizontal or vertical axis, while the rotateZ() function behaves the same as the 2D rotate() function.

An alternative rotate3d() function requires four arguments. The first three measurements specify a point defining a 3D vector in x/y/z terms from the origin of the element. (For example, setting the vector to -0.2,1,0.2 spins the object as if it were a child’s top leaning slightly forward and to the left, while setting it to 0,1,0 would straighten it.) The fourth argument specifies the angle of rotation around that vector, using deg or rad measurements.

The translate3d() function requires three x, y and z measurements. Otherwise, use any combination of translateX(), translateY(), and translateZ() functions. The z translation must specify absolute measurements. The perceived movement towards or away from the viewer depends on how much perspective has been applied.

The scale3d() function accepts three x, y and z measurements, otherwise they can be specified separately with scaleX(), scaleY(), and scaleZ(). Scaling along the z axis ordinarily has no effect for flat web content, but does affect the sort of nested 3D objects described in the next section.

The transform-origin property accepts an additional third z measurement to place the transformation point behind or in front of where the element displays, in absolute units. (Alternately, specify the transform-origin-z property.) This example shows a series of elements that rotate to various degrees from an origin point within the gray parent box that is far behind them:

3d originZ.png

(View live sample)

.parent {
   perspective          : 10000px;
   perspective-origin-y : -3000px;  /* view from above */
   background-color     : #aaa;
}
.child {
   transform-origin-z   : -620px;
   transform-origin     : 50% 50% -620px;  /* same */
}
.child:nth-of-type(1) { transform: rotateY(-50deg); }
.child:nth-of-type(2) { transform: rotateY(-30deg); }
.child:nth-of-type(3) { transform: rotateY(-10deg); }
.child:nth-of-type(4) { transform: rotateY(10deg); }
.child:nth-of-type(5) { transform: rotateY(30deg); }
.child:nth-of-type(6) { transform: rotateY(50deg); }

By default, elements that rotate away from view display as a mirror image. Changing the backface-visibility property to hidden (from the default visible) enables flip-panel effects in which elements only appear if they face the viewer.

This example shows a pair of child elements positioned at the same coordinates within the parent card, but one of which is rotated to face the other way. As described below, you can rotate the entire card along with its children. In this case, with the backface hidden, only one of the child face elements displays at a time:

 <div class="card">
   <div class="face" id="jackheart"></div>
   <div class="face"></div>
 </div>

3d backface.png

(View live sample)

body {
    background         : #ddd;
    perspective          : 1200px;
}
div {
    position           : absolute;
    width                : 280px;
    height                 : 400px;
    border                   : thin solid #777;
    border-radius            : 0.5em;
}
.card {
    left               : 0px;
    transform            : rotateY(45deg);    /* vary this to rotate the entire card */
    transform-style      : preserve-3d;    /* this allows each face to rotate within the card */
}
.face {
    background-size    : 100% 100%;
    backface-visibility  : hidden;    /* only display when facing viewer */
}
.face:first-of-type {
    transform          : rotateY(0deg);
}
.face:last-of-type {
    transform          : rotateY(180deg);
    background-image   : url(cardBack2.jpeg);
}
#jackheart {
    background-image   : url(JackHeart.jpeg);
}

While 2D transforms can be represented as a 6-element matrix() function, 3D transforms correspond to 16-element matrix3d() functions. Here’s how an element might appear within the web inspector’s computed style panel:

transform: matrix3d(0.642, 0, 0.766, 0, 0, 1, 0, 0, -0.766, 0, 0.642, 0, 0, 0, 0, 1);

Nested 3D transforms

Applying a 3D rotation to an element aligns it to a different plane than that of the viewing screen. Applying 3D translations and scale effects also overrides the default coordinate system. The transform-style property allows you to transform nested content independently within that already modified space.

To clarify how to use this feature, this extended example builds a cube representing playing dice that can spin freely. The markup is implemented as a series of nested elements:

 <div class="scene">
     <div class="dice">
         <div class="centered">
             <div class="face"></div>
             <div class="face"></div>
             <div class="face"></div>
             <div class="face"></div>
             <div class="face"></div>
             <div class="face"></div>
         </div>
     </div>
 </div>

Global styles define absolutely positioned 100-pixel-square boxes. The outlines will help clarify each nested transform:

div {
    box-sizing     : border-box;
    position       : absolute;
    width          : 100px;
    height         : 100px;
    outline-offset : 20px;
    outline-width  : 3px;
    outline-style  : solid;
}

The outermost scene element defines the overall perspective:

.scene {
    perspective   : 500px;
    outline-color : pink;
}

3Dnest scene.png

The next dice element is rotated arbitrarily:

.dice {
    transform       : rotateX(30deg) rotateY(50deg) rotateZ(20deg);
    transform-style : preserve-3d;
    outline-color   : lightgreen;
}

3Dnest dice.png

The preserve-3d above renders any child element’s transforms in three dimensions relative to the dice element’s own transformed space. Otherwise the default flat value would make them appear on the dice element’s surface as if on a display screen.

In this case, the nested centered element is there simply as a convenience to drop the cube back to accommodate its full volume:

.centered {
    transform       : translateZ(-50px);
    transform-style : preserve-3d;
    outline-color   : gold;
}

3Dnest centered.png

Various properties define the dice’s edges with rounded corners, along with the small dot images that will be arranged to form a pattern on each face. Only one dot displays in the first face’s pattern, and the rest are pushed outside the displaying area:

.face {
    border-radius       : 6px;
    border              : 2px solid #777;
    background-color    : #fff;
    background-repeat   : no-repeat;
    background-image    : url(dot.png), url(dot.png), url(dot.png),
                          url(dot.png), url(dot.png), url(dot.png);
    outline-color       : transparent;
}
.face:nth-of-type(1) {
    background-position : 40px 40px, -20px -20px, -20px -20px,
                         -20px -20px, -20px -20px, -20px -20px;
}

3Dnest face1.png

The next four faces use transform-origin to pivot outward at right angles along each edge of the first face:

.face:nth-of-type(2) {
    transform           : rotateY(-90deg);
    transform-origin    : left;
    background-position : 10px 10px, -20px -20px, -20px -20px,
                         -20px -20px, -20px -20px, 70px 70px;
}
.face:nth-of-type(3) {
    transform           : rotateY(90deg);
    transform-origin    : right;
    background-position : 10px 10px, 40px 40px, -20px -20px,
                         -20px -20px, -20px -20px, 70px 70px;
}
.face:nth-of-type(4) {
    transform           : rotateX(90deg);
    transform-origin    : top;
    background-position : 10px 10px, -20px -20px, 10px 70px,
                          70px 10px, -20px -20px, 70px 70px;
}
.face:nth-of-type(5) {
    transform           : rotateX(-90deg);
    transform-origin    : bottom;
    background-position : 10px 10px, -20px -20px, 10px 70px,
                          70px 10px, 40px 40px, 70px 70px;
}

3Dnest face5.png

Note that all the backfaces are visible, and only become hidden when other opaque boxes appear in front of them.

The sixth face simply uses translateZ() to drop it back to close off the cube:

.face:nth-of-type(6) {
    transform           : translateZ(-100px);
    transform-origin    : center;
    background-position : 10px 10px, 10px 40px, 10px 70px,
                          70px 10px, 70px 40px, 70px 70px;
}

3Dnest face6.png

Applying different rotations to the dice element causes nested transform spaces to render relative to it, thus spinning the entire object. Here is how a script can control the spin:

var diceStyle = document.querySelector('.dice').style;
diceStyle.transform = 'rotateX(' + spin() + ') ' + 'rotateY(' +
       spin() + ') ' + 'rotateZ(' + spin() + ') ' ;
// ...or diceStyle.WebkitTransform, diceStyle.MozTransform, etc.

function spin() { return( Math.floor( Math.random() * 360 ) + 'deg') }

3Dnest spin.png

Once such a complex 3D object takes up space, the effect of the scaleZ() or scale3d() functions becomes apparent. An animated transition between scale3d(0,0,0) and scale3d(1,1,1) sizes the object in all three dimensions:

scaleZ.png

(View live sample)

While there are limits to what you can accomplish when transforming flat web content, this technique of building 3D shapes such as cubes may form the basis of simple virtual reality scenes, such as the following (suitable for a mobile browser) that pans to view the surface of Mars:

mars.png

View the sample to see how the viewer is positioned within the display elements:

(View sample here)

This example places an element randomly within a coordinate space, and rotates it to face the path to travel there. When viewed on WebKit nightly builds that support custom CSS filters, something amusing happens along the way. (As of this writing, Canary requires the Enable CSS Shaders flag enabled under chrome://flags)

custom filter random path.png

(View sample here)