Skip to content

Commit

Permalink
[css-transforms-1] Clarify difference between post-multiply, pre-mult…
Browse files Browse the repository at this point in the history
…iply and multiply. Clean up examples and editorial changes. w3c#909
  • Loading branch information
dirkschulze authored and fergald committed May 7, 2018
1 parent e7381cd commit 460fcf2
Showing 1 changed file with 90 additions and 70 deletions.
160 changes: 90 additions & 70 deletions css-transforms-1/Overview.bs
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,14 @@ When used in this specification, terms have the meanings assigned in this sectio
: <dfn>identity transform function</dfn>
:: A <a href="#transform-functions">transform function</a> that is equivalent to a identity 4x4 matrix (see <a href="#mathematical-description">Mathematical Description of Transform Functions</a>). Examples for identity transform functions are ''translate(0)'', ''translateX(0)'', ''translateY(0)'', ''scale(1)'', ''scaleX(1)'', ''scaleY(1)'', ''rotate(0)'', ''skew(0, 0)'', ''skewX(0)'', ''skewY(0)'' and ''matrix(1, 0, 0, 1, 0, 0)''.

: <dfn>post-multiply</dfn>
:: Term <var>A</var> post-multiplied by term <var>B</var> is equal to <var>A</var> &middot; <var>B</var>.

: <dfn>pre-multiply</dfn>
:: Term <var>A</var> pre-multiplied by term <var>B</var> is equal to <var>B</var> &middot; <var>A</var>.

: <dfn>multiply</dfn>
:: Multiply term <var>A</var> by term <var>B</var> is equal to <var>A</var> &middot; <var>B</var>.

