在固定轴上旋转 CSS 立方体



我有一个用CSS构造的立方体。它由 6 个面组成,每个面被转换为立方体的一个面,所有 6 个面都与类.cube在一个<div>下。我对多维数据集所做的任何旋转都是在这个封闭的cube类上完成的。

我希望立方体根据鼠标拖动输入旋转。到目前为止,它有点工作。我只是将 x 和 y 鼠标移动转换为围绕 x 和 y 轴的立方体旋转。

但这有一个主要问题。作为简单的执行旋转

transform: rotateX(xdeg) rotateY(ydeg)

CSS 属性。这样做的问题是旋转的 y 轴随着 x 旋转而旋转。

假设我将立方体绕 x 轴旋转 90 度。现在,如果我也尝试沿 y 轴将立方体旋转 90 度,我希望立方体向右或向左旋转 90 度(从我的角度来看)。但相反,它围绕当前可见的前脸旋转。也就是说,由于首先出现的 x 轴旋转,y 轴旋转了 90 度,所以现在从用户的角度来看,它看起来好像立方体绕它的 z 轴旋转。

我希望能够以从用户的角度使 x y 和 z 轴保持固定的方式旋转立方体。此外,立方体需要从当前状态旋转,以防用户将手指从按钮上抬起并再次单击并拖动。

我一直觉得这很难做到。我觉得仅使用rotateX/Y/Z属性可能无法做到这一点,而是我可能必须使用 3d 矩阵或 rotate3d 属性?

我知道这可能不是使用 CSS 实现的最简单的事情,但我仍然想这样做。有人可以指出我如何解决这个问题的正确方向吗?

#cube-wrapper {
position: absolute;
left: 50%;
top: 50%;
perspective: 1500px;
}
.cube {
position: relative;
transform-style: preserve-3d;
}
/* Size and border color for each face */
.face {
position: absolute;
width: 200px;
height: 200px;
border: solid green 3px;
}
/* Transforming every face into their correct positions */
#front_face {
transform: translateX(-100px) translateY(-100px) translateZ(100px);
}
#back_face {
transform: translateX(-100px) translateY(-100px) translateZ(-100px);
}
#right_face {
transform: translateY(-100px) rotateY(90deg);
}
#left_face {
transform: translateY(-100px) translateX(-200px) rotateY(90deg);
}
#top_face {
transform: translateX(-100px) translateY(-200px) rotateX(90deg);
}
#bottom_face {
transform: translateX(-100px) rotateX(90deg);
}
.cube {
transform: rotateX(90deg) rotateY(90deg);
}
<!-- Wrapper for the cube -->
<div id="cube-wrapper">
<div class="cube">
<!-- A div for each face of the cube -->
<div id="front_face" class="face"></div>
<div id="right_face" class="face"></div>
<div id="back_face" class="face"></div>
<div id="left_face" class="face"></div>
<div id="top_face" class="face"></div>
<div id="bottom_face" class="face"></div>
</div>
</div>

我真的不能添加任何javascript,因为我实际上是用purescript编写逻辑的。但是代码只是注册一个鼠标向下处理程序,该处理程序获取当前鼠标 x 和 y,将其与最后一个 x 和 y 进行比较,并通过更改.cube的 transform 属性来相应地围绕 x 和 y 轴旋转立方体。

{transform: "rotateX(90deg) rotateY(90deg)"}

注意:事实证明,这个问题在CSS中很难解决。如果你真的需要像这样的复杂转换,其中新的转换应该应用于以前的状态,可以尝试其他方法。

无论如何,我将首先解释我所经历的步骤,我面临的问题以及我为解决它而采取的步骤。它真的很复杂和混乱,但它有效。最后,我把我用的代码作为JavaScript。

解释

所以我开始了解一些关于 CSS 转换的事情。一个主要的事情是,当您将一串转换传递给transform属性时,如下所示

transform: "rotateX(90deg) rotateY(90deg)"

这些转换不会合并为一个复合转换。相反,应用第一个,然后应用下一个,依此类推。因此,虽然我预计立方体会沿对角线旋转 90 度,但它并没有这样做。

正如@ihazkode所建议的那样,rotate3d是要走的路。它允许围绕任意轴旋转,而不是局限于 X、Y 和 Z 轴。rotate3d需要 3 个参数

rotate3d(x, y, z, angle).

