Title: CSS Transforms Module Level 2 Shortname: css-transforms Level: 2 Status: ED !Delta Spec: yes Work Status: Exploring Group: csswg ED: https://drafts.csswg.org/css-transforms-2/ TR: https://www.w3.org/TR/css-transforms-2/ Previous Version: https://www.w3.org/TR/2021/WD-css-transforms-2-20211109/ Editor: Tab Atkins Jr., Google https://www.google.com, https://xanthir.com/contact/, w3cid 42199 Editor: L. David Baron, Google https://www.google.com, https://dbaron.org/, w3cid 15393 Editor: Simon Fraser, Apple Inc https://www.apple.com/, simon.fraser@apple.com, w3cid 44066 Editor: Dean Jackson, Apple Inc https://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 adds new transform 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 provide child elements with an extra perspective transformation. 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]]. Value Definitions {#values} ----------------- This specification follows the CSS property definition conventions from [[!CSS21]] using the value definition syntax from [[!CSS-VALUES-3]]. Value types not defined in this specification are defined in CSS Values & Units [[!CSS-VALUES-3]]. Combination with other CSS modules may expand the definitions of these value types. In addition to the property-specific values listed in their definitions, all properties defined in this specification also accept the CSS-wide keywords as their property value. For readability they have not been repeated explicitly. 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(none)''. 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. Resolved value of 'transform' {#serialization-of-the-computed-value} ---------------------------------------------------------------- The 'transform' property is a resolved value special case property. [[!CSSOM]] When the computed value is a <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.
<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: 1. Start with the identity matrix. 2. Translate by the computed X and Y values of 'perspective-origin' 3. Multiply by the matrix that would be obtained from the ''perspective()'' transform function, where the length is provided by the value of the 'perspective' property 4. Translate by the negated computed X and Y values of 'perspective-origin'
<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> div { height: 150px; width: 150px; } .scene { background-color: rgba(0, 0, 0, 0.3); border: 1px solid black; perspective: 500px; } .container { transform-style: preserve-3d; } .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="scene"> <div class="container"> Lorem ipsum dolor sit amet, consectetaur adipisicing elit… <div></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, and the scene element adds perspective to the 3D rendering context. 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. 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 they choose. 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. When the second or third values are missing, they default to ''0px''. If the third value is omitted or zero, this specifies a 2d translation, equivalent to the ''translate()'' function. Otherwise, this specifies a 3d translation, equivalent to the ''translate3d()'' function. Note: The resolved value of the 'translate' property is the computed value, and thus {{Window/getComputedStyle()}} includes percentage values in its results.> [ < > < >? ]? Initial: none Applies to: transformable elements Inherited: no Percentages: relative to the width of the reference box (for the first value) or the height (for the second value) Computed Value: the keyword ''translate/none'' or a pair of computed < > values and an absolute length Animation type: by computed value, 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. The axis can be specified with either the x, y, or z keywords, which specify a rotation around that axis, equivalent to the ''rotateX()'', ''rotateY()'', and ''rotateZ()'' 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. There is no difference in behavior between a rotation specified as an <> | [ x | y | z | < >{3} ] && < > Initial: none Applies to: transformable elements Inherited: no Computed value: the keyword ''rotate/none'', or an < > with an 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 the Y value is not given, then it defaults to being the same as the X value. If the Z value is not given, then it defaults to ''1''. If the third value is omitted, ''1'', or ''100%'', this specifies a 2d scaling, equivalent to the ''scale()'' function. Otherwise, this specifies a 3d scaling, equivalent to the ''scale3d()'' function. There is no difference in behavior between the third value being omitted and the third value being ''1'' or ''100%''. A <> | < > ]{1,3} Initial: none Applies to: transformable elements Inherited: no Computed value: the keyword ''scale/none'', or a list of 3 < >s Animation type: by computed value, but see below for ''scale/none''
The transformation matrix computation is amended to the following:
The transformation matrix is computed from the 'transform', 'transform-origin', 'translate', 'rotate', 'scale', and 'offset' properties as follows:
1. Start with the identity matrix.
2. Translate by the computed X, Y, and Z values of 'transform-origin'.
3. Translate by the computed X, Y, and Z values of 'translate'.
4. Rotate by the computed <
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:
Name: transform-style
Value: flat | preserve-3d
Initial: flat
Applies to: transformable elements
Inherited: no
Percentages: N/A
Computed value: specified keyword
Used value: flat if a grouping property is present, specified keyword otherwise
Animation type: discrete
A computed value of ''transform-style/preserve-3d'' for 'transform-style'
on a transformable element
establishes both a stacking context and
a [=containing block for all descendants=].
If the used value is ''transform-style/preserve-3d''
then it also establishes or extends a 3D rendering context.
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 element to have a used style of ''transform-style/flat'' for ''transform-style/preserve-3d''.
* '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''.
* 'contain': ''contain/paint'' and any other property/value combination that causes [=paint containment=].
Note: this includes any property that affect the [=used value=] of the 'contain' property,
such as ''content-visibility: hidden''.
The 'perspective' Property {#perspective-property}
==========================
Name: perspective
Value: none | <
: <
The use of this property with any value other than ''perspective/none'' establishes a stacking context. It also establishes a [=containing block for all descendants=], just like the 'transform' property does.
Issue: We don't really need to be a stacking context or containing block for perspective, but maybe webcompat means we can't change this.
The values of the 'perspective' and 'perspective-origin' properties are used to compute the perspective matrix, as described above.
The 'perspective-origin' Property {#perspective-origin-property}
=================================
The 'perspective-origin' property establishes the origin for the 'perspective' property. It effectively sets the X and Y position at which the viewer appears to be looking at the children of the element.
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.
: <
The 'perspective-origin' property is a resolved value special case property like
Name: backface-visibility
Value: visible | hidden
Initial: visible
Applies to: transformable elements
Inherited: no
Percentages: N/A
Computed value: specified keyword
Animation type: discrete
A computed value of ''backface-visibility/hidden'' for 'backface-visibility'
on a transformable element that participates in a 3D rendering context
establishes both a stacking context and
a [=containing block for all descendants=].
The 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 #917.
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.
Issue: formally describe the syntax of the 3D transform functions in SVG,
as is done for the 2-D functions.
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 <
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(product)
w = sin(t * theta) / 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]
return
Interpolation 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 <
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 {
transform: translateX(100px);
}
div:hover {
transform: translateY(100px);
transition: transform 3s;
}
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.
div {
transform: translateX(100px);
}
div:hover {
transform: translateZ(100px);
transition: transform 3s;
}
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',
}
);
The SVG 'transform' Attribute {#svg-transform}
=============================
This specification will also introduce the new presentation attributes 'transform-origin', 'perspective', 'perspective-origin', 'transform-style' and 'backface-visibility'.
Values on new introduced presentation attributes get parsed following the syntax rules on SVG Data Types [[!SVG11]].
SVG Animation {#svg-animation}
=============
The <{animate}> and <{set}> element {#svg-animate-element}
-----------------------------------
The introduce presentation attributes 'perspective', 'perspective-origin', 'transform-style' and 'backface-visibility' are animatable. 'transform-style' and 'backface-visibility' are non-additive.
More Issues {#more-issues}
==========================
Issue: Per https://lists.w3.org/Archives/Public/www-style/2015Mar/0371.html,
the WG resolved to add a formula for decomposing a transform into a unified "scale"
(the spec already defines how to decompose it into scaleX/Y/Z),
for use by things like SVG's non-scaling stroke spec.
Formula is defined here.
Security and Privacy Considerations {#priv-sec}
===============================================
This specification introduces no new security or privacy considerations.
Changes
Recent Changes
Substantive changes since 9 November 2021 WD:
None yet
Substantive changes since 3 March 2020 WD: