Title: CSS Transforms Module Level 2 Shortname: css-transforms Level: 2 Status: ED Work Status: Exploring Group: csswg ED: https://drafts.csswg.org/css-transforms-2/ Editor: Tab Atkins Jr., Google Inc http://google.com, http://xanthir.com/contact/, w3cid 42199 Editor: Simon Fraser, Apple Inc http://www.apple.com/, simon.fraser@apple.com, w3cid 44066 Editor: Dean Jackson, Apple Inc http://www.apple.com/, dino@apple.com, w3cid 42080 Editor: Theresa O'Connor, Apple Inc http://www.apple.com/, eoconnor@apple.com, w3cid 40614 Abstract: CSS transforms allows elements styled with CSS to be transformed in two-dimensional or three-dimensional space. Abstract: Abstract: This spec add new tranform functions and properties for three-dimensional transforms, and convenience functions for simple transforms. Ignored Terms: SVG data types
spec:css-transforms-1; type:property; text:transform text:transform-origin type:dfn; text: transformation matrix text: transformable element text: transformed element text: 2d matrix text: reference box type: type; text:Introduction {#intro} ===================== This specification is a delta spec that extends [[!css-transforms-1]] to allow authors to transform elements in three-dimensional space. New transform functions for the 'transform' property allow three-dimensional transforms, and additional properties make working with three-dimensional transforms easier, and allow the author to control how nested three-dimensional transformed elements interact. 1. The 'perspective' property allows the author to make child elements with three-dimensional transforms appear as if they live in a common three-dimensional space. The 'perspective-origin' property provides control over the origin at which perspective is applied, effectively changing the location of the "vanishing point". 2. The 'transform-style' property allows 3D-transformed elements and their 3D-transformed descendants to share a common three-dimensional space, allowing the construction of hierarchies of three-dimensional objects. 3. The 'backface-visibility' property comes into play when an element is flipped around via three-dimensional transforms such that its reverse side is visible to the viewer. In some situations it is desirable to hide the element in this situation, which is possible using the value of ''backface-visibility/hidden'' for this property. Note: While some values of the 'transform' property allow an element to be transformed in a three-dimensional coordinate system, the elements themselves are not three-dimensional objects. Instead, they exist on a two-dimensional plane (a flat surface) and have no depth. This specification also adds three convenience properties, 'scale', 'translate' and 'rotate', that make it easier to describe and animate simple transforms. Module Interactions {#module-interactions} ------------------------------------------ The 3D transform functions here extend the set of functions for the 'transform' property. Some values of 'perspective', 'transform-style' and 'backface-visibility' result in the creation of a [=containing block for all descendants=], and/or the creation of a stacking context. Three-dimensional transforms affect the visual layering of elements, and thus override the back-to-front painting order described in Appendix E of [[!CSS21]]. Terminology {#terminology} ========================== : 3D transformed element :: An element whose computed value for the 'transform' property includes one of the 3D transform functions : 3D matrix :: A 4x4 matrix which does not fulfill the requirements of an <<2D matrix>>. : identity transform function :: In addition to the identity transform function in CSS Transforms, examples for identity transform functions include ''translate3d(0, 0, 0)'', ''translateZ(0)'', ''scaleZ(1)'', ''rotate3d(1, 1, 1, 0)'', ''rotateX(0)'', ''rotateY(0)'', ''rotateZ(0)'' and ''matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1)''. A special case is perspective: ''perspective(infinity)''. The value of m34 becomes infinitesimal small and the transform function is therefore assumed to be equal to the identity matrix. : perspective matrix :: A matrix computed from the values of the 'perspective' and 'perspective-origin' properties as described below. : accumulated 3D transformation matrix :: A matrix computed for an element relative to the root of its 3D rendering context, as described below. : 3D rendering context :: A set of elements with a common ancestor which share a common three-dimensional coordinate system, as described below. Serialization of the computed value of <type: function; text: matrix() spec:css2; type: dfn; text: stacking context spec: infra type:dfn; text: list spec: filter-effects-1; type:property; text:filter; spec: html; type: element; text: a;
3x3 matrix for two-dimensional transformations.
div { transform: scale(2) rotate(45deg); transform: scale(2) rotate3d(0, 0, 1, 45deg); }With 3D support, the second definition will override the first one. Without 3D support, the second definition is invalid and a UA falls back to the first definition.
Demonstration of the initial coordinate space.
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, Y and Z 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, Y and Z values of 'transform-origin' 3D Transform Rendering {#3d-transform-rendering} ---------------------- Normally, elements render as flat planes, and are rendered into the same plane as their containing block. Often this is the plane shared by the rest of the page. Two-dimensional transform functions can alter the appearance of an element, but that element is still rendered into the same plane as its containing block. Three-dimensional transforms can result in transformation matrices with a non-zero Z component (where the Z axis projects out of the plane of the screen). This can result in an element rendering on a different plane than that of its containing block. This may affect the front-to-back rendering order of that element relative to other elements, as well as causing it to intersect with other elements.
<style> div { height: 150px; width: 150px; } .container { border: 1px solid black; } .transformed { transform: rotateY(50deg); } </style> <div class="container"> <div class="transformed"></div> </div>
Diagrams showing how scaling depends on the 'perspective' property and Z position. In the top diagram, Z is half of d. In order to make it appear that the original circle (solid outline) appears at Z (dashed circle), the circle is scaled up by a factor of two, resulting in the light blue circle. In the bottom diagram, the circle is scaled down by a factor of one-third to make it appear behind the original position.
Diagram showing the effect of moving the perspective origin upward.
The perspective matrix is computed as follows:
<style> div { height: 150px; width: 150px; } .container { perspective: 500px; border: 1px solid black; } .transformed { transform: rotateY(50deg); } </style> <div class="container"> <div class="transformed"></div> </div>
<style> .container { background-color: rgba(0, 0, 0, 0.3); perspective: 500px; } .container > div { position: absolute; left: 0; } .container > :first-child { transform: rotateY(45deg); background-color: orange; top: 10px; height: 135px; } .container > :last-child { transform: translateZ(40px); background-color: rgba(0, 0, 255, 0.6); top: 50px; height: 100px; } </style> <div class="container"> <div></div> <div></div> </div>This example shows show elements in a 3D rendering context can intersect. The container element establishes a 3D rendering context for itself and its two children. The children intersect with each other, and the orange element also intersects with the container.
<style> div { height: 150px; width: 150px; } .container { perspective: 500px; border: 1px solid black; } .transformed { transform: rotateY(50deg); background-color: blue; } .child { transform-origin: top left; transform: rotateX(40deg); background-color: lime; } </style> <div class="container"> <div class="transformed"> <div class="child"></div> </div> </div>This example shows how nested 3D transforms are rendered. The blue div is transformed as in the previous example, with its rendering influenced by the perspective on its parent element. The lime element also has a 3D transform, which is a rotation about the X axis (anchored at the top, by virtue of the transform-origin). However, the lime element is being rendered into the plane of its parent because it is not a member of the same 3D rendering context; the parent is [=flattening element|flattening=]. Thus the lime element only appears shorter; it does not "pop out" of the blue element.
<style> div { height: 150px; width: 150px; } .container { perspective: 500px; border: 1px solid black; } .transformed { transform-style: preserve-3d; transform: rotateY(50deg); background-color: blue; } .child { transform-origin: top left; transform: rotateX(40deg); background-color: lime; } </style>This example is identical to the previous example, with the addition of ''transform-style: preserve-3d'' on the blue element. The blue element now extends the 3D rendering context of its container. Now both blue and lime elements share a common three-dimensional space, so the lime element renders as tilting out from its parent, influenced by the perspective on the container.
<style> .body { perspective: 500px; } #card { position: relative; height: 300px; width: 200px; transition: transform 1s; transform-style: preserve-3d; } #card.flipped { transform: rotateY(180deg); } .face { position: absolute; top: 0; left: 0; width: 100%; height: 100%; background-color: silver; border-radius: 40px; backface-visibility: hidden; } .back { transform: rotateY(180deg); } </style> <div id="card" onclick="this.classList.toggle('flipped')"> <div class="front face">Front</div> <div class="back face">Back</div> </div>
.transformed { height: 100px; width: 100px; background: lime; transform: perspective(50px) translateZ(100px); }All of the box's corners have z-coordinates greater than the perspective. This means that the box is behind the viewer and will not display. Mathematically, the point (x, y) first becomes (x, y, 0, 1), then is translated to (x, y, 100, 1), and then applying the perspective results in (x, y, 100, −1). The w-coordinate is negative, so it does not display. An implementation that doesn't handle the w < 0 case separately might incorrectly display this point as (−x, −y, −100), dividing by −1 and mirroring the box.
.transformed { height: 100px; width: 100px; background: radial-gradient(yellow, blue); transform: perspective(50px) translateZ(50px); }Here, the box is translated upward so that it sits at the same place the viewer is looking from. This is like bringing the box closer and closer to one's eye until it fills the entire field of vision. Since the default transform-origin is at the center of the box, which is yellow, the screen will be filled with yellow. Mathematically, the point (x, y) first becomes (x, y, 0, 1), then is translated to (x, y, 50, 1), then becomes (x, y, 50, 0) after applying perspective. Relative to the transform-origin at the center, the upper-left corner was (−50, −50), so it becomes (−50, −50, 50, 0). This is transformed to something very far to the upper left, such as (−5000, −5000, 5000). Likewise the other corners are sent very far away. The radial gradient is stretched over the whole box, now enormous, so the part that's visible without scrolling should be the color of the middle pixel: yellow. However, since the box is not actually infinite, the user can still scroll to the edges to see the blue parts.
.transformed { height: 50px; width: 50px; background: lime; border: 25px solid blue; transform-origin: left; transform: perspective(50px) rotateY(-45deg); }The box will be rotated toward the viewer, with the left edge staying fixed while the right edge swings closer. The right edge will be at about z = ''70.7px'', which is closer than the perspective of ''50px''. Therefore, the rightmost edge will vanish ("behind" the viewer), and the visible part will stretch out infinitely far to the right. Mathematically, the top right vertex of the box was originally (100, −50), relative to the transform-origin. It is first expanded to (100, −50, 0, 1). After applying the transform specified, this will get mapped to about (70.71, −50, 70.71, −0.4142). This has w = −0.4142 < 0, so we need to slice away the part of the box with w < 0. This results in the new top-right vertex being (50, −50, 50, 0). This is then mapped to some faraway point in the same direction, such as (5000, −5000, 5000), which is up and to the right from the transform-origin. Something similar is done to the lower right corner, which gets mapped far down and to the right. The resulting box stretches far past the edge of the screen. Again, the rendered box is still finite, so the user can scroll to see the whole thing if he or she chooses. However, the right part has been chopped off. No matter how far the user scrolls, the rightmost ''30px'' or so of the original box will not be visible. The blue border was only ''25px'' wide, so it will be visible on the left, top, and bottom, but not the right. The same basic procedure would apply if one or three vertices had w < 0. However, in that case the result of truncating the w < 0 part would be a triangle or pentagon instead of a quadrilateral.
Name: translate Value: none | <The 'translate' property accepts 1-3 values, each specifying a translation against one axis, in the order X, Y, then Z. If only one or two values are given, this specifies a 2d translation, equivalent to the ''translate()'' function. If the second value is missing, it defaults to ''0px''. If three values are given, this specifies a 3d translation, equivalent to the ''translate3d()'' function.> [ < > < >? ]? Initial: none Applies to: transformable elements Inherited: no Percentages: relative to the width of the containing block (for the first value) or the height (for the second value) Computed Value: the keyword ''translate/none'' or a pair of computed < > values and optionally an absolute length Animation type: by computed value, adding a third ''0'' value if needed to match components, but see below for ''translate/none''
Name: rotate Value: none | <The 'rotate' property accepts an angle to rotate an element, and optionally an axis to rotate it around. If the axis is omitted, this specifies a 2d rotation, equivalent to the ''rotate()'' function. Otherwise, it specifies a 3d rotation: if x, y, or z is given, it specifies a rotation around that axis, equivalent to the ''rotateX()''/etc 3d transform functions. Alternately, the axis can be specified explicitly by giving three numbers representing the x, y, and z components of an origin-centered vector, equivalent to the ''rotate3d()'' function. Note: While ''rotate: 30deg;'' and ''rotate: z 30deg;'' technically specify the same rotation, the first declaration is a 2d transform equivalent to ''transform: rotate(30deg);'', while the second is a 3d transform equivalent to ''transform: rotateZ(30deg);'', which can have additional side-effects in UAs.> | [ x | y | z | < >{3} ] && < > Initial: none Applies to: transformable elements Inherited: no Computed value: the keyword ''rotate/none'', or an < > with an optional axis consisting of a list of three < >s Animation type: as SLERP, but see below for ''rotate/none''
Name: scale Value: none | <The 'scale' property accepts 1-3 values, each specifying a scale along one axis, in order X, Y, then Z. If only the X value is given, the Y value defaults to the same value. If one or two values are given, this specifies a 2d scaling, equivalent to the ''scale()'' function. If three values are given, this specifies a 3d scaling, equivalent to the ''scale3d()'' function. ---- All three properties accept (and default to) the value none, which produces no transform at all. In particular, this value does not trigger the creation of a stacking context or [=containing block for all descendants=], while all other values (including “identity” transforms like ''translate: 0px'') create a stacking context and [=containing block for all descendants=], per usual for transforms. When 'translate', 'rotate' or 'scale' are animating or transitioning, and the from value or to value (but not both) is ''translate/none'', the value ''translate/none'' is replaced by the equivalent identity value (''0px'' for translate, ''0deg'' for rotate, ''1'' for scale). Serialization {#individual-transform-serialization} --------------------------------------------------- Because these properties have three distinct modes of behavior (no transform, 2d transform, or 3d transform), serialization must take this into account: : for 'translate' :: If a 2d translation is specified, the property must serialize with only one or two values (per usual, if the second value is ''0px'', the default, it must be omitted when serializing). If a 3d translation is specified, all three values must be serialized. It must serialize as the keyword ''translate/none'' if and only if ''translate/none'' was originally specified. (An identity transform does not count; it must serialize as the 2d or 3d version, as appropriate.) : for 'rotate' :: If a 2d rotation is specified, the property must serialize as just an <>{1,3} Initial: none Applies to: transformable elements Inherited: no Computed value: the keyword ''scale/none'', or a list of 2 or 3 < >s Animation type: by computed value, but see below for ''scale/none''
Name: transform-style Value: auto | flat | preserve-3d Initial: auto Applies to: transformable elements Inherited: no Percentages: N/A Computed value: specified keyword Animation type: discreteA value of "flat" for 'transform-style' establishes a stacking context, and establishes a 3D rendering context. Elements with a used value of "auto" are ignored for the purposes of 3D rendering context computation, and those with a used value of "preserve-3d" extend the 3D rendering context to which they belong, even if values for the ''transform'' or ''perspective'' properties would otherwise cause flattening. A value of "preserve-3d" establishes a stacking context, and a [=containing block for all descendants=]. Grouping property values {#grouping-property-values} ------------------------ The following CSS property values require the user agent to create a flattened representation of the descendant elements before they can be applied, and therefore force the used value of ''transform-style'' to ''flat'': * 'overflow': any value other than ''overflow/visible'' or ''overflow/clip''. * 'opacity': any value less than 1. * 'filter': any value other than ''filter/none''. * 'clip': any value other than ''clip/auto''. * 'clip-path': any value other than ''clip-path/none''. * 'isolation': used value of ''isolation/isolate''. * 'mask-image': any value other than ''mask-image/none''. * 'mask-border-source': any value other than ''mask-border-source/none''. * 'mix-blend-mode': any value other than ''mix-blend-mode/normal''. The following CSS property values cause an ''transform-style/auto'' value of ''transform-style'' to become ''transform-style/flat'': * 'transform': any value other than ''transform/none''. * 'perspective': any value other than ''perspective/none''. In both cases the computed value of 'transform-style' is not affected. Issue: Having overflow imply transform-style: flat causes every element with non-visible/clip overflow to become a stacking context, which is unwanted. See Bug 28252. The 'perspective' Property {#perspective-property} ==========================
Name: perspective Value: none | <Where <> Initial: none Applies to: transformable elements Inherited: no Percentages: N/A Computed value: the keyword ''perspective/none'' or an absolute length Animation type: by computed value
Name: perspective-origin Value: <The values of the 'perspective' and 'perspective-origin' properties are used to compute the perspective matrix, as described above. The values for 'perspective-origin' represent an offset of the perspective origin from the top left corner of the reference box.> Initial: 50% 50% Applies to: transformable elements Inherited: no Percentages: refer to the size of the reference box Computed value: see 'background-position' Animation type: by computed value
Name: backface-visibility Value: visible | hidden Initial: visible Applies to: transformable elements Inherited: no Percentages: N/A Computed value: specified keyword Animation type: discreteThe visibility of an element with ''backface-visibility: hidden'' is determined as follows: 1. Compute the element's accumulated 3D transformation matrix. 2. If the component of the matrix in row 3, column 3 is negative, then the element should be hidden. Otherwise it is visible. Issue: Backface-visibility cannot be tested by only looking at m33. See Bug 23014. Note: The reasoning for this definition is as follows. Assume elements are rectangles in the x–y plane with infinitesimal thickness. The front of the untransformed element has coordinates like (x, y, ε), and the back is (x, y, −ε), for some very small ε. We want to know if after the transformation, the front of the element is closer to the viewer than the back (higher z-value) or further away. The z-coordinate of the front will be m13x + m23y + m33ε + m43, before accounting for perspective, and the back will be m13x + m23y − m33ε + m43. The first quantity is greater than the second if and only if m33 > 0. (If it equals zero, the front and back are equally close to the viewer. This probably means something like a 90-degree rotation, which makes the element invisible anyway, so we don't really care whether it vanishes.) SVG and 3D transform functions {#svg-three-dimensional-functions} ============================== This specification explicitly requires three-dimensional transform functions to apply to the container elements: <{a}>, <{g}>, <{svg}>, all graphics elements, all graphics referencing elements and the SVG <{foreignObject}> element. Three-dimensional transform functions and the properties 'perspective', 'perspective-origin', 'transform-style' and 'backface-visibility' can not be used for the elements: <{clipPath}>, <{linearGradient}>, <{radialGradient}> and <{pattern}>. If a transform list includes a three-dimensional transform function, the complete transform list must be ignored. The values of every previously named property must be ignored. Transformable elements that are contained by one of these elements can have three-dimensional transform functions. The <{clipPath}>, <{mask}>, <{pattern}> elements require the user agent to create a flattened representation of the descendant elements before they can be applied, and therefore override the behavior of ''transform-style: preserve-3d''. If the 'vector-effect' property is set to ''non-scaling-stroke'' and an object is within a 3D rendering context the property has no affect on stroking the object. The Transform Functions {#transform-functions} ======================= The value of the 'transform' property is a list of <transform-function>. The set of allowed transform functions is given below. Wherever <
For derived transform functions that have a two-dimensional primitive and a three-dimensional primitive, the context decides about the used primitive. See Interpolation of primitives and derived transform functions. Interpolation of Matrices {#matrix-interpolation} ================================================= When interpolating between two matrices, each matrix is decomposed into the corresponding translation, rotation, scale, skew and (for a 3D matrix) perspective values. Each corresponding component of the decomposed matrices gets interpolated numerically and recomposed back to a matrix in a final step. Interpolation of 3D matrices {#interpolation-of-3d-matrices} ---------------------------- ### Decomposing a 3D matrix ### {#decomposing-a-3d-matrix} The pseudo code below is based upon the "unmatrix" method in "Graphics Gems II, edited by Jim Arvo", but modified to use Quaternions instead of Euler angles to avoid the problem of Gimbal Locks. The following pseudocode works on a 4x4 homogeneous matrix:
Input: matrix ; a 4x4 matrix Output: translation ; a 3 component vector scale ; a 3 component vector skew ; skew factors XY,XZ,YZ represented as a 3 component vector perspective ; a 4 component vector quaternion ; a 4 component vector Returns false if the matrix cannot be decomposed, true if it can // Normalize the matrix. if (matrix[3][3] == 0) return false for (i = 0; i < 4; i++) for (j = 0; j < 4; j++) matrix[i][j] /= matrix[3][3] // perspectiveMatrix is used to solve for perspective, but it also provides // an easy way to test for singularity of the upper 3x3 component. perspectiveMatrix = matrix for (i = 0; i < 3; i++) perspectiveMatrix[i][3] = 0 perspectiveMatrix[3][3] = 1 if (determinant(perspectiveMatrix) == 0) return false // First, isolate perspective. if (matrix[0][3] != 0 || matrix[1][3] != 0 || matrix[2][3] != 0) // rightHandSide is the right hand side of the equation. rightHandSide[0] = matrix[0][3] rightHandSide[1] = matrix[1][3] rightHandSide[2] = matrix[2][3] rightHandSide[3] = matrix[3][3] // Solve the equation by inverting perspectiveMatrix and multiplying // rightHandSide by the inverse. inversePerspectiveMatrix = inverse(perspectiveMatrix) transposedInversePerspectiveMatrix = transposeMatrix4(inversePerspectiveMatrix) perspective = multVecMatrix(rightHandSide, transposedInversePerspectiveMatrix) else // No perspective. perspective[0] = perspective[1] = perspective[2] = 0 perspective[3] = 1 // Next take care of translation for (i = 0; i < 3; i++) translate[i] = matrix[3][i] // Now get scale and shear. 'row' is a 3 element array of 3 component vectors for (i = 0; i < 3; i++) row[i][0] = matrix[i][0] row[i][1] = matrix[i][1] row[i][2] = matrix[i][2] // Compute X scale factor and normalize first row. scale[0] = length(row[0]) row[0] = normalize(row[0]) // Compute XY shear factor and make 2nd row orthogonal to 1st. skew[0] = dot(row[0], row[1]) row[1] = combine(row[1], row[0], 1.0, -skew[0]) // Now, compute Y scale and normalize 2nd row. scale[1] = length(row[1]) row[1] = normalize(row[1]) skew[0] /= scale[1]; // Compute XZ and YZ shears, orthogonalize 3rd row skew[1] = dot(row[0], row[2]) row[2] = combine(row[2], row[0], 1.0, -skew[1]) skew[2] = dot(row[1], row[2]) row[2] = combine(row[2], row[1], 1.0, -skew[2]) // Next, get Z scale and normalize 3rd row. scale[2] = length(row[2]) row[2] = normalize(row[2]) skew[1] /= scale[2] skew[2] /= scale[2] // At this point, the matrix (in rows) is orthonormal. // Check for a coordinate system flip. If the determinant // is -1, then negate the matrix and the scaling factors. pdum3 = cross(row[1], row[2]) if (dot(row[0], pdum3) < 0) for (i = 0; i < 3; i++) scale[i] *= -1; row[i][0] *= -1 row[i][1] *= -1 row[i][2] *= -1 // Now, get the rotations out quaternion[0] = 0.5 * sqrt(max(1 + row[0][0] - row[1][1] - row[2][2], 0)) quaternion[1] = 0.5 * sqrt(max(1 - row[0][0] + row[1][1] - row[2][2], 0)) quaternion[2] = 0.5 * sqrt(max(1 - row[0][0] - row[1][1] + row[2][2], 0)) quaternion[3] = 0.5 * sqrt(max(1 + row[0][0] + row[1][1] + row[2][2], 0)) if (row[2][1] > row[1][2]) quaternion[0] = -quaternion[0] if (row[0][2] > row[2][0]) quaternion[1] = -quaternion[1] if (row[1][0] > row[0][1]) quaternion[2] = -quaternion[2] return true### Interpolation of decomposed 3D matrix values ### {#interpolation-of-decomposed-3d-matrix-values} Each component of the decomposed values translation, scale, skew and perspective of the source matrix get linearly interpolated with each corresponding component of the destination matrix. Note: For instance,
translate[0]
of the source matrix and translate[0]
of the destination matrix are interpolated numerically, and the result is used to set the translation of the animating element.
Quaternions of the decomposed source matrix are interpolated with quaternions of the decomposed destination matrix using the spherical linear interpolation (Slerp) as described by the pseudo code below:
Input: quaternionA ; a 4 component vector quaternionB ; a 4 component vector t ; interpolation parameter with 0 <= t <= 1 Output: quaternionDst ; a 4 component vector product = dot(quaternionA, quaternionB) // Clamp product to -1.0 <= product <= 1.0 product = min(product, 1.0) product = max(product, -1.0) if (abs(product) == 1.0) quaternionDst = quaternionA return theta = acos(dot) w = sin(t * theta) * 1 / sqrt(1 - product * product) for (i = 0; i < 4; i++) quaternionA[i] *= cos(t * theta) - product * w quaternionB[i] *= w quaternionDst[i] = quaternionA[i] + quaternionB[i] return### Recomposing to a 3D matrix ### {#recomposing-to-a-3d-matrix} After interpolation, the resulting values are used to transform the elements user space. One way to use these values is to recompose them into a 4x4 matrix. This can be done following the pseudo code below:
Input: translation ; a 3 component vector scale ; a 3 component vector skew ; skew factors XY,XZ,YZ represented as a 3 component vector perspective ; a 4 component vector quaternion ; a 4 component vector Output: matrix ; a 4x4 matrix Supporting functions (matrix is a 4x4 matrix): matrix multiply(matrix a, matrix b) returns the 4x4 matrix product of a * b // apply perspective for (i = 0; i < 4; i++) matrix[i][3] = perspective[i] // apply translation for (i = 0; i < 4; i++) for (j = 0; j < 3; j++) matrix[3][i] += translation[j] * matrix[j][i] // apply rotation x = quaternion[0] y = quaternion[1] z = quaternion[2] w = quaternion[3] // Construct a composite rotation matrix from the quaternion values // rotationMatrix is a identity 4x4 matrix initially rotationMatrix[0][0] = 1 - 2 * (y * y + z * z) rotationMatrix[0][1] = 2 * (x * y - z * w) rotationMatrix[0][2] = 2 * (x * z + y * w) rotationMatrix[1][0] = 2 * (x * y + z * w) rotationMatrix[1][1] = 1 - 2 * (x * x + z * z) rotationMatrix[1][2] = 2 * (y * z - x * w) rotationMatrix[2][0] = 2 * (x * z - y * w) rotationMatrix[2][1] = 2 * (y * z + x * w) rotationMatrix[2][2] = 1 - 2 * (x * x + y * y) matrix = multiply(matrix, rotationMatrix) // apply skew // temp is a identity 4x4 matrix initially if (skew[2]) temp[2][1] = skew[2] matrix = multiply(matrix, temp) if (skew[1]) temp[2][1] = 0 temp[2][0] = skew[1] matrix = multiply(matrix, temp) if (skew[0]) temp[2][0] = 0 temp[1][0] = skew[0] matrix = multiply(matrix, temp) // apply scale for (i = 0; i < 3; i++) for (j = 0; j < 4; j++) matrix[i][j] *= scale[i] returnInterpolation of primitives and derived transform functions {#interpolation-of-transform-functions} =================================================================================================== Two transform functions with the same name and the same number of arguments are interpolated numerically without a former conversion. The calculated value will be of the same transform function type with the same number of arguments. Special rules apply to <
div.animate( { transform: ['scale(1)', 'scale(2)'] }, { duration: 1000, easing: 'ease', } );to produce the expected behavior when extended as follows:
div.animate( { transform: ['scale(1)', 'scale(2)'] }, { duration: 1000, easing: 'ease', iterations: 5, iterationComposite: 'accumulate', } );