控制CSS立方体旋转(变换)并从3D矩阵中提取值

10

我用CSS制作了一个立方体,通过按上下和左右键旋转,但是我在旋转方向方面遇到了问题。

尝试 #1

DEMO

使用这篇文章,我成功地将按键绑定并应用于立方体的旋转。我的第一个问题是CSS transform函数会旋转元素轴,所以当我按上键时,Y轴和Z轴交换位置。我调整了原始代码来解决这个问题,但另一个问题是,由于轴是向量,当我按两次上键时,X轴和Z轴回到原位,但向量被反转了(按左键开始将立方体向右旋转,反之亦然),所以现在我必须向相反的方向旋转立方体才能得到想要的结果,但我不知道如何检测轴是否被反转了。

JavaScript

var xAngle = 0,
    yAngle = 0,
    zAngle = 0,
    cube = $("#cube");

$(document).keydown(function(e) { //keyup maybe better?

  e.preventDefault();

  var key = e.which,
      arrow = {left: 37, up: 38, right: 39, down: 40},
      x = xAngle/90,
      y = yAngle/90;

    switch(key) {
      case arrow.left:
        if (x%2 == 0)
          yAngle -= 90;
        else
          zAngle += 90;
      break;
      case arrow.up:
        if (y%2 == 0)
          xAngle += 90;
        else
          zAngle -= 90;
      break;
      case arrow.right:
        if (x%2 == 0)
          yAngle += 90;
        else
          zAngle -=90;
      break;
      case arrow.down:
        if (y%2 == 0)
          xAngle -= 90;
        else
          zAngle += 90;
      break;
    }

    var rotate = "rotateX(" + xAngle + "deg) rotateY(" + yAngle + "deg) rotateZ(" + zAngle + "deg)";
    cube.css({"transform":rotate});  

});

第二次尝试

演示

我又制作了一个版本,使用了这篇文章中的方法,通过分解和更新 CSS 3D 矩阵来解决同样的问题,但它也有其他问题。在随机方向重复按箭头后,方块的视角会改变(一次可以看到多个面)。

如果我可以从 3D 矩阵中获取旋转值或矢量方向就太好了,但我找到的解决方案似乎都不起作用。我猜想这是因为 3D 矩阵是通过乘以所有传递的函数(rotateX、rotateY 和 translateZ)的值得出的,而这种数学对我来说太过高深了。

JavaScript

var Vector = function(x, y, z) {
    this.x = x;
    this.y = y;
    this.z = z;
}

WebKitCSSMatrix.prototype.transformVector = function(v) {
    var xOut = this.m11*v.x + this.m12*v.y + this.m13*v.z;
    var yOut = this.m21*v.x + this.m22*v.y + this.m23*v.z;
    var zOut = this.m31*v.x + this.m32*v.y + this.m33*v.z;

    return new Vector(xOut, yOut, zOut);
};

function applyRotation(vector, angle) {

    var cube = $('#cube');

    var matrix = new WebKitCSSMatrix(cube.css('webkitTransform'));

    var vector = matrix.transformVector(vector);

    var newMatrix = matrix.rotateAxisAngle(vector.x, vector.y, vector.z, angle);

    cube.get(0).style.webkitTransform = newMatrix;
}

// rotate using arrow keys
$(document).keyup(function(e) {

    e.preventDefault();

    var key = e.which,
            arrow = {left: 37, up: 38, right: 39, down: 40},
            v,
            a;

        switch(key) {
            case arrow.left:
                v = new Vector(0,1,0),
                a = -90;
            break;

            case arrow.right:
                v = new Vector(0,1,0),
                a = 90;
            break;

            case arrow.up:
                v = new Vector(1,0,0),
                a = 90;
            break;

            case arrow.down:
                v = new Vector(1,0,0),
                a = -90;
            break;
        }

        applyRotation(v, a);

});

第三次尝试

演示

我制作的第三个版本分别旋转每个面,并在旋转后更改类名,因此我始终只需向右旋转X和Y,但是当旋转发生时,立方体被分解,我认为上下旋转是错误的(而且代码有点臃肿丑陋)。这种方法的唯一好处是对于不支持preserve-3d属性的浏览器具有更大的跨浏览器兼容性。

