使用Javascript进行CSS 3D转换后获得div的真实2D顶点坐标

时间:2015-03-21 19:23:58

标签: javascript html css

我一直试图解决这个问题几天,但我似乎无法做对。

基本上,我有一些div,其父级具有CSS透视和应用了rotateX 3D变换,我需要获得这些div的实际屏幕坐标。

这是一个jsfiddle,我的意思是一个例子(虽然没有正常工作)。

https://jsfiddle.net/6ev6d06z/3/

正如您所看到的,顶点是关闭的(由于其父项的转换)

我尝试过使用

getBoundingClientRect()

但这似乎并未考虑3D变换。 我不知道是否有一种已经建立的方法可以得到我需要的东西但是我猜必须有一种方法来使用matrix3D来计算坐标。

感谢任何帮助。

2 个答案:

答案 0 :(得分:4)

因为它没有内置的方法来获得转换元素的每个顶点的实际2d坐标。对于所有API(例如getBoundingClientRect),它们返回变换元素的边界矩形,表示为2点矩形[(上,左),(下,右)]。

话虽这么说,你可以通过一点努力和矩阵数学绝对得到实际的坐标。最简单的方法是使用预制的矩阵库进行数学计算(我已经掌握了关于math.js的好东西但没有使用过它),虽然它本身肯定是可行的。

