我正在尝试使用单个rotate3d
函数同时旋转立方体的多个轴,但我得到了一个奇怪的旋转。
如果我使用transform: rotateX(-30deg) rotateY(45deg)
我会得到一个漂亮的角度,但如果我这样做transform: rotate3d(-2, 3, 0, 15deg)
这个角度看起来很奇怪。
* { box-sizing: border-box; }
.scene {
width: 50px;
height: 50px;
}
.cube {
width: 50px;
height: 50px;
margin: 50px;
position: relative;
transform-style: preserve-3d;
transform: rotateX(-30deg) rotateY(45deg); /* works as expected */
/* transform: rotate3d(-2, 3, 0, 15deg); */ /* looks very weird */
transition: transform 1s;
}
.cube__face {
position: absolute;
width: 50px;
height: 50px;
border: 1px solid black;
}
.cube__face--front {
background: hsla( 0, 100%, 50%, 0.7);
transform: rotateY( 0deg) translateZ(25px);
}
.cube__face--right {
background: hsla( 60, 100%, 50%, 0.7);
transform: rotateY( 90deg) translateZ(25px);
}
.cube__face--back {
background: hsla(120, 100%, 50%, 0.7);
transform: rotateY(180deg) translateZ(25px);
}
.cube__face--left {
background: hsla(180, 100%, 50%, 0.7);
transform: rotateY(-90deg) translateZ(25px);
}
.cube__face--top {
background: hsla(240, 100%, 50%, 0.7);
transform: rotateX( 90deg) translateZ(25px);
}
.cube__face--bottom {
background: hsla(300, 100%, 50%, 0.7);
transform: rotateX(-90deg) translateZ(25px);
}
<div class="cube">
<div class="cube__face cube__face--front"></div>
<div class="cube__face cube__face--back"></div>
<div class="cube__face cube__face--right"></div>
<div class="cube__face cube__face--left"></div>
<div class="cube__face cube__face--top"></div>
<div class="cube__face cube__face--bottom"></div>
</div>
我知道我可能需要应用一些数学来使其工作,但我对线性代数一无所知。有没有一个公式来实现我想要的?
编辑 1:
我找到了这个问题,与我的相似,但我仍然无法弄清楚。我尝试了transform: rotate3d(-30, 45, 0, 54deg)
(sqrt(30² + 45²) = 54),它更接近预期结果,但仍然不是我正在寻找的公式。
编辑2:
我找到了一个网站,可以根据任何其他转换函数计算matrix3d
。我的初始变换的结果矩阵是matrix3d(0.707107, 0.353553, 0.612372, 0, 0, 0.866025, -0.5, 0, -0.707107, 0.353553, 0.612372, 0, 0, 0, -100, 1)
,这就像一个魅力!但我需要动态地做到这一点。
我可以用window.getComputedStyle
获取我的元素的当前matrix3d
,并且我在 mdn 文档中找到了一些函数来创建旋转矩阵并将 2 个矩阵相乘。但是,如果我为 X 轴创建一个 90º 的旋转矩阵并将其乘以当前矩阵,然后将生成的矩阵应用于 CSS 转换,则立方体会疯狂地进行转换。我是这样做的:
const matrix = window.getComputedStyle(cube).transform.slice(9, -1).split(', ').map(Number)
const rotation = rotateAroundXAxis(90)
const final = multiplyMatrices(matrix, rotation)
cube.style.transform = `matrix3d(${final.join(', ')})`
我真的很感激这里的任何帮助。
TL;DRrotate3d(0.853553, -1.319479, 0.353553, -0.936325rad)
好的,所以我不知道从
哪里开始,但让我解释一下转换是如何工作的。因此,任何 3D 变换(例如旋转、平移、剪切、缩放等)都可以以 4x4 齐次矩阵的形式表示。(因此 CSSmatrix3d
需要 16 个值)
假设我们有一个 4x4 矩阵T
并且我们想要转换一个点(x, y, z)
以便新点(x', y', z')
。我们可以通过执行以下矩阵乘法来找出新点:
| x' | | T11 T12 T13 T14 | | x |
| y' | = | T21 T22 T23 T24 | x | y |
| z' | | T31 T32 T33 T34 | | z |
| 1 | | T41 T42 T43 T44 | | 1 |
现在,如果转换不涉及任何转换,我们也可以用 3x3 矩阵 (afaik) 来表示这种转换。在这种情况下,如果使用以下矩阵乘法找到新点:
| x' | | T11 T12 T13 | | x |
| y' | = | T21 T22 T23 | x | y |
| z' | | T31 T32 T33 | | z |
好的,现在让我们首先以这种矩阵形式表达rotateX(-30deg) rotateY(45deg)
。我将使用此处给出的Rx(Θ)
和Ry(Θ)
来查找净转换矩阵T
。此外,css 旋转轴/FOR 而不是点,因此-30deg
将被30deg
,并且45deg
将为我们-45deg
,正如它在这里所说的那样。
T = Ry(-45deg) x Rx(30deg) // order of multiplication is important, what happens first is rightmost then things are added on left
= | 0.707107 -0.353553 -0.612372 |
| 0 0.866025 -0.5 |
| 0.707107 0.353553 0.612372 |
≈ | 0.707107 -0.353553 -0.612372 0 | // same as above but 4x4 version
| 0 0.866025 -0.5 0 | // this is what getComputedStyle gives
| 0.707107 0.353553 0.612372 0 |
| 0 0 0 1 |
这里关于 wolfram alpha 的计算
您也可以从计算样式中获取上述T
矩阵。并从这里开始使用它。
现在让我们看看什么是rotate3d
.rotate3d(ux, uy, uz, a)
将旋转该点,保持轴矢量u (ux, uy, uz)
的角度a
。我们需要做的转型是T
.所以现在我们需要以rotate3d
的形式表达广义T
.
我们将使用此公式来找出轴。
| ux | | (0.353553) - (-0.5) | | 0.853553 |
| uy | = | (-0.612372) - (0.707107) | = | -1.319479 |
| uz | | (0) - (-0.353553) | | 0.353553 |
我们将使用此公式来找出角度。
0 = arccos((0.707107 + 0.866025 + 0.612372 - 1) / 2)
= 0.936325 rad // ie -0.936325 rad according to CSS convention
所以最后rotateX(-30deg) rotateY(45deg)
和rotate3d(0.853553, -1.319479, 0.353553, -0.936325rad)
一样
演示:
[...document.querySelectorAll("[name='transform']")]
.forEach(radio => {
radio.addEventListener("change", () => {
let selectedTransform = document.querySelector("[name='transform']:checked").value;
let cubeClasses = document.querySelector(".cube").classList;
cubeClasses.remove("transform-a", "transform-b", "transform-c");
cubeClasses.add(selectedTransform)
})
})
* { box-sizing: border-box; }
.scene {
width: 50px;
height: 50px;
}
.cube {
width: 50px;
height: 50px;
margin: 50px;
position: relative;
transform-style: preserve-3d;
transition: transform 1s;
}
.cube.transform-a {
transform: rotateX(-30deg) rotateY(45deg);
}
.cube.transform-b {
transform: rotate3d(0.853553, -1.319479, 0.353553, -0.936325rad);
}
.cube.transform-c {
transform: matrix3d(0.707107, -0.353553, -0.612372, 0, 0, 0.866025, -0.5, 0, 0.707107, 0.353553, 0.612372, 0, 0, 0, 0, 1);
}
.cube__face {
position: absolute;
width: 50px;
height: 50px;
border: 1px solid black;
}
.cube__face--front {
background: hsla( 0, 100%, 50%, 0.7);
transform: rotateY( 0deg) translateZ(25px);
}
.cube__face--right {
background: hsla( 60, 100%, 50%, 0.7);
transform: rotateY( 90deg) translateZ(25px);
}
.cube__face--back {
background: hsla(120, 100%, 50%, 0.7);
transform: rotateY(180deg) translateZ(25px);
}
.cube__face--left {
background: hsla(180, 100%, 50%, 0.7);
transform: rotateY(-90deg) translateZ(25px);
}
.cube__face--top {
background: hsla(240, 100%, 50%, 0.7);
transform: rotateX( 90deg) translateZ(25px);
}
.cube__face--bottom {
background: hsla(300, 100%, 50%, 0.7);
transform: rotateX(-90deg) translateZ(25px);
}
<div class="cube transform-b">
<div class="cube__face cube__face--front"></div>
<div class="cube__face cube__face--back"></div>
<div class="cube__face cube__face--right"></div>
<div class="cube__face cube__face--left"></div>
<div class="cube__face cube__face--top"></div>
<div class="cube__face cube__face--bottom"></div>
</div>
<label>
<input type="radio" name="transform" value="transform-a"/>
<code>rotateX(-30deg) rotateY(45deg)</code>
</label>
<label><br>
<input type="radio" name="transform" value="transform-b" checked/>
<code>rotate3d(0.853553, -1.319479, 0.353553, -0.936325rad)</code>
</label>
<label><br>
<input type="radio" name="transform" value="transform-c"/>
<code>matrix3d(0.707107, -0.353553, -0.612372, 0, 0, 0.866025, -0.5, 0, 0.707107, 0.353553, 0.612372, 0, 0, 0, 0, 1)</code>
</label>
所有这些复杂性和数学,然后他们说"cSs iS nOt PrOgRaMmInG"cSs iS eAsY" xD :P
这是一个用于计算rotate3d
的香草JS实现:
class Matrix {
constructor(raw) {
this.raw = raw;
}
static ofRotationX(a) {
return new Matrix([
[1, 0, 0],
[0, Math.cos(a), -Math.sin(a)],
[0, Math.sin(a), Math.cos(a)]
])
}
static ofRotationY(a) {
return new Matrix([
[Math.cos(a), 0, Math.sin(a)],
[0, 1, 0],
[-Math.sin(a), 0, Math.cos(a)]
])
}
static ofRotationZ(a) {
return new Matrix([
[Math.cos(a), -Math.sin(a), 0],
[Math.sin(a), Math.cos(a), 0],
[0, 0, 1],
])
}
get trace() {
let { raw } = this;
return raw[0][0] + raw[1][1] + raw[2][2];
}
multiply(matB) {
let { raw: a } = this;
let { raw: b } = matB;
return new Matrix([
[
a[0][0] * b[0][0] + a[0][1] * b[1][0] + a[0][2] * b[2][0],
a[0][0] * b[0][1] + a[0][1] * b[1][1] + a[0][2] * b[2][1],
a[0][0] * b[0][2] + a[0][1] * b[1][2] + a[0][2] * b[2][2]
],
[
a[1][0] * b[0][0] + a[1][1] * b[1][0] + a[1][2] * b[2][0],
a[1][0] * b[0][1] + a[1][1] * b[1][1] + a[1][2] * b[2][1],
a[1][0] * b[0][2] + a[1][1] * b[1][2] + a[1][2] * b[2][2]
],
[
a[2][0] * b[0][0] + a[2][1] * b[1][0] + a[2][2] * b[2][0],
a[2][0] * b[0][1] + a[2][1] * b[1][1] + a[2][2] * b[2][1],
a[2][0] * b[0][2] + a[2][1] * b[1][2] + a[2][2] * b[2][2]
]
]);
}
}
function getRotate3d(transMat) {
let { raw: t } = transMat;
return {
axis: [t[2][1] - t[1][2], t[0][2] - t[2][0], t[1][0] - t[0][1]],
angle: -1 * Math.acos((transMat.trace - 1)/2)
}
}
console.log(getRotate3d(
Matrix.ofRotationY(-45 * Math.PI/180)
.multiply(Matrix.ofRotationX(30 * Math.PI/180))
));