JavaScript

$(document).keyup(function(e) {

  e.preventDefault();

  var key = e.which,
      arrow = {left: 37, up: 38, right: 39, down: 40},
      front = "rotateX(0deg) translateZ(100px)",
      back = "rotateX(180deg) translateZ(100px)",
      right = "rotateY(90deg) translateZ(100px)",
      left = "rotateY(-90deg) translateZ(100px)",
      top = "rotateX(90deg) translateZ(100px)",
      bottom = "rotateX(-90deg) translateZ(100px)";

    switch(key) {
      case arrow.left:
        $(".front").css({"transform":left});
        $(".back").css({"transform":right});
        $(".left").css({"transform":back});
        $(".right").css({"transform":front});
        var front = $(".front");
        var back = $(".back");
        var left = $(".left");
        var right = $(".right");
        front.removeClass("front").addClass("left");
        back.removeClass("back").addClass("right");
        right.removeClass("right").addClass("front");
        left.removeClass("left").addClass("back");
      break;
      case arrow.up:
        $(".front").css({"transform":top});
        $(".back").css({"transform":bottom});
        $(".top").css({"transform":back});
        $(".bottom").css({"transform":front});
        var front = $(".front");
        var back = $(".back");
        var top = $(".top");
        var bottom = $(".bottom");
        front.removeClass("front").addClass("top");
        back.removeClass("back").addClass("bottom");
        top.removeClass("top").addClass("back");
        bottom.removeClass("bottom").addClass("front");
      break;
      case arrow.right:
        $(".front").css({"transform":right});
        $(".back").css({"transform":left});
        $(".left").css({"transform":front});
        $(".right").css({"transform":back});
        var front = $(".front");
        var back = $(".back");
        var left = $(".left");
        var right = $(".right");
        front.removeClass("front").addClass("right");
        back.removeClass("back").addClass("left");
        right.removeClass("right").addClass("back");
        left.removeClass("left").addClass("front");
      break;
      case arrow.down:
        $(".front").css({"transform":bottom});
        $(".back").css({"transform":top});
        $(".top").css({"transform":front});
        $(".bottom").css({"transform":back});
        var front = $(".front");
        var back = $(".back");
        var top = $(".top");
        var bottom = $(".bottom");
        front.removeClass("front").addClass("bottom");
        back.removeClass("back").addClass("top");
        top.removeClass("top").addClass("front");
        bottom.removeClass("bottom").addClass("back");
      break;
    }

});

参考材料:


我的第一个演示在我这里运行良好(FF和Chrome)。 - Morpheus
@Morpheus 当你按照他所描述的方式交替垂直方向和水平方向时,有时会出现故障。 - Zach Saucier
不确定是否有帮助,但我在第一个示例中添加了一些轴 - Zach Saucier
在示例3中,$(".front").css({"transform":left});这行代码是无意义的,因为在第一次执行后,left变成了一个jQuery对象,而不是一个变换字符串! - Eric
2个回答

2
尝试2的问题在于rotateAxisAngle按照相反的顺序执行了矩阵乘法。更糟糕的是,该类中没有按照您想要的顺序执行乘法的函数。
作为另一种方法,我选择使用浏览器本身来进行计算。我创建了一个将被隐藏的div,我会对其应用变换以获取新矩阵。
采用这种方法后,javascript代码甚至更短:
function applyTransform (transform) {

    var cubeCalculator = $('.cubecalculator');
    var cube = $('#cube');

    var matrix = cubeCalculator.css('webkitTransform');
    var composite = transform + ' ' + matrix;
    cubeCalculator.get(0).style.webkitTransform = composite;

    matrix = cubeCalculator.css('webkitTransform');
    cube.get(0).style.webkitTransform = matrix;
}