在伪代码中,您需要做什么:

  1. 获取文档坐标系中已转换父元素的未转换边界。
  2. 获取文档坐标系中目标元素的未转换边界。
  3. 计算目标相对于父级未转换边界的未转换边界。  一个。从(2)的边界减去(1)的顶部/左侧偏移量。
  4. 获取父元素的css转换。
  5. 获取父元素的transform-origin(默认为(50%,50%))。
  6. 获取实际应用的变换(-origin * css transform * origin)
  7. 将(3)中的四个顶点乘以(6)的计算变换。
  8. 执行均匀除法(用w分量除x,y,z)以应用透视。
  9. 将投影顶点转换回文档坐标系。
  10. 趣味!
  11. 然后在实际代码中获得乐趣:https://jsfiddle.net/cLnmgvb3/1/

    $(".target").on('click', function(){
        $(".vertex").remove();
    
        // Note: The 'parentOrigin' and 'rect' are computed relative to their offsetParent rather than in doc
        //       coordinates. You would need to change how these offsets are computed to make this work in a
        //       more complicated page. In particular, if txParent becomes the offsetParent of 'this', then the
        //       origin will be wrong.
    
        // (1) Get the untransformed bounds of the parent element. Here we only care about the relative offset
        //     of the parent element to its offsetParent rather than it's full bounding box. This is the origin
        //     that the target elements are relative to.
        var txParent = document.getElementById('transformed');
    
        var parentOrigin = [ txParent.offsetLeft, txParent.offsetTop, 0, 0 ];
        console.log('Parent Origin: ', parentOrigin);
    
        // (2) Get the untransformed bounding box of the target elements. This will be the box that is transformed.
        var rect = { left: this.offsetLeft, top: this.offsetTop, right: this.offsetLeft + this.offsetWidth, bottom: this.offsetTop + this.offsetHeight };
    
        // Create the vertices in the coordinate system of their offsetParent - in this case <body>.
        var vertices =
            [
                [ rect.left, rect.top, 0, 1 ],
                [ rect.right, rect.bottom, 0, 1 ],
                [ rect.right, rect.top, 0, 1 ],
                [ rect.left, rect.bottom, 0, 1 ]
            ];
        console.log('Original: ', vertices);
    
        // (3) Transform the vertices to be relative to transformed parent (the element with
        //     the CSS transform on it).
        var relVertices = [ [], [], [], [] ];
        for (var i = 0; i < 4; ++i)
        {
            relVertices[i][0] = vertices[i][0] - parentOrigin[0];
            relVertices[i][1] = vertices[i][1] - parentOrigin[1];
            relVertices[i][2] = vertices[i][2];
            relVertices[i][3] = vertices[i][3];
        }
    
        // (4) Get the CSS transform from the transformed parent
        var tx = getTransform(txParent);
        console.log('Transform: ', tx);
    
        // (5) Get the CSS transform origin from the transformed parent - default is '50% 50%'
        var txOrigin = getTransformOrigin(txParent);
        console.log('Transform Origin: ', txOrigin);
    
        // (6) Compute the full transform that is applied to the transformed parent (-origin * tx * origin)
        var fullTx = computeTransformMatrix(tx, txOrigin);
        console.log('Full Transform: ', fullTx);
    
        // (7) Transform the vertices from the target element's bounding box by the full transform
        var txVertices = [ ];
        for (var i = 0; i < 4; ++i)
        {
            txVertices[i] = transformVertex(fullTx, relVertices[i]);
        }
    
        console.log('Transformed: ', txVertices);
    
        // (8) Perform the homogeneous divide to apply perspective to the points (divide x,y,z by the w component).
        var projectedVertices = [ ];
        for (var i = 0; i < 4; ++i)
        {
            projectedVertices[i] = projectVertex(txVertices[i]);
        }
    
        console.log('Projected: ', projectedVertices);
    
        // (9) After the transformed vertices have been computed, transform them back into the coordinate
        // system of the offsetParent.
        var finalVertices = [ [], [], [], [] ];
        for (var i = 0; i < 4; ++i)
        {
            finalVertices[i][0] = projectedVertices[i][0] + parentOrigin[0];
            finalVertices[i][1] = projectedVertices[i][1] + parentOrigin[1];
            finalVertices[i][2] = projectedVertices[i][2];
            finalVertices[i][3] = projectedVertices[i][3];
        }
    
        // (10) And then add the vertex elements in the 'offsetParent' coordinate system (in this case again
        //      it is <body>).
        for (var i = 0; i < 4; ++i)
        {
           $("<div></div>").addClass("vertex")
              .css('position', 'absolute')
              .css('left', finalVertices[i][0])
              .css('top', finalVertices[i][1])
              .appendTo('body');
        }
      });
    
    function printMatrix(mat)
    {
        var str = '';
        for (var i = 0; i < 4; ++i)
        {
            for (var j = 0; j < 4; ++j)
            {
                str += (' ' + mat[i][j]);
            }
    
            str += '\r\n';
        }
    
        console.log(str);
    }
    
    function getTransform(ele)
    {
        var st = window.getComputedStyle(ele, null);
    
        var tr = st.getPropertyValue("-webkit-transform") ||
                 st.getPropertyValue("-moz-transform") ||
                 st.getPropertyValue("-ms-transform") ||
                 st.getPropertyValue("-o-transform") ||
                 st.getPropertyValue("transform");
    
        var values = tr.split('(')[1],
        values = values.split(')')[0],
        values = values.split(',');
    
        var mat = [ [1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 1, 0], [0, 0, 0, 1] ];    
        if (values.length === 16)
        {
            for (var i = 0; i < 4; ++i)
            {
                for (var j = 0; j < 4; ++j)
                {
                    mat[j][i] = +values[i * 4 + j];
                }
            }
        }
        else
        {
            for (var i = 0; i < 3; ++i)
            {
                for (var j = 0; j < 2; ++j)
                {
                    mat[j][i] = +values[i * 2 + j];
                }
            }
        }
    
        return mat;
    }
    
    function getTransformOrigin(ele)
    {
        var st = window.getComputedStyle(ele, null);
    
        var tr = st.getPropertyValue("-webkit-transform-origin") ||
                 st.getPropertyValue("-moz-transform-origin") ||
                 st.getPropertyValue("-ms-transform-origin") ||
                 st.getPropertyValue("-o-transform-origin") ||
                 st.getPropertyValue("transform-origin");
    
        var values = tr.split(' ');
    
        var out = [ 0, 0, 0, 1 ];
        for (var i = 0; i < values.length; ++i)
        {
            out[i] = parseInt(values[i]);
        }    
    
        return out;
    }
    
    function createTranslateMatrix(x, y, z)
    {
        var out = 
        [
            [1, 0, 0, x],
            [0, 1, 0, y],
            [0, 0, 1, z],
            [0, 0, 0, 1]
        ];
    
        return out;
    }
    
    function multiply(pre, post)
    {
        var out = [ [], [], [], [] ];
    
        for (var i = 0; i < 4; ++i)
        {       
            for (var j = 0; j < 4; ++j)
            {
                var sum = 0;
    
                for (var k = 0; k < 4; ++k)
                {
                    sum += (pre[k][i] * post[j][k]);
                }
    
                out[j][i] = sum;
            }
        }
    
        return out;
    }
    
    function computeTransformMatrix(tx, origin)
    {
       var out;
    
       var preMul = createTranslateMatrix(-origin[0], -origin[1], -origin[2]);
       var postMul = createTranslateMatrix(origin[0], origin[1], origin[2]);
    
       var temp1 = multiply(preMul, tx);
    
       out = multiply(temp1, postMul);
    
       return out;
    }
    
    function transformVertex(mat, vert)
    {
       var out = [ ];
    
        for (var i = 0; i < 4; ++i)
        {
            var sum = 0;
            for (var j = 0; j < 4; ++j)
            {
                sum += +mat[i][j] * vert[j];
            }
    
            out[i] = sum;
        }
    
       return out;
    }
    
    function projectVertex(vert)
    {
        var out = [ ];
    
        for (var i = 0; i < 4; ++i)
        {
            out[i] = vert[i] / vert[3];
        }
    
        return out;
    }
    

答案 1 :(得分:1)

注意:接受的答案不是跨浏览器兼容的。这与浏览器计算偏移属性的愚蠢多样的方式有关。 我将上面的答案改为使用

 var rect=this.getBoundingClientRect() 

,结果更具跨浏览器兼容性。

https://jsfiddle.net/2znLxda2/