x y 和 z 指定旋转轴。看待它的方式是这样的:想象一下从(x,y,z)到您指定的transform-origin画一条线。这条线将是旋转轴。现在想象一下,您正在(x,y,z)向原点。在此视图中,对象将顺时针旋转angle度。

但是,我仍然面临一个问题。虽然rotate3d让我以更直观的方式旋转立方体,但我仍然面临这样的问题:在旋转立方体一次(用鼠标)后,如果我再次单击并尝试旋转立方体,它会弹回其原始状态并从那里旋转,这不是我想要的。我希望它从当前状态旋转,无论旋转状态如何。

我发现了一种非常混乱的方式来使用matrix3d属性来做到这一点。基本上,每次发生鼠标按下和鼠标移动事件时,我都会遵循以下步骤

  1. 我会根据鼠标按下发生的位置和鼠标移动的当前鼠标位置计算一个向量。例如,如果鼠标按下发生在 (123,145) 处,然后在 (120,143) 处发生鼠标移动,则可以从这两个点生成一个向量,如 [x, y, z, m] 其中

    x是 x 分量,它是新的 x 位置减去鼠标向下 x 位置 = 120 - 123 = -3

    y是 y 分量,类似于 x,它 = 143-145 = -2

    z= 0,因为鼠标无法沿 z 方向移动

    m 是向量的大小,可以计算为平方根 (x 2 + y2) = 3.606

    因此,鼠标移动可以表示为向量 [-3, -2, 0, 3.606]

  2. 现在请注意,立方体的旋转矢量应垂直于鼠标移动。例如,如果我将鼠标向上移动 3 个像素,则鼠标移动矢量为 [0,-1,0,3](y 为负数,因为在浏览器中左上角是原点)。但是如果我使用这个向量作为旋转向量并将其传递到rotate3d中,则会顺时针(从上方看时)围绕 y 轴旋转立方体。但这是不对的!如果我向上滑动鼠标,它应该围绕它的 x 轴旋转!要解决这个问题,只需交换 x 和 y 并否定新的 x。也就是说,向量应为 [1,0,0,3]。因此,步骤 1 中的向量应改为 [2,-3,0,3.606]。

现在我只是将多维数据集的transform属性设置为

transform: "rotate3d(2,-3,0,3.606)"

所以现在,我想出了如何根据鼠标移动正确旋转立方体,而不会面临之前尝试进行rotateX然后rotateY的问题。

  1. 现在立方体可以正确旋转了。但是,如果我放开鼠标,然后再次执行鼠标按下并尝试旋转立方体怎么办?如果我按照上面的相同步骤操作,则发生的情况是我传递给rotate3d的新向量将替换旧向量。因此,立方体将重置为其初始位置,并对其应用新的旋转。但这是不对的。我希望立方体保持以前的状态,然后从该状态开始,它应该通过新的旋转矢量进一步旋转。

为此,我需要将新旋转附加到上一个旋转上。所以我可以做这样的事情

transform: "rotate3d(previous_rotation_vector) rotate3d(new_rotation_vector)"

毕竟,这将执行第一个旋转,然后在该旋转之上执行第二个旋转。但是想象一下执行 100 次旋转。transform属性需要喂食 100rotate3d秒。这不是最好的方法。

这是我的想法。在任何时候,如果您查询节点的transformcss 属性,例如

$('.cube').css('transform');