// rotate using arrow keys
$(document).keyup(function(e) {

    e.preventDefault();

    var key = e.which,
        arrow = {left: 37, up: 38, right: 39, down: 40},
        t;

    switch(key) {
        case arrow.left:
            t = 'rotateY(-90deg)';
        break;

        case arrow.right:
            t = 'rotateY(90deg)';
        break;

        case arrow.up:
            t = 'rotateX(90deg)';
        break;

        case arrow.down:
            t = 'rotateX(-90deg)';
        break;
    }

    applyTransform (t);

});

我认为代码已经相当自我解释了:我将变换应用于元素,作为新变换和当前变换的组合(您不需要从矩阵中提取值,可以直接应用)

演示

(我不知道为什么,它在CodePen上没有起作用。已将其移动到Fiddle...)

最后我让* Firefox 表现正常了!

function applyTransform (transform1, transform2) {
    var matrix, composite1, composite2;
    var cubeCalculator = $('.cubecalculator');
    var cube = $('#cube');

    matrix = cubeCalculator.css('transform');
    composite1 = transform1 + ' ' + matrix;
    composite2 = transform2 + ' ' + matrix;
    cubeCalculator.get(0).style.transform = composite2;
    cube.get(0).style.transition = 'none';
    cube.get(0).style.transform = composite1;

    window.setTimeout (function() {
        cube.get(0).style.transform = composite2;
        cube.get(0).style.transition = 'transform 1s';
    }, 10   );
}

// rotate using arrow keys
$(document).keyup(function(e) {

    e.preventDefault();

    var key = e.which,
        arrow = {left: 37, up: 38, right: 39, down: 40},
        t1, t2;

    switch(key) {
        case arrow.left:
            t1 = 'rotateY(0deg)';
            t2 = 'rotateY(-90deg)';
        break;

        case arrow.right:
            t1 = 'rotateY(0deg)';
            t2 = 'rotateY(90deg)';
        break;

        case arrow.up:
            t1 = 'rotateX(0deg)';
            t2 = 'rotateX(90deg)';
        break;

        case arrow.down:
            t1 = 'rotateX(0deg)';
            t2 = 'rotateX(-90deg)';
        break;
    }

    applyTransform (t1, t2);

});

稍微复杂一些的代码,但可以让浏览器清楚地知道你想让它做什么......只要等到过渡结束,就能正常工作。

更糟糕的是,类中没有函数可以按照您想要的顺序进行乘法运算。请使用new WebKitCSSMatrix().rotateAxisAngle(...).multiply(matrix)代替matrix.rotateAxisAngle(...) - Eric
是的,非常好用!我稍微调整了一下你的代码,去掉了供应商前缀http://codepen.io/teodragovic/pen/xKBqg现在它可以在Chrome、Opera和Safari中正常工作,但在FF中表现非常奇怪(我猜Gecko不能正确处理乘法)。感谢你和@Eric提供的一些很棒的见解。 - Teo Dragovic
很高兴它有所帮助!我一直在调查FF问题。这只是动画的问题:看到在错误的移动结束时,面部颜色如何改变(变成正确的颜色)。因此,最终位置是正确的,但动画是错误的...有点像一个漏洞,但我不知道是否可以以某种方式避免。 - vals

0
Demo 2 差不多完成了。你的问题在于你正在使用立方体的中间动画状态来计算新位置:
var matrix = new WebKitCSSMatrix(cube.css('webkitTransform'));

相反,您应该存储和更新内部目标矩阵:

此外,似乎这段代码片段:

var vector = matrix.transformVector(vector);
var newMatrix = matrix.rotateAxisAngle(vector.x, vector.y, vector.z, angle);

没有按预期工作。这是您要寻找的:

var newMatrix = new WebKitCSSMatrix().rotateAxisAngle(...).multiply(matrix)

http://codepen.io/eric-wieser/pen/BoeyD


@vals:糟糕...我以为我已经修复了那个问题。 - Eric
@vals:我已经在面部添加了字母 - 发生了一些奇怪的事情。 - Eric
@vals:最终状态仍然正确,但 Webkit 在插值矩阵方面表现非常糟糕。 - Eric
一直在调查中... 我仍然不完全理解问题所在... - vals
@vals:我认为这里的方法是比较你的工作代码生成的矩阵和我的矩阵。 - Eric

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接