在固定轴上旋转 CSS 立方体 [英] Rotating CSS cube on fixed axes

查看:28
本文介绍了在固定轴上旋转 CSS 立方体的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一个使用 CSS 构建的立方体.它由 6 个面组成,每个面被转换为立方体的一个面,所有 6 个面都在一个

下,类为 .cube.我对立方体所做的任何旋转都是在这个封闭的 cube 类上完成的.

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

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

变换: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 {位置:绝对;左:50%;顶部:50%;视角:1500px;}.cube {位置:相对;变换风格:preserve-3d;}/* 每个面的大小和边框颜色 */.脸 {位置:绝对;宽度:200px;高度:200px;边框:纯绿色3px;}/* 将每个人脸变换到正确的位置 */#front_face {变换:translateX(-100px) translateY(-100px) translateZ(100px);}#back_face {变换:translateX(-100px) translateY(-100px) translateZ(-100px);}#right_face {变换:translateY(-100px)rotateY(90deg);}#left_face {变换:translateY(-100px) translateX(-200px)rotateY(90deg);}#top_face {变换:translateX(-100px) translateY(-200px)rotateX(90deg);}#bottom_face {变换:translateX(-100px)rotateX(90deg);}.cube {变换:rotateX(90deg)rotateY(90deg);}

<!-- 多维数据集的包装器 --><div id="cube-wrapper"><div class="cube"><!-- 立方体每个面的 div --><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>

我无法真正添加任何 javascript,因为我实际上是在 purescript 中编写逻辑.但是代码只是注册了一个 mousedown 处理程序,它接受当前鼠标的 x 和 y,将它与最后的 x 和 y 进行比较,并通过更改 .cube 带有像这样的值.

 {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, 角度).

