如何获取 DOM 元素的变换矩阵?
How to get transform matrix of a DOM element?
如何获取DOM元素的变换矩阵?就像 canvas 上下文一样,我们在 DOM 上有 getTransform() 方法吗?
getComputedStyle($el).transform
应该 return 一个 2 x 3 变换矩阵,如果有任何 2d 变换,如果在 z 轴上也有变换,则为 4 x 4 matrix3d。请参阅 my answer here 以了解如何操作以防万一。
对于嵌套元素,生成的矩阵是从父元素到子元素的矩阵相乘:
<parent>
<firstChild>
<grandChild>
</grandChild>
</firstChild>
</parent>
上面生成的 DOMMatrix 将是:
✝ Mparent • MfirstChild • M孙子
transform 属性中上面的 svg 等价物是:
transform="transformparent transformfirstChildtransform grandChild
因此,从 RIGHT 到 LEFT 逻辑上 应用了转换(应用了孙子的转换首先是它自己)并且它的数学投影是将矩阵从 LEFT 乘以 RIGHT,如 [=60 所示=]✝以上.
不过有一个问题,在 SVG 中,所有变换都相对于 viewBox 的绝对 0,0 原点(左上角),无论 where/what 元素是什么。为此,我们需要一些特殊条件:
1- 使用 transform-origin: 0px 0px;
:
In CSS some transformations, like rotate is relative to the center of the element, to make things easier for people. However this creates the problem of "rotate(Xdeg)" being relative to the element's width/height (default css rotate is around 50% 50%). A default css "rotate(40deg)" on square with 100px side does NOT result in the same matrix as a square with 300px. To NOT incorporate the variable width/height of the element, one thing we can do is to make sure the element uses transform-origin: 0px 0px;
.
2- 只使用transform
,不使用css定位:
Make sure relative/absolute positioned elements do NOT incorporate values that modify the location of the element such as left, top, bot, right ...
because these won't be captured in the element's transformation matrix, unless you manually call el.getBoundingClientRect()
and derive the matrix entries yourself
如果满足这些条件,下面的示例 (also fiddle) 应该可以正常工作。目标是计算红色(父)+ 蓝色(子)方块的组合矩阵,并将 magenta/yellow 与蓝色方块叠加。 Magenta 的矩阵是通过从左到右乘以红色和蓝色的矩阵,从右到左乘以黄色的矩阵来计算的:
/*THIS EXAMPLE WORKS IF THE MECHANISM OF TRANSFORMATION MATRICES
IN CSS DOM IS CLOSE ENOUGH TO SVG,
WHICH MEANS LIKE IN SVG, ROTATE SHOULD NOT
BE AROUND AN ELEMENTS CENTER (default) BUT TO
THE TOP LEFT CORNER OF THE ELEMENT.
IN SVG, ALL TRANFORMATIONS ARE RELATIVE TO THE TOP LEFT
CORNER OF THE VIEWBOX WHICH CANNOT
BE 100% TRANSPORTED TO CSS.
THE CLOSEST I COULD FIND WAS TO
SET TRANSFORM-ORIGIN: 0px 0px WHICH SEEMS TO WORK */
/*There is red parent square and blue child square
inside which both have cascading transformations.
The goal is to combine their transformation matrices
and make yellow (W) and magenta (U) squares
superimpose with the blue square*/
//get all the elements in variables
const [elX, elY, elU, elW] = [...document.getElementsByTagName("div")];
//make a regexp to extract the entries of the matrix, with match, it returns [a, b, c, d, e, f]
const rgx = /(-?[0-9]+(?:\.[0-9]+)?)/gi;
//DOMMatrices expect an array of 6 numbers for 2D transformations
//get the matrix of big red parent square
const matrix_elX = new DOMMatrix(getComputedStyle(elX).transform.match(rgx));
//get the matrix of the small blue child square
const matrix_elY = new DOMMatrix(getComputedStyle(elY).transform.match(rgx));
/*
'result' is MX * MY and 'result2' is MY * MX
magenta square U uses result,
yellow square W uses result2,
magenta square U correctly superimposes with blue square,
but yellow square W does NOT,
therefore MX * MY and MY * MX are not the same, as you can expect that matrice multiplication is NOT commutative
Below uses DOMMatrix.fromMatrix is create clones of the matrices because operations does mutate the matrix itself.
*/
let result = DOMMatrix.fromMatrix(matrix_elY).preMultiplySelf(DOMMatrix.fromMatrix(matrix_elX)),
result2 = DOMMatrix.fromMatrix(matrix_elX).preMultiplySelf(DOMMatrix.fromMatrix(matrix_elY));
elU.style.transform = result;
elW.style.transform = result2;
* {
margin: 0px;
box-sizing: border-box;
transform-origin: 0px 0px;
}
#x {
position: relative;
background: red;
width: 300px;
height: 300px;
transform: translate(-20px, -20px) rotate(-30deg) translate(150px, 100px) rotate(20deg);
}
#y {
position: relative;
background: blue;
width: 100px;
height: 100px;
transform: rotate(-5deg) rotate(10deg) translate(200px, 50px) rotate(75deg) translate(35px, 10px) rotate(-25deg);
}
#u {
position: absolute;
top: 0px;
width: 100px;
height: 100px;
background: magenta;
opacity: 0.5;
}
#w {
position: absolute;
top: 0px;
width: 100px;
height: 100px;
background: yellow;
opacity: 0.5;
}
<div id="x">
<div id="y">
</div>
</div>
<div id="u">U</div>
<div id="w">W</div>
如何获取DOM元素的变换矩阵?就像 canvas 上下文一样,我们在 DOM 上有 getTransform() 方法吗?
getComputedStyle($el).transform
应该 return 一个 2 x 3 变换矩阵,如果有任何 2d 变换,如果在 z 轴上也有变换,则为 4 x 4 matrix3d。请参阅 my answer here 以了解如何操作以防万一。
对于嵌套元素,生成的矩阵是从父元素到子元素的矩阵相乘:
<parent>
<firstChild>
<grandChild>
</grandChild>
</firstChild>
</parent>
上面生成的 DOMMatrix 将是:
✝ Mparent • MfirstChild • M孙子
transform 属性中上面的 svg 等价物是:
transform="transformparent transformfirstChildtransform grandChild
因此,从 RIGHT 到 LEFT 逻辑上 应用了转换(应用了孙子的转换首先是它自己)并且它的数学投影是将矩阵从 LEFT 乘以 RIGHT,如 [=60 所示=]✝以上.
不过有一个问题,在 SVG 中,所有变换都相对于 viewBox 的绝对 0,0 原点(左上角),无论 where/what 元素是什么。为此,我们需要一些特殊条件:
1- 使用 transform-origin: 0px 0px;
:
In CSS some transformations, like rotate is relative to the center of the element, to make things easier for people. However this creates the problem of "rotate(Xdeg)" being relative to the element's width/height (default css rotate is around 50% 50%). A default css "rotate(40deg)" on square with 100px side does NOT result in the same matrix as a square with 300px. To NOT incorporate the variable width/height of the element, one thing we can do is to make sure the element uses
transform-origin: 0px 0px;
.
2- 只使用transform
,不使用css定位:
Make sure relative/absolute positioned elements do NOT incorporate values that modify the location of the element such as
left, top, bot, right ...
because these won't be captured in the element's transformation matrix, unless you manually callel.getBoundingClientRect()
and derive the matrix entries yourself
如果满足这些条件,下面的示例 (also fiddle) 应该可以正常工作。目标是计算红色(父)+ 蓝色(子)方块的组合矩阵,并将 magenta/yellow 与蓝色方块叠加。 Magenta 的矩阵是通过从左到右乘以红色和蓝色的矩阵,从右到左乘以黄色的矩阵来计算的:
/*THIS EXAMPLE WORKS IF THE MECHANISM OF TRANSFORMATION MATRICES
IN CSS DOM IS CLOSE ENOUGH TO SVG,
WHICH MEANS LIKE IN SVG, ROTATE SHOULD NOT
BE AROUND AN ELEMENTS CENTER (default) BUT TO
THE TOP LEFT CORNER OF THE ELEMENT.
IN SVG, ALL TRANFORMATIONS ARE RELATIVE TO THE TOP LEFT
CORNER OF THE VIEWBOX WHICH CANNOT
BE 100% TRANSPORTED TO CSS.
THE CLOSEST I COULD FIND WAS TO
SET TRANSFORM-ORIGIN: 0px 0px WHICH SEEMS TO WORK */
/*There is red parent square and blue child square
inside which both have cascading transformations.
The goal is to combine their transformation matrices
and make yellow (W) and magenta (U) squares
superimpose with the blue square*/
//get all the elements in variables
const [elX, elY, elU, elW] = [...document.getElementsByTagName("div")];
//make a regexp to extract the entries of the matrix, with match, it returns [a, b, c, d, e, f]
const rgx = /(-?[0-9]+(?:\.[0-9]+)?)/gi;
//DOMMatrices expect an array of 6 numbers for 2D transformations
//get the matrix of big red parent square
const matrix_elX = new DOMMatrix(getComputedStyle(elX).transform.match(rgx));
//get the matrix of the small blue child square
const matrix_elY = new DOMMatrix(getComputedStyle(elY).transform.match(rgx));
/*
'result' is MX * MY and 'result2' is MY * MX
magenta square U uses result,
yellow square W uses result2,
magenta square U correctly superimposes with blue square,
but yellow square W does NOT,
therefore MX * MY and MY * MX are not the same, as you can expect that matrice multiplication is NOT commutative
Below uses DOMMatrix.fromMatrix is create clones of the matrices because operations does mutate the matrix itself.
*/
let result = DOMMatrix.fromMatrix(matrix_elY).preMultiplySelf(DOMMatrix.fromMatrix(matrix_elX)),
result2 = DOMMatrix.fromMatrix(matrix_elX).preMultiplySelf(DOMMatrix.fromMatrix(matrix_elY));
elU.style.transform = result;
elW.style.transform = result2;
* {
margin: 0px;
box-sizing: border-box;
transform-origin: 0px 0px;
}
#x {
position: relative;
background: red;
width: 300px;
height: 300px;
transform: translate(-20px, -20px) rotate(-30deg) translate(150px, 100px) rotate(20deg);
}
#y {
position: relative;
background: blue;
width: 100px;
height: 100px;
transform: rotate(-5deg) rotate(10deg) translate(200px, 50px) rotate(75deg) translate(35px, 10px) rotate(-25deg);
}
#u {
position: absolute;
top: 0px;
width: 100px;
height: 100px;
background: magenta;
opacity: 0.5;
}
#w {
position: absolute;
top: 0px;
width: 100px;
height: 100px;
background: yellow;
opacity: 0.5;
}
<div id="x">
<div id="y">
</div>
</div>
<div id="u">U</div>
<div id="w">W</div>