在固定轴上旋转 CSS 立方体

Geo*_*.M. 4 css 3d rotation css-transforms

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

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

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

transform: rotateX(xdeg) rotateY(ydeg)
Run Code Online (Sandbox Code Playgroud)

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

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

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

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

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

transform: rotateX(xdeg) rotateY(ydeg)
Run Code Online (Sandbox Code Playgroud)
#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);
}
Run Code Online (Sandbox Code Playgroud)

我真的无法添加任何 javascript,因为我实际上是在 purescript 中编写逻辑。但是代码只是注册了一个 mousedown 处理程序,它获取当前鼠标的 x 和 y,将它与最后的 x 和 y 进行比较,并通过更改.cube具有类似值的变换属性,从而围绕 x 和 y 轴旋转立方体。

  {transform: "rotateX(90deg) rotateY(90deg)"}
Run Code Online (Sandbox Code Playgroud)

Geo*_*.M. 6

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

不管怎样,我首先要解释我所经历的步骤、我面临的问题以及我为解决它所采取的步骤。这确实令人费解和混乱,但它有效。最后,我把我使用的代码作为 JavaScript。

解释

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

transform: "rotateX(90deg) rotateY(90deg)"
Run Code Online (Sandbox Code Playgroud)

这些转换不会合并为一个单一的复合转换。而是应用第一个,然后应用下一个,依此类推。所以虽然我希望立方体对角旋转 90 度,但它没有这样做。

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

rotate3d(x, y, z, angle).
Run Code Online (Sandbox Code Playgroud)

xy 和 z 指定旋转轴。看它的方式是这样的:想象一下,从画线(x,y,z)的到transform-origin你指定的。这条线将是旋转轴。现在想象一下你正在寻找走向原点 (x,y,z)。从这个观点,该对象将旋转顺时针angle度。

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

我找到了一种使用该matrix3d属性的非常混乱的方法。基本上,每次发生 mousedown 和 mousemove 事件时,我都会遵循这些步骤

  1. 我会根据 mousedown 发生的位置和 mousemove 的当前鼠标位置计算一个向量。例如,如果 mousedown 发生在 (123,145),然后 mousemove 发生在 (120,143),那么可以从这两个点制作一个向量作为 [x, y, z, m] 其中

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

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

    z = 0 因为鼠标不能在 z 方向移动

    m 是矢量的大小,可以计算为 squareroot(x 2 + y 2 ) = 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)"
Run Code Online (Sandbox Code Playgroud)

所以现在,我想出了如何根据鼠标移动正确旋转立方体,而不会遇到之前尝试制作 arotateX和 then 的问题rotateY

  1. 现在立方体可以正确旋转。但是如果我松开鼠标然后再次执行鼠标按下并尝试旋转立方体会怎样。如果我按照上面相同的步骤进行操作,我传递给的新向量rotate3d将替换旧向量。所以立方体被重置到它的初始位置,并对其应用新的旋转。但这是不对的。我希望立方体保持之前的状态,然后该状态开始,它应该通过新的旋转向量进一步旋转。

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

transform: "rotate3d(previous_rotation_vector) rotate3d(new_rotation_vector)"
Run Code Online (Sandbox Code Playgroud)

毕竟,这将执行第一次旋转,然后在此基础上执行第二次旋转。但是再想象一下执行 100 次旋转。该transform属性需要馈送 100rotate3d秒。这不是解决这个问题的最佳方式。

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

$('.cube').css('transform');
Run Code Online (Sandbox Code Playgroud)

你回来3个值之一:“无”,如果该对象尚未在所有迄今转化,一个2D变换矩阵(貌似matrix2d(...))如果有喷丸只进行2D变换或3D变换矩阵(貌似matrix3d(...)不然。

所以我能做的就是,在执行旋转操作后,立即查询并获取多维数据集的变换矩阵并保存。下次我执行新的轮换时,请执行以下操作:

transform: "matrix3d(saved_matrix_from_last_rotation) rotate3d(new_rotation_vector)"
Run Code Online (Sandbox Code Playgroud)

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

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

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

transform: rotate3d(1,0,0,90deg);
Run Code Online (Sandbox Code Playgroud)

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

transform: matrix3d(saved values) rotate3d(0,1,0,45deg)
Run Code Online (Sandbox Code Playgroud)

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

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

我注意到的是,如果您将旋转向量作为这样的 4x1 矩阵

x
y
z
angle
Run Code Online (Sandbox Code Playgroud)

并将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;
}
Run Code Online (Sandbox Code Playgroud)