x y 和 z 指定旋转轴.看待它的方式是这样的:想象从 (x,y,z) 到你指定的 transform-origin 画一条线.这条线将是旋转轴.现在假设您正在朝向 from (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(x2 + 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 属性需要输入 100 个 rotate3ds.这不是解决此问题的最佳方式.

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

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

你会得到 3 个值之一:none"如果对象到目前为止还没有被转换,一个 2D 转换矩阵(看起来像 matrix2d(...))如果只是已执行 2D 转换或 3D 转换矩阵(否则看起来像 matrix3d(...).

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

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

这将首先将立方体转换为其最后的旋转状态,然后在其上应用新的旋转.无需传递 100 个 rotate3ds.

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

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

变换:rotate3d(1,0,0,90deg);

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

transform: matrix3d(保存的值)rotate3d(0,1,0,45deg)

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

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

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

x是z角度

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

JavaScript 代码

好了,说得够多了.这是我使用的代码.抱歉,如果不是很清楚.

var lastX;//存储鼠标按下时的 x 位置var lastY;//从鼠标按下的y位置var matrix3d = [[1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 1, 0], [0, 0, 0, 1]]//这个单位矩阵执行没有转变$(document).ready(function() {$('body').on('mousedown', function(event) {$('body').on('mouseup', function() {$('body').off('mousemove');m = $('.cube').css('transform');//如果此条件为真,则变换属性在初始状态下为none"或matrix2d",这在立方体处于 0 旋转时发生.if(m.match(/matrix3d/) == null)matrix3d = [[1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 1, 0], [0, 0, 0, 1]];//无变换的恒等矩阵别的matrix3d = stringToMatrix(m.substring(8,m.length));});lastX=event.pageX;lastY=event.pageY;$('body').on('mousemove', 函数(事件){var x = -(event.pageY - lastY);var y = event.pageX - lastX;var 角度 = Math.sqrt(x*x + y*y);var r = [[x],[y],[0],[角度]];//旋转向量旋转 3d = 乘法(matrix3d,r);//相乘得到正确变换的旋转向量var str = 'matrix3d' + matrixToString(matrix3d)+ 'rotate3d('+rotate3d[0][0]+','+rotate3d[1][0]+','+rotate3d[2][0]+','+rotate3d[3][0]+'度)';$('.cube').css('transform',str);});});});//将变换矩阵转换为由逗号分隔并括在括号中的所有元素组成的字符串.函数矩阵到字符串(矩阵){var s = "(";for(i=0; i

I have a cube constructed using CSS. It's made of 6 faces and each face is transformed to form one face of the cube, and all the 6 faces are under one <div> with the class .cube. Any rotation I do to the cube is done on this enclosing cube class.

I want the cube to rotate based on mouse drag input. So far it kinda works. I just translate x and y mouse movement into cube rotation about the x and y axes.

But there's one major problem with this. I perform the rotation as a simple

transform: rotateX(xdeg) rotateY(ydeg)

CSS property. The issue with this is that the y axis of rotation is getting rotated with the x rotation.

Suppose I rotate the cube 90 degrees around the x axis. Now, if I try to rotate the cube 90 degrees along the y axis as well, I would expect the cube to rotate 90 degrees to the right or left (from my perspective). But instead, it's rotating about it's currently visible front face. That is, the y axis got rotated 90 degrees thanks to the x axis rotation that came first, and so now from the perspective of the user, it looks as if the cube is rotating around it's z axis.

I want to be able to rotate the cube in a way that the x y and z axes remain fixed from the perspective of the user. Also the cube needs to rotate from the current state in case the user lifts their finger off the button and clicks again and drags.

I've been finding this difficult to do. I feel this may not be possible using just the rotateX/Y/Z properties and instead I might have to use the 3d matrix or rotate3d properties?

I know this may not be the easiest thing to achieve using CSS but I still want to do it. Could someone point me in the right direction on how to solve this problem?

#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>

I can't really add any javascript because I'm actually coding the logic in purescript. But the code just registers a mousedown handler that takes the current mouse x and y, compares it to the last x and y and accordingly rotates the cube around the x and y axes by changing the transform property of .cube with a value like.

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

解决方案

Note: It turns out this problem is kinda difficult to solve in CSS. If you really need a complex transformation like this where new transformations should be applied onto the previous state maybe try some other method.

Anyway, I'm first going to explain the steps I went through, the problems I faced and the steps I took to solve it. It's really convoluted and messy but it works. At the end, I've put the code I used as JavaScript.

Explanation

So I've come to understand a couple of things about transformations in CSS. One main thing is that when you pass a string of transformations to the transform property, like this

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

these transformations are not combined into one single composite transformation. Instead the first one is applied, then the next one is applied on top of that and so on. So while I expected the cube to rotate diagonally by 90degrees, it didn't do that.

As @ihazkode suggested, rotate3d was the way to go. It allows rotation around any arbitrary axes instead of being limited to X, Y and Z axes. rotate3d takes 3 arguments

rotate3d(x, y, z, angle).

x y and z specify the rotation axis. The way to look at it is like this: Imagine drawing a line from (x,y,z) to the transform-origin you specified. This line will the be axis of rotation. Now imagine you are looking towards the origin from (x,y,z). From this view, the object will rotate clockwise by angle degrees.

However, I still faced a problem. Although rotate3d let's me rotate the cube in a far more intuitive way, I still faced the problem where after rotating the cube once (with the mouse) if I again clicked and tried rotating the cube, it would snap back to its original state and rotate from there, which is not what I wanted. I wanted it to rotate from it's current state, whatever rotation state that may be.

I found a very messy way to do it using the matrix3d property. Basically I'd follow these steps every time the mousedown and mousemove events occurred

  1. I'd calculate a vector based on the position that mousedown occured and the current mouse position from mousemove. For example, if mousedown occured at (123,145) and then a mousemove occured at (120,143), then a vector can be made from these two points as [x, y, z, m] where

    x is the x component which is the new x position minus the mouse down x position = 120 - 123 = -3

    y is the y component, similar to x, which = 143-145 = -2

    z = 0 since the mouse cannot move in the z direction

    m is the magnitude of the vector which can be calculated as squareroot(x2 + y2) = 3.606

    So the mouse movement can be represented as the vector [-3, -2, 0, 3.606]

  2. Now notice that the rotation vector of the cube should be perpendicular to the mouse movement. For example, if I move my mouse straight up by 3 pixels, the mouse movement vector is [0,-1,0,3] (y is negative because in the browser the top left corner is the origin). But if I use this vector as the rotation vector and pass it into rotate3d, that rotates the cube clockwise (when looking from above) around the y axis. But that's not right! If I swipe my mouse upwards, it should rotate around it's x axis! To solve this, just swap x and y and negate the new x. That is, the vector should be [1,0,0,3]. Therefore, the vector from step 1 should instead be [2,-3,0,3.606].

Now I just set the transform property of my cube as

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

So now, I figured out how to rotate the cube correctly based on mouse movement, without facing the previous problem of trying to make a rotateX and then rotateY.

  1. Now the cube can rotate correctly. But what if I let go of the mouse and then again perform a mousedown and try rotating the cube. If I follow the same steps from above, what happens is the new vector that I pass to rotate3d will replace the old one. So the cube is reset to it's initial position and the new rotation is applied to it. But that's not right. I want the cube to remain in the state it was in previously, and then from that state it should rotate further by the new rotation vector.

To do this, I need to append the new rotation onto the previous rotation. So I could do something like this

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

After all, this would perform the first rotation and then perform the second rotation on top of that. But then imagine performing 100 rotations. The transform property would need to be fed 100 rotate3ds. That wouldn't be the best way to go about this.

Here's what I figured. At any point if you query the transform css property of a node like

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

you get back one of 3 values: "none" if the object hasn't been transformed at all so far, a 2D transformation matrix (looks like matrix2d(...)) if only 2D transformations have peen performed, or a 3D transformation matrix (looks like matrix3d(...) otherwise.

So what I can do is, immediately after performing a rotate operation, query and get the transformation matrix of the cube and save it. Next time I perform a new rotation, do this:

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

This would first transform the cube to it's last state of rotation and then apply the new rotation on top of that. No need to pass a 100 rotate3ds.

  1. There's one last problem I discovered. There's still the same issue of the axes of an object rotating along with the object.

Suppose I rotate the cube 90 degrees along the x axis with

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

and then rotate it from there around it's the y axis by 45 degrees with

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

I would expect the cube to rotate upwards 90 and then rotate to the right by 45. But instead it rotated up by 90 and then rotated around currently visible front face by 45 instead of rotating to the right. It's the exact same problem I mentioned in my question. The problem is, although rotate3d allows you to rotate an object around any arbitrary axis of rotation, that arbitrary axis is still with reference to the axis of the object and not by a fixed x, y and z axes with respect to the user. It's the same gosh darn problem of the axes rotating with the object.

So if the cube is currently in some rotated state and I want it to rotate further on a vector (x,y,z) obtained through the mouse as in step 1 and 2, I first need to somehow transform this vector into it's correct position based on what state the cube is in currently.

What I noticed is if you take the rotation vector as a 4x1 matrix like this

x
y
z
angle

and took the matrix3d matrix as a 4x4 matrix, then if I multiplied the 3D transformation matrix by the rotation vector, I get the old rotation vector but transformed into it's correct position. Now I can apply this vector after the 3d matrix as in step 3 and FINALLY the cube is behaving exactly the way it should.

JavaScript code

Okay that was enough talk. Here's the code I used. Sorry if it's not very clear.

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;
}

这篇关于在固定轴上旋转 CSS 立方体的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