The Transform Rendering Model {#transform-rendering}
====================================================
Expand All @@ -156,22 +164,23 @@ Specifying a value other than ''transform/none'' for the 'transform' property es
The coordinate space is a coordinate system with two axes: the X axis increases horizontally to the right; the Y axis increases vertically downwards.

<p id="transformation-matrix-computation">
The [=transformation matrix=] is computed from the 'transform' and 'transform-origin' properties as follows:
The [=transformation matrix=] is computed from the 'transform' and 'transform-origin' properties as follows:

1. Start with the identity matrix.
2. Translate by the computed X and Y of 'transform-origin'
3. Multiply by each of the transform functions in 'transform' property from left to right
4. Translate by the negated computed X and Y values of 'transform-origin'
1. Start with the identity matrix.
2. Translate by the computed X and Y of 'transform-origin'
3. Multiply by each of the transform functions in 'transform' property from left to right
4. Translate by the negated computed X and Y values of 'transform-origin'

Transforms apply to [=transformable elements=].

Note: Transformations do affect the visual rendering, but have no affect on the CSS layout other than affecting overflow. Transforms are also taken into account when computing client rectangles exposed via the Element Interface Extensions, namely <a href="https://www.w3.org/TR/cssom-view/#dom-element-getclientrects">getClientRects()</a> and <a href="https://www.w3.org/TR/cssom-view/#dom-element-getboundingclientrect">getBoundingClientRect()</a>, which are specified in [[CSSOM-VIEW]].

<div class="example">
<pre>
div {
transform: translate(100px, 100px);
}</pre>
<pre><code highlight=css>
div {
transform: translate(100px, 100px);
}
</code></pre>

This transform moves the element by 100 pixels in both the X and Y directions.

Expand All @@ -182,13 +191,13 @@ div {
</div>

<div class="example">
<pre>
<pre><code highlight=css>
div {
height: 100px; width: 100px;
transform-origin: 50px 50px;
transform: rotate(45deg);
}
</pre>
</code></pre>

The 'transform-origin' property moves the point of origin by 50 pixels in both the X and Y directions. The transform rotates the element clockwise by 45&deg; about the point of origin. After all transform functions were applied, the translation of the origin gets translated back by -50 pixels in both the X and Y directions.

Expand All @@ -198,28 +207,35 @@ The 'transform-origin' property moves the point of origin by 50 pixels in both t
</div>

<div class="example">
<pre>
<pre><code highlight=css>
div {
height: 100px; width: 100px;
transform: translate(80px, 80px) scale(1.5, 1.5) rotate(45deg);
}
</pre>
</code></pre>

This transformation translates the local coordinate system by 80 pixels in both the X and Y directions, then applies a 150% scale, then a 45&deg; clockwise rotation about the Z axis. The impact on the rendering of the element can be intepreted as an application of these transforms in reverse order: the elements is rotated, then scaled, then translated.
The visual appareance is as if the <a element>div</a> element gets translated by 80px to the bottom left direction, then scaled up by 150% and finally rotated by 45&deg;.

<div class="figure">
<img src="examples/compound_transform.svg" alt="The transform specified above" width="270" height="270">
</div>
Each <<transform-function>> can get represented by a corresponding 4x4 matrix. To map a point from the coordinate space of the <a element>div</a> box to the coordinate space of the parent element, these transforms get multiplied in the reverse order:
1. The rotation matrix gets multiplied with the scale matrix.
2. The result of the previous multiplication is then multiplied with the translation matrix to create the accumulated transformation matrix.
3. Finally, the point to map gets pre-multiplied with the accumulated transformation matrix.

For more details see <a href="#transform-function-lists">The Transform Function Lists</a>.

<div class="figure">
<img src="examples/compound_transform.svg" alt="The transform specified above" width="270" height="270">
</div>

Note that an identical rendering can be obtained by nesting elements with the equivalent transforms:
Note: The identical rendering can be obtained by nesting elements with the equivalent transforms:

<pre>
<pre><code highlight=html>
&lt;div style="transform: translate(80px, 80px)">
&lt;div style="transform: scale(1.5, 1.5)">
&lt;div style="transform: rotate(45deg)">&lt;/div>
&lt;/div>
&lt;/div>
</pre>
</code></pre>
</div>

For elements whose layout is governed by the CSS box model, the transform property does not affect the flow of the content surrounding the transformed element. However, the extent of the overflow area takes into account transformed elements. This behavior is similar to what happens when elements are offset via relative positioning. Therefore, if the value of the 'overflow' property is ''overflow/scroll'' or ''overflow/auto'', scrollbars will appear as needed to see content that is transformed outside the visible area. Specifically, transforms can extend (but do not shrink) the size of the overflow area, which is computed as the union of the bounds of the elements before and after the application of transforms.
Expand Down Expand Up @@ -312,11 +328,11 @@ If two or more values are defined and either no value is a keyword, or the only
</dl>

For SVG elements without associated CSS layout box the initial [=used value=] is ''0 0'' as if the user agent style sheet contained:
<pre>
<pre><code highlight=css>
*:not(svg), *:not(foreignObject) &gt; svg {
transform-origin: 0 0;
}
</pre>
</code></pre>

The 'transform-origin' property is a <a>resolved value special case</a> property like 'height'. [[!CSSOM]]

Expand Down Expand Up @@ -378,7 +394,7 @@ Since the previously named SVG attributes become presentation attributes, their

This example shows the combination of the 'transform' style property and the <a element-attr for>transform</a> presentation attribute.

<pre>
<pre><code highlight=xml>
&lt;svg xmlns="http://www.w3.org/2000/svg">
&lt;style>
.container {
Expand All @@ -390,7 +406,7 @@ This example shows the combination of the 'transform' style property and the <a
&lt;rect width="100" height="100" fill="blue" />
&lt;/g>
&lt;/svg>
</pre>
</code></pre>

<div class="figure">
<img src="examples/svg-translate1.svg" width="470" height="240" alt="Translated SVG container element.">
Expand Down Expand Up @@ -463,7 +479,7 @@ Issue(w3c/csswg-drafts#893): User coordinate space statement breaks SVG.

The 'transform-origin' property on the pattern in the following example specifies a ''50%'' translation of the origin in the horizontal and vertical dimension. The 'transform' property specifies a translation as well, but in absolute lengths.

<pre>
<pre><code highlight=xml>
&lt;svg xmlns="http://www.w3.org/2000/svg">
&lt;style>
pattern {
Expand All @@ -480,7 +496,7 @@ The 'transform-origin' property on the pattern in the following example specifie

&lt;rect width="200" height="200" fill="url(#pattern-1)" />
&lt;/svg>
</pre>
</code></pre>

An SVG <{pattern}> element doesn't have a bounding box. The [=reference box=] of the referencing <{rect}> element is used instead to solve the relative values of the 'transform-origin' property. Therefore the point of origin will get translated by 100 pixels temporarily to rotate the user space of the <{pattern}> elements content.

Expand All @@ -507,7 +523,7 @@ With this specification, the <{animate}> element and the <{set}> element can ani

The animation effect is post-multiplied to the underlying value for additive <{animate}> animations (see below) instead of added to the underlying value, due to the specific behavior of <<transform-list>> animations.

Issue(w3c/csswg-drafts#909) Clarify post-/pre-multiply column-/row-major order.
Issue(w3c/csswg-drafts#909): Clarify post-/pre-multiply column-/row-major order.

<var ignore=''>From-to</var>, <var ignore=''>from-by</var> and <var ignore=''>by</var> animations are defined in SMIL to be equivalent to a corresponding <var>values</var> animation. However, <var ignore=''>to</var> animations are a mixture of additive and non-additive behavior [[SMIL3]].

Expand Down Expand Up @@ -557,16 +573,16 @@ Note: This paragraph focuses on the requirements of [[SMIL]] and the extension d

<div class="example">

A <var>by</var> animation with a by value v<sub>b</sub> is equivalent to the same animation with a values list with 2 values, the neutral element for addition for the domain of the target attribute (denoted 0) and v<sub>b</sub>, and ''additive="sum"''. [[SMIL3]]
A <var>by</var> animation with a by value v<sub>b</sub> is equivalent to the same animation with a values list with 2 values, the neutral element for addition for the domain of the target attribute (denoted 0) and v<sub>b</sub>, and ''additive&#61;"sum"''. [[SMIL3]]

<pre>
&lt;rect width="100" height="100">
<pre><code highlight=xml>
&lt;rect width="100" height="100">
&lt;animateTransform attributeName="transform" attributeType="XML"
type="scale" by="1" dur="5s" fill="freeze"/>
&lt;/rect>
</pre>
type="scale" by="1" dur="5s" fill="freeze"/>
&lt;/rect>
</code></pre>

The neutral element for addition when performing a <var>by</var> animation with ''type="scale"'' is the value 0. Thus, performing the animation of the example above causes the rectangle to be invisible at time 0s (since the animated transform list value is ''scale(0)''), and be scaled back to its original size at time 5s (since the animated transform list value is ''scale(1)'').
The neutral element for addition when performing a <var>by</var> animation with ''type&#61;"scale"'' is the value 0. Thus, performing the animation of the example above causes the rectangle to be invisible at time 0s (since the animated transform list value is ''scale(0)''), and be scaled back to its original size at time 5s (since the animated transform list value is ''scale(1)'').

</div>

Expand All @@ -579,12 +595,14 @@ The SVG '<a href="https://www.w3.org/TR/SVG/animate.html#TargetAttributes">attri

In this example the gradient transformation of the linear gradient gets animated.

<pre>&lt;linearGradient gradientTransform="scale(2)">
&lt;animate attributeName="gradientTransform" from="scale(2)" to="scale(4)"
dur="3s" additive="sum"/>
&lt;animate attributeName="transform" from="translate(0, 0)" to="translate(100px, 100px)"
dur="3s" additive="sum"/>
&lt;/linearGradient></pre>
<pre><code highlight=xml>
&lt;linearGradient gradientTransform="scale(2)">
&lt;animate attributeName="gradientTransform" from="scale(2)" to="scale(4)"
dur="3s" additive="sum"/>
&lt;animate attributeName="transform" from="translate(0, 0)" to="translate(100px, 100px)"
dur="3s" additive="sum"/>
&lt;/linearGradient>
</code></pre>

The <{linearGradient}> element specifies the <{linearGradient/gradientTransform}> presentation attribute. The two <{animate}> elements address the target attribute <{linearGradient/gradientTransform}> and 'transform'. Even so all animations apply to the same gradient transformation by taking the value of the <{linearGradient/gradientTransform}> presentation attribute, applying the scaling of the first animation and applying the translation of the second animation one after the other.

Expand Down Expand Up @@ -666,32 +684,34 @@ The Transform Function Lists {#transform-function-lists}

If a list of <<transform-function>> is provided, then the net effect is as if each transform function had been specified separately in the order provided. For example,

<pre>
&lt;div style="transform:translate(-10px,-20px) scale(2) rotate(45deg) translate(5px,10px)"/>
</pre>
<pre><code highlight=html>
&lt;div style="transform: translate(-10px,-20px) scale(2) rotate(45deg) translate(5px,10px)"/>
</code></pre>

is functionally equivalent to:

<pre>
&lt;div style="transform:translate(-10px,-20px)">
&lt;div style="transform:scale(2)">
&lt;div style="transform:rotate(45deg)">
&lt;div style="transform:translate(5px,10px)">
<pre><code highlight=html>
&lt;div style="transform: translate(-10px,-20px)" id="root">
&lt;div style="transform: scale(2)">
&lt;div style="transform: rotate(45deg)">
&lt;div style="transform: translate(5px,10px)" id="child">
&lt;/div>
&lt;/div>
&lt;/div>
&lt;/div>
</pre>
</code></pre>

That is, in the absence of other styling that affects position and dimensions, a nested set of transforms is equivalent to a single list of transform functions, applied from the top ancestor (with the id <code>root</code>) to the deepest descendant (with the id <code>child</code>). The resulting transform is the matrix multiplication of the list of transforms.

That is, in the absence of other styling that affects position and dimensions, a nested set of transforms is equivalent to a single list of transform functions, applied from the outside in. The resulting transform is the matrix multiplication of the list of transforms.
Issue(w3c/csswg-drafts#909): Backport point mapping from one coordinate space to another from SVG.

If a transform function causes the [=current transformation matrix=] of an object to be non-invertible, the object and its content do not get displayed.

<div class="example">

The object in the following example gets scaled by 0.

<pre>
<pre><code highlight=html>
&lt;style>
.box {
transform: scale(0);
Expand All @@ -701,7 +721,7 @@ The object in the following example gets scaled by 0.
&lt;div class="box">
Not visible
&lt;/div>
</pre>
</code></pre>

The scaling causes a non-invertible CTM for the coordinate space of the div box. Therefore neither the div box, nor the text in it get displayed.

Expand Down Expand Up @@ -772,7 +792,7 @@ Two different types of transform functions that share the same primitive, or tra
The following example describes a transition from ''translateX(100px)'' to ''translateY(100px)'' in 3 seconds on hovering over the div box. Both transform functions derive from the same primitive ''translate()''
and therefore can be interpolated.

<pre>
<pre><code highlight=css>
div {
transform: translateX(100px);
}
Expand All @@ -781,7 +801,7 @@ and therefore can be interpolated.
transform: translateY(100px);
transition: transform 3s;
}
</pre>
</code></pre>

For the time of the transition both transform functions get transformed to the common primitive. ''translateX(100px)'' gets converted to ''translate(100px, 0)'' and ''translateY(100px)'' gets converted to ''translate(0, 100px)''. Both transform functions can then get interpolated numerically.
</div>
Expand All @@ -792,7 +812,7 @@ If both transform functions share a primitive in the two-dimensional space, both

In this example a two-dimensional transform function gets animated to a three-dimensional transform function. The common primitive is ''translate3d()''.

<pre>
<pre><code highlight=css>
div {
transform: translateX(100px);
}
Expand All @@ -801,7 +821,7 @@ In this example a two-dimensional transform function gets animated to a three-di
transform: translateZ(100px);
transition: transform 3s;
}
</pre>
</code></pre>

First ''translateX(100px)'' gets converted to ''translate3d(100px, 0, 0)'' and ''translateZ(100px)'' to ''translate3d(0, 0, 100px)'' respectively. Then both converted transform functions get interpolated numerically.

Expand All @@ -813,27 +833,27 @@ Interpolation of Matrices {#matrix-interpolation}

When interpolating between two matrices, each matrix is decomposed into the corresponding translation, rotation, scale, skew. Each corresponding component of the decomposed matrices gets interpolated numerically and recomposed back to a matrix in a final step.

<div class="example">
In the following example the element gets translated by 100 pixel in both the X and Y directions and rotated by 1170&deg; on hovering. The initial transformation is 45&deg;. With the usage of transition, an author might expect a animated, clockwise rotation by three and a quarter turns (1170&deg;).

<div class="example">
<pre>
&lt;style>
div {
transform: rotate(45deg);
}
div:hover {
transform: translate(100px, 100px) rotate(1215deg);
transition: transform 3s;
}
&lt;/style>
<pre><code highlight=html>
&lt;style>
div {
transform: rotate(45deg);
}
div:hover {
transform: translate(100px, 100px) rotate(1215deg);
transition: transform 3s;
}
&lt;/style>

&lt;div>&lt;/div>
</pre>
</div>
&lt;div>&lt;/div>
</code></pre>

The number of transform functions on the source transform ''rotate(45deg)'' differs from the number of transform functions on the destination transform ''translate(100px, 100px) rotate(1125deg)''. According to the last rule of <a href="#interpolation-of-transforms">Interpolation of Transforms</a>, both transforms must be interpolated by matrix interpolation. With converting the transformation functions to matrices, the information about the three turns gets lost and the element gets rotated by just a quarter turn (90&deg;).

To achieve the three and a quarter turns for the example above, source and destination transforms must fulfill the third rule of <a href="#interpolation-of-transforms">Interpolation of Transforms</a>. Source transform could look like ''translate(0, 0) rotate(45deg)'' for a linear interpolation of the transform functions.
</div>

In the following we differ between the <a href="#interpolation-of-2d-matrices">interpolation of two 2D matrices</a> and the interpolation of two matrices where at least one matrix is not a [=2D matrix=].

Expand Down

0 comments on commit 460fcf2

Please sign in to comment.