你会得到 3 个值之一:如果对象到目前为止根本没有被变换,则返回"none",如果只有 2D 变换执行了 peen,则返回 2D 变换矩阵(看起来像matrix2d(...)),或者 3D 变换矩阵(看起来像matrix3d(...)否则。

所以我能做的是在执行旋转操作后立即查询并获取立方体的转换矩阵并保存它。下次我执行新轮换时,请执行以下操作:

transform: "matrix3d(saved_matrix_from_last_rotation) rotate3d(new_rotation_vector)"

这将首先将立方体转换为其最后的旋转状态,然后在该状态上应用新的旋转。无需通过 100rotate3d秒。

  1. 我发现了最后一个问题。仍然存在相同的问题,即对象的轴与对象一起旋转。

假设我沿 x 轴将立方体旋转 90 度

transform: rotate3d(1,0,0,90deg);

然后从那里围绕它的 y 轴旋转 45 度

transform: matrix3d(saved values) rotate3d(0,1,0,45deg)

我希望立方体向上旋转 90,然后向右旋转 45。但相反,它向上旋转了 90,然后围绕当前可见的正面旋转了 45,而不是向右旋转。这与我在问题中提到的问题完全相同。问题是,尽管rotate3d允许您围绕任何任意旋转轴旋转对象,但该任意轴仍然参考对象的轴,而不是相对于用户的固定 x、y 和 z 轴。这与轴随物体旋转的天哪问题相同。

因此,如果立方体当前处于某种旋转状态,并且我希望它在步骤 1 和 2 中通过鼠标获得的向量 (x,y,z) 上进一步旋转,我首先需要以某种方式将此向量转换为正确的位置基于立方体当前所处的状态

我注意到的是,如果您将旋转矢量作为 4x1 矩阵,如下所示

x
y
z
angle

并将matrix3d矩阵作为 4x4 矩阵,然后如果我将 3D 变换矩阵乘以旋转向量,我得到旧的旋转向量,但转换为它的正确位置。现在我可以像步骤 3 中一样在 3d 矩阵之后应用这个向量,最后立方体的行为完全符合它应该的方式。

JavaScript 代码

好了,说的已经够多了。这是我使用的代码。对不起,如果不是很清楚。

var lastX; //stores x position from mousedown
var lastY; //y position from mousedown
var matrix3d = [[1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 1, 0], [0, 0, 0, 1]] //this identity matrix performs no transformation
$(document).ready(function() {
$('body').on('mousedown', function(event) {
$('body').on('mouseup', function() {
$('body').off('mousemove');
m = $('.cube').css('transform');
//if this condition is true, transform property is either "none" in initial state or "matrix2d" which happens when the cube is at 0 rotation.
if(m.match(/matrix3d/) == null) 
matrix3d = [[1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 1, 0], [0, 0, 0, 1]]; //identity matrix for no transformaion
else
matrix3d = stringToMatrix(m.substring(8,m.length));
});
lastX=event.pageX;
lastY=event.pageY;
$('body').on('mousemove', function (event) {
var x = -(event.pageY - lastY);
var y = event.pageX - lastX;
var angle = Math.sqrt(x*x + y*y);
var r = [[x],[y],[0],[angle]]; //rotation vector
rotate3d = multiply(matrix3d, r); //multiply to get correctly transformed rotation vector
var str = 'matrix3d' + matrixToString(matrix3d)
+ ' rotate3d(' + rotate3d[0][0] + ', ' + rotate3d[1][0] + ', ' + rotate3d[2][0] + ', ' + rotate3d[3][0] + 'deg)';
$('.cube').css('transform',str);
});
});
});
//converts transform matrix to a string of all elements separated by commas and enclosed in parentheses.
function matrixToString(matrix) {
var s = "(";
for(i=0; i<matrix.length; i++) {
for(j=0; j<matrix[i].length; j++) {
s+=matrix[i][j];
if(i<matrix.length-1 || j<matrix[i].length-1) s+=", ";
}
}
return s+")";
}
//converts a string of transform matrix into a matrix
function stringToMatrix(s) {
array=s.substring(1,s.length-1).split(", ");
return [array.slice(0,4), array.slice(4,8), array.slice(8,12), array.slice(12,16)];
}
//matrix multiplication
function multiply(a, b) {
var aNumRows = a.length, aNumCols = a[0].length,
bNumRows = b.length, bNumCols = b[0].length,
m = new Array(aNumRows);  // initialize array of rows
for (var r = 0; r < aNumRows; ++r) {
m[r] = new Array(bNumCols); // initialize the current row
for (var c = 0; c < bNumCols; ++c) {
m[r][c] = 0;             // initialize the current cell
for (var i = 0; i < aNumCols; ++i) {
m[r][c] += a[r][i] * b[i][c];
}
}
}
return m;
}

使用 rotate3d

它相对易于使用,但您仍然需要将当前的跟踪脚本链接到正确的参数

您可以控制旋转量(以度为单位)和受影响的轴(x,y,z)。您可以同时再选择一个。

示例 1 -旋转 X 轴:

#cube-wrapper {
position: absolute;
left: 50%;
top: 50%;
perspective: 1500px;
}
.cube {
position: relative;
transform-style: preserve-3d;
animation-name: rotate;
animation-duration: 30s;
animation-timing-function: linear;
animation-iteration-count: infinite;
}
@keyframes rotate {
0% {
transform: rotate3d(0, 0, 0, 0);
}
100% {
transform: rotate3d(1, 0, 0, 360deg); /*controls rotation amount on one axis) */
;
}
}
/* Size and border color for each face */
.face {
position: absolute;
width: 200px;
height: 200px;
border: solid green 3px;
}
/* Transforming every face into their correct positions */
#front_face {
transform: translateX(-100px) translateY(-100px) translateZ(100px);
background: rgba(255, 0, 0, 0.5);
}
#back_face {
transform: translateX(-100px) translateY(-100px) translateZ(-100px);
background: rgba(255, 0, 255, 0.5);
}
#right_face {
transform: translateY(-100px) rotateY(90deg);
background: rgba(255, 255, 0, 0.5);
}
#left_face {
transform: translateY(-100px) translateX(-200px) rotateY(90deg);
background: rgba(0, 255, 0, 0.5);
}
#top_face {
transform: translateX(-100px) translateY(-200px) rotateX(90deg);
background: rgba(0, 255, 255, 0.5);
}
#bottom_face {
transform: translateX(-100px) rotateX(90deg);
background: rgba(255, 255, 255, 0.5);
}
.cube {
transform: rotateX(90deg) rotateY(90deg);
}
<html>
<head>
<title>3D Cube in PureScript</title>
<link rel="stylesheet" type="text/css" href="css/cube_ref.css" />
<script type="text/javascript" src=../js/jquery-3.2.1.min.js></script>
</head>
<body style="width: 100%; height:100%;">
<!-- Wrapper for the cube -->
<div id="cube-wrapper">
<div class="cube">
<!-- A div for each face of the cube -->
<div id="front_face" class="face"></div>
<div id="right_face" class="face"></div>
<div id="back_face" class="face"></div>
<div id="left_face" class="face"></div>
<div id="top_face" class="face"></div>
<div id="bottom_face" class="face"></div>
</div>
</div>
</body>
<script type="text/javascript" src=js/cube.js></script>
</html>

