Calculate absolute dimensions of a div rotated in perspective with css3

…衆ロ難τιáo~ 提交于 2019-11-30 06:48:27

I get a headache with matrices, so I'm doing this with proportions.

If you see the div from above (hence seeing the rotation in the two dimensions it takes place in), you're seeing it as a segment on the xz plane, with coordinates (-250, 0) (250, 0), or in general (-w/2, 0) (w/2, 0) After a rotation on the y axis, the coordinates will become, similarly to what you stated

(-Math.cos(angle) * w/2, -Math.sin(angle) * w/2)
( Math.cos(angle) * w/2,  Math.sin(angle) * w/2)

, being the rotation counterclockwise, with the origin at the center of the div, and of angle radians.

Using the perspective means that these coordinates are not displayed just by discarding the z, but they are first projected according to their distance from the observer.

Now, the projection plane is the one where the unrotated things lay, with z = 0. I deduce this from the fact that when unrotated divs are projected, they remain the same size. If you take a point with distance p (the perspective value) from the z plane, so with xz coordinates (0, -p), and draw a line from this point to the vertices of the rotated segment, up to when it crosses the projection plan, the points you get are the new segment coordinates which yield the div final size.

With a proportion between the triangles (0, -p) (0, 0) (x, 0) and (0, -p) (0, sin*w/2) (cos*w/2, sin*w/2), you get that

p : x = (p + sin*w/2) : cos*w/2
x = (p * cos*w/2) / (p + sin*w/2)

which in general means that when you project the point (x, y, z) onto the plan you get

x * p / (p + z)
y * p / (p + z)
0

So your final div coordinates (on xz, relative to div's center) will be

(-Math.cos(angle) * w/2 * p / (p + -Math.sin(angle) * w/2), 0)
( Math.cos(angle) * w/2 * p / (p +  Math.sin(angle) * w/2), 0)

From which you can calculate its width but also its position - which is non trivial, since its nearest-to-the-viewer half will appear bigger than the other half.

Look at the following test for more details (it fails when you're too close to the objects, I'm not sure why, probably some variable overflows)

<!DOCTYPE html>
<html>
    <head>
    <script type="text/javascript" src="http://code.jquery.com/jquery-latest.js"></script>
    <script type="text/javascript">
    var WIDTH = 500;
    var P = 300;
    jQuery(function(){
        function test(width, angle, p) {
            $('body').
                append($('<div id="info" />')).
                append($('<div id="container" />').
                    css({
                        margin: '50px 0px',
                        border: '1px solid black',
                        width: width+'px',
                        '-webkit-perspective': p
                    }).
                    append($('<div id="real" />').addClass('the_div').css({ 'width': width+'px' }))).
                append($('<div id="fake" />').addClass('the_div'));

            setInterval(function() {
                angle += 1;

                $('#real').css({ '-webkit-transform': 'rotateY('+angle+'deg)' }).html(width);

                // initial coordinates
                var A = 0;
                var B = width;
                // translate the center (assuming -perspective-origin at 50%)
                A -= width/2;
                B -= width/2;
                // new coordinates
                A = calc(A, angle*Math.PI/180, p);
                B = calc(B, angle*Math.PI/180, p);
                // translate back
                A += width/2;
                B += width/2;
                if(B < A) { var tmp = A; A = B; B = tmp; } // swap
                var realwidth = B-A;
                $('#fake').html(width+'<br/>'+A+', '+B).css({
                    'width': realwidth+'px',
                    'margin-left': A+'px'
                });

                // shows debug information
                var debug = function(values) { return values.map(function(i){ return i+': '+eval(i); }).join('<br />'); }
                $('#info').html($('<div />').html(debug(['width', 'p', 'angle', 'A', 'B', 'realwidth'])));

            }, 40);
        }

        function calc(oldx, angle, p) {
            var x = Math.cos(angle) * oldx;
            var z = Math.sin(angle) * oldx;

            return x * p / (p+z);
        }

        test(WIDTH, 0, P);
    });
    </script>
    <style type="text/css">
        * { margin: 0px; padding: 0px; }
        body { padding: 40px 100px; }
        .the_div { height: 100px; border: 2px solid black; background-color: rgba(255, 192, 0, 0.5); }
    </style>
    </head>
    <body></body>
</html>

Note that if you're not giving a perspective value, the result will be equal as having an infinite value for it.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!