简体   繁体   English

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

[英]get real 2D vertex coordinates of a div after CSS 3D transformations with Javascript

I've been trying to figure this out for a couple of days now but I can't seem to get it right. 我已经尝试了几天,但似乎无法正确解决。

Basically, I have some divs whose parent has a CSS perspective and rotateX 3D transformations applied and I need to get the actual on-screen coordinates of those divs. 基本上,我有一些div,其父级具有CSS透视图并应用了rotateX 3D转换,我需要获取这些div的实际屏幕坐标。

Here's a jsfiddle with an example of what I mean (albeit not working properly). 这是一个jsfiddle,上面有我的意思的示例(尽管无法正常工作)。

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

As you can see, the vertexes are off (thanks to the transformations of its parents) 如您所见,顶点已关闭(由于其父级的转换)

I've tried using 我试过使用

getBoundingClientRect()

but that doesn't seem to be taking the 3D transforms into consideration. 但这似乎并没有考虑3D转换。 I don't know if there's an already established method to get what I need but otherwise I guess there must be a way of calculating the coordinates using the matrix3D. 我不知道是否有一种已经建立的方法来获取我所需要的东西,否则我想必须有一种使用matrix3D计算坐标的方法。

Any help is appreciated. 任何帮助表示赞赏。

As it is there is not a builtin way to get the actual 2d coordinates of each vertex of the transformed element. 因为它没有内置的方法来获取转换后的元素的每个顶点的实际2d坐标。 In the case of all the APIs (such as getBoundingClientRect), they return a bounding rectangle of the transformed element represented as a 2point rectangle [(top,left), (bottom,right)]. 对于所有API(例如getBoundingClientRect),它们返回已转换元素的边界矩形,表示为2点矩形[(top,left),(bottom,right)]。

That being said, you can absolutely get the actual coordinates with a little bit of effort and matrix math. 话虽如此,您只需花费一点点精力和矩阵数学就可以绝对获得实际坐标。 The easiest thing to do would be to use a premade matrix library to do the math (I've head good things about math.js but have not used it), although it is certainly doable yourself. 最简单的方法是使用预制的矩阵库进行数学运算(我对math.js有所了解,但并未使用过),尽管它当然可以自己实现。

In pseudo-code for what you will need to do: 用伪代码执行以下操作:

  1. Get the untransformed bounds of the transformed parent element in the document coordinate system. 获取文档坐标系中已转换父元素的未转换范围。
  2. Get the untransformed bounds of the target element in the document coordinate system. 获取文档坐标系中目标元素的未变换范围。
  3. Compute the target's untransformed bounds relative to the parent's untransformed bounds. 相对于父对象的未变换范围,计算目标的未变换范围。 a. 一种。 Subtract the top/left offset of (1) from the bounds of (2). 从(2)的边界减去(1)的上/左偏移。
  4. Get the css transform of the parent element. 获取父元素的css转换。
  5. Get the transform-origin of the parent element (defaults to (50%, 50%)). 获取父元素的变换原点(默认为(50%,50%))。
  6. Get the actual applied transform (-origin * css transform * origin) 获取实际应用的转换(-origin * css transform * origin)
  7. Multiply the four vertices from (3) by the computed transform from (6). 将(3)中的四个顶点乘以(6)中计算出的变换。
  8. Perform the homogeneous divide (divide x, y, z by the w component) to apply perspective. 执行齐次除法(将x,y,z除以w分量)以应用透视图。
  9. Transform the projected vertices back into the document coordinate system. 将投影的顶点转换回文档坐标系。
  10. Fun! 有趣!

And then for fun in real code: https://jsfiddle.net/cLnmgvb3/1/ 然后在真实代码中获得乐趣: 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;
}

Note: The accepted answer is not cross browser compatible. 注意:接受的答案与跨浏览器不兼容。 This has to do with the stupidly diverse ways browsers calculate offset properties. 这与浏览器计算偏移量属性的愚蠢方式有关。 I changed the answer above to use 我更改了上面的答案以使用

 var rect=this.getBoundingClientRect() 

and the results is more cross-browser compatible. 并且结果更加兼容跨浏览器。

https://jsfiddle.net/2znLxda2/ https://jsfiddle.net/2znLxda2/

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM