samedi 5 mai 2012

CSS transform-origin Coming to SVG


Recently Dirk Schulze and I added SVG support for the CSS transform-origin property to WebKit. The CSS property's semantics can be a little subtle so I thought that a brief explanation and some examples might be a nice way to open this blog. Unfortunately I'm not particularly good at brevity. I will try and cover the transform-origin property, how it's actually used in "portable" HTML, and how the SVG support differs from HTML.

The CSS transform-origin property is used along with the CSS transform property to specify geometric transformations that do more than just translate. For example if you specify style="transform:rotate(45deg)" for an HTML element, the transform-origin specifies the point about which the element will be rotated. The transform-origin's value is a pair of lengths or keywords specified relative to the element's upper left hand corner. The default transform-origin is "50% 50%", so by default rotate(45deg) means rotate the element around its center. Which is a rather convenient default, because even if it's not what you wanted it's pretty easy to understand what's going on when you look at the result.

Older graphics systems, like Java2D, did not provide a nice default for the transform origin or such a flexible way of specifying it. That meant that nearly every developer tasked with displaying a rotated graphic had to first blunder into what you see on the left, before sorting out the combination of translations that would deliver what they really wanted (on the right).


Roll over the diagram to run the animation.

The transform-origin specifies a location relative to the upper left hand corner of CSS border box of the target element it's being applied to. The location syntax is rather flexible, to make it easy to specify the common cases. The location can be specified with a pair of lengths, which are numbers with units like "100mm", or percentages like 50%. The location can also be specified with a pair of keywords like top, bottom, left, right, center, or a combination of keywords and lengths. If only keywords are used then they can be specified in any order, i.e. "bottom left" is the same as "left bottom" which is the same as "0% 100%". Keywords and percentages are defined in terms of the border box's width and height. When keywords and lengths are used in combination then the X value must come first, and then the Y value. No matter how it's specified, the transform-origin is used to compute effective transform-origin X and Y values in the target element's coordinate system.

The demo below illustrates the various possibilities for transform-origin values. The diagram represents a single div element with 20 pixel margin and border, 10 pixel padding, and 300x100 pixel content area. The X,Y axes illustrate the coordinate system within which the transform-origin is defined. Clicking on the diagram displays the (not comprehensive) list of transform-origin values that all mean the same thing. One noteworthy point about this is that when the transform-origin lines up with one of the border box's corners, the most readable way of specifying the transform-origin is with a pair of keywords.

margin: 20px;
border: 20px;
padding: 10px;
width: 300px height: 100px;

Equivalent Values for CSS transform-origin property (click on the diagram above)


What the transform-origin really means is that before the transform is applied the target element will be translated in the negative direction by transform-origin's effective X and Y values, and then after the transform has been applied the target element will be translated back, by the same amount. All three steps are applied within a coordinate system whose origin is defined by the upper left corner of the target's CSS border box. Not put too fine a point on it, but the translation specified by transform-origin is computed once, before the transform is applied. In other words, it's not affected by transform elements like scale() or rotate.

It's somewhat easier to to visualize this with a diagram than it is to describe in words. In the animated demo that follows we're applying "transform: scale(1.25) rotate(30deg)" to the square div element whose transform-origin is the default, i.e. the center of the element. The animation shows all three steps of the process: translating relative to the origin of the element's CSS border box, applying the transform, and then translating back.


Click on the diagram to run the animation.

What should be somewhat obvious from this little visualization is that a transform which only specifies a translation is not affected by the transform-origin. In fact the WebKit implementation checks for this special case and skips the transform-origin translations to save a few cycles. It's also worthwhile to remember that this process is always the same, no matter how the transform-origin is specified.

The CSS transform and transform-origin properties are part of the CSS draft standard. They're supported in the latest Chrome/Safari, Firefox, Internet Explorer, and Opera browsers with platform specific names. To write portable styles that depend on transform and transform-origin you must reiterate them per platform, using a vendor-specific prefix. It's ugly. For example:


.target {
-webkit-transform-origin: 100% 100%; /* Chrome, Safari */
-webkit-transform: rotate(45deg);
-moz-transform-origin: 100% 100%; /* Firefox */
-moz-transform: rotate(45deg);
-o-transform-origin: 100% 100%; /* Opera */
-o-transform: rotate(45deg);
-ms-transform-origin: 100% 100%; /* Internet Explorer */
-ms-transform: rotate(45deg);
transform-origin: 100% 100%; /* Someday... */
transform: rotate(45deg);
}

Similarly, to programmatically change the transform-origin property of an element's style, without getting cute:


target.style.webkitTransformOrigin = "100% 100%";
target.style.OTransformOrigin = "100% 100%";
target.style.MozTransformOrigin = "100% 100%";
target.style.msTransformOrigin = "100% 100%";

SVG has always supported a transform attribute which is conveniently called just "transform". Recent WebKit changes have added SVG support for the CSS transform and transform-origin styles as well as making transform-origin a presentation attribute. With these changes, SVG elements' transform and transform-origin can be styled with CSS. In most respects SVG elements respond to transform and transform-origin in the same way as HTML elements do, however there are a few inevitable differences. Graphical SVG elements don't have a CSS box, nor do they support the border, margin, and padding CSS properties. For SVG, the transform-origin is defined relative to the SVG element's bounding box, which includes the stroke used to draw its border. This almost doesn't bear saying, since it's pretty much what you'd expect and effectively the same as HTML. The other difference is a little more surprising: transform-origin values that are specified with concrete lengths like 100px (not with keywords or percentages) are not interpreted relative to the origin of the element's bounding box, but relative to the origin of the enclosing element.

It turns out the difference in the concrete transform-origin values, like 100px 100px, can sometimes make specifying a transform-origin in SVG more straightforward than in HTML. Clicking on on demo below causes the red "orbiters" to fly once around their circular trajectory. The center of the trajectory circle is at 300,150 and the initial position of the orbiters is at the top of the circle, effectively at zero degrees. To fly the orbiters around the circle we animate the rotate element of their transform and specify their transform-origin as 300px 150px. In other words their center of rotation is just the center of the trajectory circle.

<style>
...
.orbiter {
...
-webkit-transform-origin: 300px 150px;
}
</style>

<svg xmlns="http://www.w3.org/2000/svg"<
<circle cx="300" cy="150" r="100" class="trajectory"/>
<circle cx="300" cy="50" r="20" class="orbiter"/>
<circle cx="300" cy="50" r="20" class="orbiter"/>
</svg>

That's probably a little easier to understand than the HTML version, where the transform-origin is interpreted relative to the orbiter itself. An HTML version of the same demo is very similar, except the transform-origin is "20px 120px" because it's defined relative to the orbiter's CSS box. This has the same effect as the SVG version, but requires a little more head scratching to understand.

The SVG demo only works correctly in browsers based on very recent builds of WebKit. If you're running one, click here to download the file. If not, check out the HTML version below.


Click on the diagram to run the animation.

Aucun commentaire:

Enregistrer un commentaire