2D Transforms in CSS3
One of the most powerful features of CSS3 are transforms, which allow us to take any element in an HTML document, and while not changing its effect on the page layout, rotate it, translate it (move it left, right, up and down), skew it and scale it. CSS3 provides both 2D and 3D transforms, but while 2D transforms are supported in all modern browsers, including IE9 and up, 3D transforms are currently only supported in Safari, Chrome and IE10.
In this article, we’ll take a look at how to use 2D transforms and the various transform functions (such as scale and rotate). We’ll also, as always, look at some gotchas when first working with transforms, and once again, there’s a tool to help you play with transforms (I developed it some time ago, but I’ve updated it significantly for this article).
What transforms do
As in many cases, an example is worth a thousand words, so let’s take a look at a couple, to illustrate how transforms work, as well as some of their most important aspects.
When you click or tap the button below, this paragraph is rotated by 45 degrees. It’s also scaled to 75% of the size it would otherwise be. However note how the rest of the page layout is not affected. That’s because transformations don’t change the box model of an element, and so leave the page layout unchanged.
This element has been skewed horizontally and vertically. You can still select the text, and otherwise interact with the element, as you would if it weren’t transformed.
Transforming elements
The syntax for CSS transforms is in many ways straightforward. There are just two new properties to contend with – transform
, and transform-origin
. The first specifies the transforms we want applied (scaling, rotating and so on), while the second, which is optional, specifies the origin for this transformation (for example, do we rotate around the middle of the element, its top left hand corner, and so on).
The transform
property
The transform
property takes as its value one or more transform functions, which each specify a transformation. Let’s take a look at each of these different functions in detail. Functions all take the form of a function name, with a value inside round brackets (as indeed do all CSS functions). So for example, we translate horizontally with the function translateX(200px)
.
OK, enough preliminaries, let’s start in with some actual transforming. We’ll start with the translate
function.
translate
The translate
function moves the contents of an element to the left (negative values) or right (positive values), and/or upwards (negative values) and downwards (positive values).
translate
takes one or two comma separated values. The first is the horizontal translation value. If there is a second, this is the vertical translation value, while if there is only one value, then the vertical translation is zero (that is, the element will only be translated horizontally).
In addition to the translate
function, there are the related translateX
and translateY
functions, which only translate an element horizontally, or vertically, respectively. Translate functions take length or percentage values, and like CSS properties, require units for values other than zero.
But enough theory, why not have a play with them?
transform: translate(0, 0)
We mentioned a moment ago that transforms don’t impact the layout of a page. There’s one area where this is not exactly true. If you translate the above element completely to the right, you’ll notice a horizontal scrollbar appears (or the page can now be scrolled to the right). While the page layout has not been changed, the overflow
property of the transformed elements containing element is affected by transforms (Safari on Mac OS X Lion is an exception to this, and perhaps indicates where this sort of UX is headed, but for now, in desktop/laptop browsers other than this, the addition of a scrollbar will impact page layout).
This element is scaled by 200% when you click the button below. Its containing div has an overflow: auto
. So, scrollbars appear when the element is transformed. In many browsers this will cause the page to reflow.
scale
The scale
function lets us zoom an element up or down in size (all the while maintaining the dimensions of its box for the purposes of the layout of the page). The function name is scale
, and it takes a number value-with a value of .5 meaning “scale to half the current size”, and of 2 meaning “scale to twice the current size”, and so on. So, if we want to make an element 75% of its current size, we use the property transform: scale(.75)
. And that’s really all there is to it. Why not have a play with it?
transform: scale(1)
It’s also possible to scale horizontally and vertically independently from one another, by using two comma separated numerical values for the scale
property. The first value scales an element horizontally, and the second, vertically. Let’s take a look.
transform: scale(1, 1)
As with translate
there are two related scale functions, scaleX
, and scaleY
. scaleX
scales only horizontally (scaleX(2)
is the equivalent of scale(2, 1)
), and scaleY
, which scales only vertically (scaleY(2)
is the equivalent of scale(1, 2)
).
rotate
Rotating text and images is a common page design technique, until now difficult to achieve on the web without considerable hackery, if at all. Transforms make rotating an element easy.
The rotate
function takes an angle value. If you’ve worked with CSS linear gradients, in particular the newer syntax, then you’ll have seen angle units before. There are several ways you can specify an angle in CSS.
degrees
As you most likely remember from school math, there are 360 degrees in a circle. So, when specifying a rotation of an element, 90 degrees is a quarter turn clockwise, 180 degrees is a half turn, 270 degrees is three quarters turn clockwise, and 360 degrees is a full revolution. Here these are below.
rotate(90deg) rotate(180deg) rotate(270deg) rotate(360deg)
Of course, it is possible to rotate an element by an arbitrary angle, for example 34.6 degrees, like so
rotate(34.6deg)
But, there are other ways we can specify rotations.
- turns
- perhaps the simplest way to specify a rotation is with the
turn
value. We can rotate an element a quarter turn clockwise with the functionrotate(.25turn)
, half a turn with .5turn, three quarters of a turn with .75 turn, and a whole turn with the functionrotate(1turn)
. WebKit and Opera support theturn
value, while Firefox (version 6) does not.
rotate(.25turn) rotate(.5turn) rotate(.75turn) rotate(1turn)
For completeness, it’s worth noting that there are two other possible angle units-radians (rad), and gradians (grad). Briefly, there are 400 gradians in a full rotation (so one grad is slightly larger than one degree), while there are 2π radians in a full rotation (radians are widely used in mathematics).
Now, if you think about rotating an element, then you might wonder what point of the element the rotation takes place around. For example – is it the center? Or one of the corners?
This element rotates around the top left hand corner when you click or tap and hold it. (transform: rotate(360deg)
)
We can in fact specify where the rotation (and as we’ll soon see, any transformation) takes place, using the transform-origin
property. transform-origin
takes two length or percentage values, which specify the horizontal and vertical “origin” of the transformation. In the above example, we have transform-origin: 0 0
. If we want to rotate around the center of the element, we use transform-origin: 50% 50%
, while to rotate around the bottom right of the element, we use transform-origin: 100% 100%
.
This element rotates around the center of the element (transform-origin: 50% 50%; transform: rotate(360deg)
).
This element rotates around the bottom right hand corner of the element (transform-origin: 100% 100%; transform: rotate(360deg)
).
(You might be able to guess that we can animate transformation changes, like we can most other CSS property changes using CSS Transitions. For more on this, see my article on CSS transitions and animations.)
So, let’s now put rotations and transform origin together so we can play around with them.
transform: rotate(0); transform-origin: 0 0
transform-origin
can also be specified with keywords, in place of percentage (or length) values. As with background-position
we can use left
, center
or right
for the horizontal origin position and top
, center
or bottom
for vertical. Where only one value is used, this applies to the horizontal origin, and the vertical is 50% (or center
). Where no transform-origin
is specified, its default value is 50% 50%
.
skew
the last of the 2 dimensional transform functions is skew
. Skewing is typically explained in mathematical terms, but if you recall a little school geometry, it isn’t really that complicated. Horizontal skew (the skewX
function) takes the box of an element, and while the top and bottom edges remain horizontal, tilts the left and right edges by the specified number of degrees to create a parallelogram. Similarly, a vertical skew (skewY
), leaves the left and right edges vertical, and creates a parallelogram with the top and bottom edges rotated by the specified number of degrees. And when you skew both horizontally and vertically, you combine both of these (where it can get really quite tricky to work out what’s going on). Does that help? You can play with the values below to get a sense of how skewing works in practice, including how it can create apparent 3D effects.
The skew
function, like most other functions, takes one or two values. As with rotate
, they are angle values, and where there are two values, they are comma separated.
transform: skew(0, 0)
Once again, as with a number of the other transform functions we’ve seen, there are two related functions – skewX
and skewY
, which each skew only in one dimension, and which take only a single, angle value.
Complex transformations
While you’ll likely never need to use it, underneath all these functions is the core of CSS transformations, based on the mathematical concept of a transformation matrix. Each of the functions we’ve seen can be represented by a matrix value. While it’s firmly in the domain of mathematics, we can for example represent the function scale(1.5,1.2)
by the matrix
1.5 | 0 | 0 0 | 1.2 | 0 0 | 0 | 1
and directly apply this matrix value using the matrix
function, like so
transform: matrix(1.5, 0, 0, 1.2, 0, 0)
this element has been scaled using a matrix function on the transform property.
The transform matrices for various functions are described in the SVG specification.
Multiple transforms
It’s possible to apply multiple transform functions at once to an element. For example we can rotate, translate and skew an element with the single transform property:
transform: scale(0.75) rotate(45deg) translate(132px, -149px) skew(32deg, -32deg);
Click the button below to apply several transformation functions simultaneously, with the property transform: scale(0.75) rotate(45deg) translate(132px, -149px) skew(32deg, -32deg);
There is however a complicating factor, which we look at in a moment.
The Transformer Tool
Hand coding transforms, as with much to do with CSS3, is an increasingly complex process. Not only is there a good deal of syntax to remember often for quite straightforward effects, but the outcome of at least some transforms isn’t necessarily obvious to most of us simply by looking at the CSS. So, to help you learn, and explore, CSS transforms, I’ve developed a 2D transform tool (there’s a 3D one as well, but we’ll cover 3D in a later article).
If you’re keen to explore transformations in more detail, and how they can be made to work together, head over and take a look, and as always, let me know what you think via twitter.
Gotchas, tips and tricks
Its fair to say that while now widely supported in modern browsers, 2D CSS transforms are still experimental, and quite quirky. Here are some of the difficulties you might encounter, and some tips and ideas for working with 2D transforms.
vendor specific prefixes
Reflecting the experimental nature of transforms, all browsers require vendor specific prefixes for the transform
and transform-origin
properties (note that function names are not prefixed). As all modern browsers including IE9 and up support 2D CSS transforms, using these transformation properties can get more than a little unwieldy. For example, the CSS for a simple rotation around the center of an element, if we include all vendor specific properties, would be something like:
img{ -webkit-transform: rotate(90deg); -moz-transform: rotate(90deg); -o-transform: rotate(90deg); -ms-transform: rotate(90deg); transform: rotate(90deg); //always include a standard for last -webkit-transform-origin: 50% 50%; -moz-transform-origin: 50% 50%; -o-transform-origin: 50% 50%; -ms-transform-origin: 50% 50%; transform-origin: 50% 50%; }
You might find the use of a CSS preprocessor like LESS or SASS worth exploring, as they can make stylesheets far more manageable when you use a lot of vendor prefixing.
Transforming inline elements in webkit
According to the current version of the specification, CSS Transforms should be applied to inline, as well as block elements, but while Opera and Firefox both correctly apply transforms to inline elements, WebKit browsers (Safari 5.1, Chrome 15) currently don’t.
A workaround for this is to give inline elements which are to be transformed display: inline-block
, which won’t affect how they are laid out in the page, but will enable these browsers to transform them.
Translating rotated content
One subtle aspect of multiple transformations is that functions are performed in sequence – from first to last in the list. The order that you specify functions can make a difference. Take a look at the following paragraphs. Both have the same scale
, rotate
and translate
functions applied. But, in the first, the element is translated to the right, while in the second, it is translated to the left. What’s going on?
Clicking the button below applies several transformation functions simultaneously, with the property transform: scale(0.75) rotate(180deg) translate(-100px,0)
Clicking the button below applies several transformation functions simultaneously, with the property transform: scale(0.75) translate(-100px, 0) rotate(180deg)
Transformations don’t take place in an absolute coordinate space. Rather, “up”, “down”, “left” and “right” are relative to the current rotation (but not skew) of the element. So, if an element is rotated half a turn, and so is “upside down” then translate(0, -100px) moves it down the page 100px. If it’s rotated a quarter turn to the right, it’s moved to the left. Similarly, translating “horizontally” is always relative to the element. So, if it’s rotated by 180 degrees, then translate(100px, 0) moves the element to the left. In short, the X and Y axes of a transformation are relative not to the page, but the element’s current rotation.
Interacting with transformed content
While the box of an element isn’t changed by a transformation, elements that are transformed display various quirks when it comes to mouse events in various browsers, most likely due to the still experimental nature of transformations. Take the element below. It rotates a quarter turn clockwise when the mouse is over it, and then back to its original rotation when the mouse is out of it.
Now, if the box of the element isn’t changed, then when rotated, hovering over any of the image which is outside that original box should not trigger a mouseover event, and so the element should rotate back to its original position. However, as makes intuitive sense, hovering over those parts of the rotated element that are outside the original box does cause mouseover events to be fired. But in addition, mouseover events are also fired when you hover over that part of the element’s original box which no longer has content because it has been rotated away. And if you move your mouse around the element, you’ll find in all browsers various locations where the rotation abruptly, though unintuitively, changes. Similar behavior can be observed for translation and other transformations.
In light of this, I’d suggest being extremely wary of transforming elements which users will interact with given the current state of browser support for transformations.
Overflowing and transformations
We mentioned earlier that while transformations don’t effect the box model of an element, and so leave the layout of a page untouched, they do effect the overflow, and if we have overflow: auto
, they can in fact impact the page flow.
In the element below, we have an image inside a div. The div has an overflow:auto
. When we hover over the element (apologies to touch device users), the contained image scales up in size.
Now, in most browsers on most platforms (Safari on Mac OS X 10.7 is an exception) the browser adds a horizontal scrollbar to the div when you hover over the image, which adds to the height of the element, reflowing the page below it. Just something to be aware of.
CSS Positioning and Transforms
While I was writing this article, CSS legend Eric Meyer posted a detailed critique of one interesting aspect of transforms.
It has to do with the positioning of elements. When we absolutely or fixed position an element, and give it say a top and left, these positions are offset from their containing box – which is not necessarily their parent element. Rather, the containing box is the first ancestor which itself has either relative, absolute or fixed position
. However, adding a transform to an element also makes it a containing block for its descendent elements! As Eric observes, this can particularly cause difficulties with fixed positioning. Rather than rehash Eric’s detailed thoughts, I’ll let you head over and read them first hand.
General Rendering Problems
On different browsers you’ll find various rendering problems with transformed content. For example, with Opera, rotated text appears to be rendered in a lighter font than the same text when it isn’t rotated. I’ve also seen redrawing problems when rotating text in WebKit browsers. In Safari on iOS 4, rotated text doesn’t necessarily align smoothly along its baseline. None of these are necessarily deal breakers, but it’s worth keeping in mind. Transforms are still experimental, so don’t necessarily expect them to be perfectly supported in all circumstances just yet.
Hardware Acceleration
There’s a widely held belief that at least some browsers hardware accelerate the rendering of CSS transformations (hardware acceleration involves the CPU handing off execution of certain types of calculation to the GPU, which can increase rendering performance significantly, particularly on mobile devices).
At present the current state of hardware acceleration for CSS transforms across all browsers and devices is difficult to pin down. A webkit engineer I tracked down confirmed that current versions of Safari (5.1 on the desktop, iOS 4), but not necessarily other WebKit browsers:
- animated 2D transforms in Safari are always hardware accelerated
- using 3D for static 2D transforms may improve performance, but may also increase memory use – a real issue for memory limited mobile devices in particular
Now, haven’t we just spent however much effort covering 2D transforms? How could this possibly help? Well, as we’ll see in an upcoming article on 3D transforms, we can use 3D transforms to do 2D transforms (for example, there’s rotate3D
). If squeezing maximum performance for iOS devices is a need, then it may be that using a 3D version of a transform will help.
It’s also worth noting that issues around what aspects of CSS transforms are hardware accelerated and how that acceleration works are implementation details in specific browsers, and not specified as part of CSS Transforms. As such, across browsers there’s likely to be little uniformity of approach to hardware acceleration, and even from one version of a browser to the next, approaches may change.
Backwards Compatibility
One of the strongest aspects of CSS3 features like gradients, border-radius, shadows and the like is that they have been typically designed so as to be easily used in a way that is backwards compatible almost automatically, or provided we keep a small number of potential challenges in mind. For example, with gradients we need to ensure we have a fallback background color or image for when gradients aren’t supported. For text-shadow
, we need to ensure that the contrast between the text and the element background is sufficient when the shadow is not drawn.
Of all new CSS3 properties, transform is the one where backwards compatibility is the most difficult to ensure. Ironically, it’s precisely because transforms don’t affect the page layout that they cause such difficulty. For example, in order to accommodate say a heading rotated 90 degree to run horizontally along the left hand side of the text which follows it, we need to create whitespace to ensure the heading does not overlap the text. We might do that with margin, or padding. We’re also likely to want to remove the whitespace where the heading has been rotated away from, by, for example, using negative margin on the paragraph following the heading. But, what happens if transforms aren’t supported? The heading text will be overlapped by the paragraph below it.
In order to use transforms for more sophisticated page layout along these lines, a solution like Modernizr, which enables different CSS to be applied based on the support, or absence of support for various CSS3 features like transforms is indispensable.
Transformations are typically most easily used in a way that is backwards compatible where animated transforms create a transition between states in an application. We’re all most likely used to sliding or flipping transitions between states in iOS and other mobile apps. CSS transforms can be used for these transitions, in conjunction with CSS Transitions, and where transforms aren’t supported (in which case it’s unlikely transitions will be as well), your users simply see an abrupt change in state.
However you plan to use transforms, as with every other aspect of web development, keep in mind those browsers which don’t support them, and ensure your user’s experience isn’t diminished to the point where information or functionality is denied them because their browser doesn’t support it.
Browser Support
As we mentioned, despite the still rather experimental nature of support for CSS 2D Transforms, they are now widely supported in modern browsers including:
- Internet Explorer 9 and up
- Firefox 3.5 up
- Safari 3.2 up
- Chrome 10 and up
- Opera 10.6 and up
- iOS 3.2 and up
- Opera Mobile 11 and higher
- Android 2.1 and up
So, support is widespread, and constantly improving. While there are definitely challenges associated with using 2D Transforms, they’re a powerful, and worthwhile addition to the repertoire of developers, and will only gain in value. What are you going to do with them?
More Reading
- The 2D Transforms Specification
- MSDN on Transforms – great to see the IE love for them!
- Opera’s developer center on, you guessed it, Transforms
- On Transformations in Internet Explorer (before and after IE9)
Great reading, every weekend.
We round up the best writing about the web and send it your way each Friday.