示例 2 -旋转 Y 轴:

#cube-wrapper {
position: absolute;
left: 50%;
top: 50%;
perspective: 1500px;
}
.cube {
position: relative;
transform-style: preserve-3d;
animation-name: rotate;
animation-duration: 30s;
animation-timing-function: linear;
animation-iteration-count: infinite;
}
@keyframes rotate {
0% {
transform: rotate3d(0, 0, 0, 0);
}
100% {
transform: rotate3d(0, 1, 0, 360deg); /*controls rotation amount on one axis) */
;
}
}
/* Size and border color for each face */
.face {
position: absolute;
width: 200px;
height: 200px;
border: solid green 3px;
}
/* Transforming every face into their correct positions */
#front_face {
transform: translateX(-100px) translateY(-100px) translateZ(100px);
background: rgba(255, 0, 0, 0.5);
}
#back_face {
transform: translateX(-100px) translateY(-100px) translateZ(-100px);
background: rgba(255, 0, 255, 0.5);
}
#right_face {
transform: translateY(-100px) rotateY(90deg);
background: rgba(255, 255, 0, 0.5);
}
#left_face {
transform: translateY(-100px) translateX(-200px) rotateY(90deg);
background: rgba(0, 255, 0, 0.5);
}
#top_face {
transform: translateX(-100px) translateY(-200px) rotateX(90deg);
background: rgba(0, 255, 255, 0.5);
}
#bottom_face {
transform: translateX(-100px) rotateX(90deg);
background: rgba(255, 255, 255, 0.5);
}
.cube {
transform: rotateX(90deg) rotateY(90deg);
}
<html>
<head>
<title>3D Cube in PureScript</title>
<link rel="stylesheet" type="text/css" href="css/cube_ref.css" />
<script type="text/javascript" src=../js/jquery-3.2.1.min.js></script>
</head>
<body style="width: 100%; height:100%;">
<!-- Wrapper for the cube -->
<div id="cube-wrapper">
<div class="cube">
<!-- A div for each face of the cube -->
<div id="front_face" class="face"></div>
<div id="right_face" class="face"></div>
<div id="back_face" class="face"></div>
<div id="left_face" class="face"></div>
<div id="top_face" class="face"></div>
<div id="bottom_face" class="face"></div>
</div>
</div>
</body>
<script type="text/javascript" src=js/cube.js></script>
</html>

示例 3 -旋转 Z 轴:

