注意: 发现这个问题在CSS中有点难以解决。如果你真的需要像这样进行新变换应用于前一个状态的复杂变换,可以尝试其他方法。
无论如何,我首先会解释我经历的步骤,我所面临的问题以及我采取的解决方法。虽然它非常复杂和混乱,但它确实有效。最后,我将提供我使用的JavaScript代码。
说明
因此,我已经了解了一些关于CSS中变换的事情。其中一个主要的事情是,当您将一系列变换字符串传递给transform
属性时,就像这样:
transform: "rotateX(90deg) rotateY(90deg)"
这些变换没有组合成一个复合变换。相反,首先应用第一个变换,然后在其上应用下一个变换,以此类推。因此,尽管我期望立方体对角线旋转90度,但它并没有这样做。
正如@ihazkode建议的那样,
rotate3d
是正确的方法。它允许绕任意轴旋转,而不仅仅限于X、Y和Z轴。
rotate3d
需要三个参数。
rotate3d(x, y, z, angle).
x
、y
和z
指定了旋转轴。可以这样理解:想象一条从(x,y,z)
指向你指定的transform-origin
的直线,这条直线将成为旋转轴。现在假设你从(x,y,z)
向原点看去,那么从这个视角下,物体将会以顺时针方向旋转angle
度。
然而,我仍然遇到了一个问题。虽然rotate3d
让我以更加直观的方式旋转立方体,但当我用鼠标旋转立方体后,再次单击并尝试旋转它时,它会回到原来的状态并从那里旋转,这不是我想要的。我希望它按照当前的状态旋转,无论旋转状态是什么。
我找到了一种非常混乱的方法,可以使用matrix3d
属性实现。基本上,每次出现mousedown和mousemove事件时,我都会按以下步骤进行操作:
我会根据mousedown发生的位置和mousemove时的当前鼠标位置计算一个向量。例如,如果mousedown发生在(123,145),然后mousemove发生在(120,143),那么可以从这两个点形成一个向量[x,y,z,m],其中
x是x分量,即新的x位置减去mousedown时的x位置=120-123=-3
y是y分量,与x类似,等于143-145=-2
z=0,因为鼠标不能在z方向移动
m是向量的大小,可以计算为squareroot(x2+y2)=3.606
因此,鼠标移动可以表示为向量[-3,-2,0,3.606]
现在注意到立方体的旋转向量应该垂直于鼠标移动。例如,如果我向上移动鼠标3像素,鼠标移动向量就是[0,-1,0,3](y为负值,因为在浏览器中左上角是原点)。但是如果我使用该向量作为旋转向量并传递给rotate3d
,它会使立方体顺时针旋转(从上面看)。但这不对!如果我向上挥动鼠标,它应该围绕它的x轴旋转!要解决这个问题,只需交换x和y,并否定新的x。也就是说,向量应该是[1,0,0,3]。因此,第一步中的向量应该改为[2,-3,0,3.606]。
现在,我只需要将立方体的transform
属性设置为:
transform: "rotate3d(2,-3,0,3.606)"
现在,我已经找到了基于鼠标移动正确旋转立方体的方法,而不会出现以前试图进行rotateX和rotateY的问题。
现在立方体可以正确旋转。但是,如果我松开鼠标,再次按下鼠标并尝试旋转立方体呢?如果我按照上面的步骤进行,那么发生的情况就是我传递给rotate3d的新向量将替换旧向量。因此,立方体将重置为其初始位置,并应用新的旋转。但是这不对。我希望立方体保持之前的状态,然后从该状态开始通过新的旋转向量进一步旋转。
要做到这一点,我需要将新的旋转附加到先前的旋转上。因此,我可以像这样做:
transform: "rotate3d(previous_rotation_vector) rotate3d(new_rotation_vector)"
毕竟,这将执行第一次旋转,然后在此基础上执行第二次旋转。但是,想象一下执行100次旋转。 transform
属性需要输入 100 个 rotate3d
。这不是最好的方法。
我想到了以下解决方案。如果您查询节点的 transform
CSS 属性,则可以在任何时候使用:
$('.cube').css('transform');
您会得到以下三个值之一:“none”(如果对象到目前为止还没有被转换过),一个2D变换矩阵(看起来像
matrix2d(...)
)(如果只进行了2D变换),或者一个3D变换矩阵(看起来像
matrix3d(...)
)(否则)。
所以,我能做的是,在执行旋转操作后立即查询并获取立方体的变换矩阵并保存它。下次执行新旋转时,这样做:
transform: "matrix3d(saved_matrix_from_last_rotation) rotate3d(new_rotation_vector)"
这将首先将立方体转换为其最后旋转状态,然后在其上应用新的旋转。不需要传递100个rotate3d
。
- 我发现还有最后一个问题。对象的轴仍会随着对象一起旋转。
假设我沿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轴。问题就在于轴随着对象一起旋转。
因此,如果立方体当前处于某个旋转状态,并且我想让它进一步沿一个向量(x,y,z)旋转,就像步骤1和2中获取的那样,我首先需要以某种方式将这个向量转换为基于立方体当前状态的正确位置。
我注意到,如果您将旋转向量作为4x1矩阵,如下所示:
x
y
z
angle
将matrix3d
矩阵视为4x4矩阵,然后如果我将3D变换矩阵乘以旋转向量,则可以获得旧的旋转向量,但是它已经转换为其正确位置。现在,我可以像步骤3中那样在3d矩阵之后应用此向量,最终立方体的行为方式与应该完全一样。
JavaScript代码
好了,说了这么多。这是我使用的代码。如果不太清楚,请见谅。
var lastX;
var lastY;
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');
if(m.match(/matrix3d/) == null)
matrix3d = [[1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 1, 0], [0, 0, 0, 1]];
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]];
rotate3d = multiply(matrix3d, r);
var str = 'matrix3d' + matrixToString(matrix3d)
+ ' rotate3d(' + rotate3d[0][0] + ', ' + rotate3d[1][0] + ', ' + rotate3d[2][0] + ', ' + rotate3d[3][0] + 'deg)';
$('.cube').css('transform',str);
});
});
});
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+")";
}
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)];
}
function multiply(a, b) {
var aNumRows = a.length, aNumCols = a[0].length,
bNumRows = b.length, bNumCols = b[0].length,
m = new Array(aNumRows);
for (var r = 0; r < aNumRows; ++r) {
m[r] = new Array(bNumCols);
for (var c = 0; c < bNumCols; ++c) {
m[r][c] = 0;
for (var i = 0; i < aNumCols; ++i) {
m[r][c] += a[r][i] * b[i][c];
}
}
}
return m;
}