#cube-wrapper {
position: absolute;
left: 50%;
top: 50%;
perspective: 1500px;
}
.cube {
position: relative;
transform-style: preserve-3d;
animation-name: rotate;
animation-duration: 30s;
animation-timing-function: linear;
animation-iteration-count: infinite;
}
@keyframes rotate {
0% {
transform: rotate3d(0, 0, 0, 0);
}
100% {
transform: rotate3d(0, 0, 1, 360deg); /*controls rotation amount on one axis) */
;
}
}
/* Size and border color for each face */
.face {
position: absolute;
width: 200px;
height: 200px;
border: solid green 3px;
}
/* Transforming every face into their correct positions */
#front_face {
transform: translateX(-100px) translateY(-100px) translateZ(100px);
background: rgba(255, 0, 0, 0.5);
}
#back_face {
transform: translateX(-100px) translateY(-100px) translateZ(-100px);
background: rgba(255, 0, 255, 0.5);
}
#right_face {
transform: translateY(-100px) rotateY(90deg);
background: rgba(255, 255, 0, 0.5);
}
#left_face {
transform: translateY(-100px) translateX(-200px) rotateY(90deg);
background: rgba(0, 255, 0, 0.5);
}
#top_face {
transform: translateX(-100px) translateY(-200px) rotateX(90deg);
background: rgba(0, 255, 255, 0.5);
}
#bottom_face {
transform: translateX(-100px) rotateX(90deg);
background: rgba(255, 255, 255, 0.5);
}
.cube {
transform: rotateX(90deg) rotateY(90deg);
}
<html>
<head>
<title>3D Cube in PureScript</title>
<link rel="stylesheet" type="text/css" href="css/cube_ref.css" />
<script type="text/javascript" src=../js/jquery-3.2.1.min.js></script>
</head>
<body style="width: 100%; height:100%;">
<!-- Wrapper for the cube -->
<div id="cube-wrapper">
<div class="cube">
<!-- A div for each face of the cube -->
<div id="front_face" class="face"></div>
<div id="right_face" class="face"></div>
<div id="back_face" class="face"></div>
<div id="left_face" class="face"></div>
<div id="top_face" class="face"></div>
<div id="bottom_face" class="face"></div>
</div>
</div>
</body>
<script type="text/javascript" src=js/cube.js></script>
</html>

示例 4 -同时旋转 X、Y 和 Z:

#cube-wrapper {
position: absolute;
left: 50%;
top: 50%;
perspective: 1500px;
}
.cube {
position: relative;
transform-style: preserve-3d;
animation-name: rotate;
animation-duration: 30s;
animation-timing-function: linear;
animation-iteration-count: infinite;
}
@keyframes rotate {
0% {
transform: rotate3d(0, 0, 0, 0);
}
100% {
transform: rotate3d(1, 1, 1, 360deg); /*controls rotation amount on one axis) */
;
}
}
/* Size and border color for each face */
.face {
position: absolute;
width: 200px;
height: 200px;
border: solid green 3px;
}
/* Transforming every face into their correct positions */
#front_face {
transform: translateX(-100px) translateY(-100px) translateZ(100px);
background: rgba(255, 0, 0, 0.5);
}
#back_face {
transform: translateX(-100px) translateY(-100px) translateZ(-100px);
background: rgba(255, 0, 255, 0.5);
}
#right_face {
transform: translateY(-100px) rotateY(90deg);
background: rgba(255, 255, 0, 0.5);
}
#left_face {
transform: translateY(-100px) translateX(-200px) rotateY(90deg);
background: rgba(0, 255, 0, 0.5);
}
#top_face {
transform: translateX(-100px) translateY(-200px) rotateX(90deg);
background: rgba(0, 255, 255, 0.5);
}
#bottom_face {
transform: translateX(-100px) rotateX(90deg);
background: rgba(255, 255, 255, 0.5);
}
.cube {
transform: rotateX(90deg) rotateY(90deg);
}
<html>
<head>
<title>3D Cube in PureScript</title>
<link rel="stylesheet" type="text/css" href="css/cube_ref.css" />
<script type="text/javascript" src=../js/jquery-3.2.1.min.js></script>
</head>
<body style="width: 100%; height:100%;">
<!-- Wrapper for the cube -->
<div id="cube-wrapper">
<div class="cube">
<!-- A div for each face of the cube -->
<div id="front_face" class="face"></div>
<div id="right_face" class="face"></div>
<div id="back_face" class="face"></div>
<div id="left_face" class="face"></div>
<div id="top_face" class="face"></div>
<div id="bottom_face" class="face"></div>
</div>
</div>
</body>
<script type="text/javascript" src=js/cube.js></script>
</html>

最新更新