簡體   English   中英

確定鼠標是否在 canvas 上的旋轉矩形上單擊

[英]Determine if mouse was clicked on a rotated rectangle on a canvas

我正在使用以下鏈接中發布的示例:

https://riptutorial.com/html5-canvas/example/19666/a-transformation-matrix-to-track-translated--rotated---scaled-shape-s-

我正在嘗試使用這個示例,對 canvas 上的 2 個矩形進行一些修改。有幾個問題(它們可能是相關的),但讓我從一個開始:

鼠標點擊不再有效。

我希望有人能幫助我指出需要什么補救措施,因為我為此苦苦掙扎了一天多,顯然我對 Javascrip 中的對象和類並不了解。 代碼在這里: https://jsfiddle.net/jackmstein/ngyfrcms/2/

        <!doctype html>

        <html>

        <head>

        <style>

            body{ background-color:white; }

            #canvas{border:1px solid red; }

        </style>

        <script>

        window.onload=(function(){

         

            var canvas=document.getElementById("canvas");

            var ctx=canvas.getContext("2d");

            var cw=canvas.width;

            var ch=canvas.height;

            function reOffset(){

                var BB=canvas.getBoundingClientRect();

                offsetX=BB.left;

                offsetY=BB.top;        

            }

            var offsetX,offsetY;

            reOffset();

            window.onscroll=function(e){ reOffset(); }

            window.onresize=function(e){ reOffset(); }

         

            // Transformation Matrix "Class"

            

            var TransformationMatrix=( function(){

                // private

                var self;

                var m=[1,0,0,1,0,0];

                var reset=function(){ var m=[1,0,0,1,0,0]; }

                var multiply=function(mat){

                    var m0=m[0]*mat[0]+m[2]*mat[1];

                    var m1=m[1]*mat[0]+m[3]*mat[1];

                    var m2=m[0]*mat[2]+m[2]*mat[3];

                    var m3=m[1]*mat[2]+m[3]*mat[3];

                    var m4=m[0]*mat[4]+m[2]*mat[5]+m[4];

                    var m5=m[1]*mat[4]+m[3]*mat[5]+m[5];

                    m=[m0,m1,m2,m3,m4,m5];

                }

                var screenPoint=function(transformedX,transformedY){

                    // invert

                    var d =1/(m[0]*m[3]-m[1]*m[2]);

                    im=[ m[3]*d, -m[1]*d, -m[2]*d, m[0]*d, d*(m[2]*m[5]-m[3]*m[4]), d*(m[1]*m[4]-m[0]*m[5]) ];

                    // point

                    return({

                        x:transformedX*im[0]+transformedY*im[2]+im[4],

                        y:transformedX*im[1]+transformedY*im[3]+im[5]

                    });

                }

                var transformedPoint=function(screenX,screenY){

                    return({

                        x:screenX*m[0] + screenY*m[2] + m[4],

                        y:screenX*m[1] + screenY*m[3] + m[5]

                    });    

                }

                // public

                function TransformationMatrix(){

                    self=this;

                }

                // shared methods

                TransformationMatrix.prototype.translate=function(x,y){

                    var mat=[ 1, 0, 0, 1, x, y ];

                    multiply(mat);

                };

                TransformationMatrix.prototype.rotate=function(rAngle){

                    var c = Math.cos(rAngle);

                    var s = Math.sin(rAngle);

                    var mat=[ c, s, -s, c, 0, 0 ];    

                    multiply(mat);

                };

                TransformationMatrix.prototype.scale=function(x,y){

                    var mat=[ x, 0, 0, y, 0, 0 ];        

                    multiply(mat);

                };

                TransformationMatrix.prototype.skew=function(radianX,radianY){

                    var mat=[ 1, Math.tan(radianY), Math.tan(radianX), 1, 0, 0 ];

                    multiply(mat);

                };

                TransformationMatrix.prototype.reset=function(){

                    reset();

                }

                TransformationMatrix.prototype.setContextTransform=function(ctx){

                    ctx.setTransform(m[0],m[1],m[2],m[3],m[4],m[5]);

                }

                TransformationMatrix.prototype.resetContextTransform=function(ctx){

                    ctx.setTransform(1,0,0,1,0,0);

                }

                TransformationMatrix.prototype.getTransformedPoint=function(screenX,screenY){

                    return(transformedPoint(screenX,screenY));

                }

                TransformationMatrix.prototype.getScreenPoint=function(transformedX,transformedY){

                    return(screenPoint(transformedX,transformedY));

                }

                TransformationMatrix.prototype.getMatrix=function(){

                    var clone=[m[0],m[1],m[2],m[3],m[4],m[5]];

                    return(clone);

                }

                // return public

                return(TransformationMatrix);

            })();

         

            // DEMO starts here

         

            // create a rect and add a transformation matrix

            // to track it's translations, rotations & scalings

            var rect1={x:30,y:30,w:50,h:35,matrix:new TransformationMatrix()};

         

        // draw the untransformed rect in black

        //    ctx.strokeRect(rect.x, rect.y, rect.w, rect.h);

            // Demo: label

        //    ctx.font='11px arial';

        //    ctx.fillText('Untransformed Rect',rect.x,rect.y-10);

         

        // transform the canvas & draw the transformed rect in red

            ctx.translate(100,0);

            ctx.scale(2,2);

            ctx.rotate(Math.PI/12);

            // draw the transformed rect

            ctx.strokeStyle='red';

            ctx.strokeRect(rect1.x, rect1.y, rect1.w, rect1.h);

            ctx.font='6px arial';

            // Demo: label

            ctx.fillText('Rect: Translated, rotated & scaled',rect1.x,rect1.y-6); 

            // reset the context to untransformed state

            ctx.setTransform(1,0,0,1,0,0);

            // record the transformations in the matrix

            var m=rect1.matrix;

            m.translate(100,0);

            m.scale(2,2);

            m.rotate(Math.PI/12);

            // use the rect's saved transformation matrix to reposition, 

            //     resize & redraw the rect

            ctx.strokeStyle='blue';

            drawTransformedRect(rect1);

            // Demo: instructions

            ctx.font='14px arial';

            ctx.fillText('Demo: click inside the blue rect',30,200);

            

            

         

            

               // DEMO starts here

         

            // create a rect and add a transformation matrix

            // to track it's translations, rotations & scalings

            var rect2={x:150,y:30,w:50,h:35,matrix:new TransformationMatrix()};

         

        // draw the untransformed rect in black

        //    ctx.strokeRect(rect.x, rect.y, rect.w, rect.h);

            // Demo: label

        //    ctx.font='11px arial';

        //    ctx.fillText('Untransformed Rect',rect.x,rect.y-10);

         

        // transform the canvas & draw the transformed rect in red

            ctx.translate(100,0);

            ctx.scale(2,2);

            ctx.rotate(Math.PI/12);

            // draw the transformed rect

            ctx.strokeStyle='red';

            ctx.strokeRect(rect2.x, rect2.y, rect2.w, rect2.h);

            ctx.font='6px arial';

            // Demo: label

            ctx.fillText('Rect: Translated, rotated & scaled',rect2.x,rect2.y-6); 

            // reset the context to untransformed state

            ctx.setTransform(1,0,0,1,0,0);

            // record the transformations in the matrix

            var m=rect2.matrix;

            m.translate(100,0);

            m.scale(2,2);

            m.rotate(Math.PI/12);

            // use the rect's saved transformation matrix to reposition, 

            //     resize & redraw the rect

            ctx.strokeStyle='blue';

            drawTransformedRect(rect2);

            // Demo: instructions

          

            

         

           

            // redraw a rect based on it's saved transformation matrix

            function drawTransformedRect(r){

                // set the context transformation matrix using the rect's saved matrix

                m.setContextTransform(ctx);

                // draw the rect (no position or size changes needed!)

                ctx.rect( r.x, r.y, r.w, r.h );

                // reset the context transformation to default (==untransformed);

                m.resetContextTransform(ctx);

            }

         

            // is the point in the transformed rectangle?

            function isPointInTransformedRect(r,transformedX,transformedY){

                var p=r.matrix.getScreenPoint(transformedX,transformedY);

                var x=p.x;

                var y=p.y;

                return(x>r.x && x<r.x+r.w && y>r.y && y<r.y+r.h);

            } 

         

            // listen for mousedown events

            canvas.onmousedown=handleMouseDown;

            function handleMouseDown(e){

                // tell the browser we're handling this event

                e.preventDefault();

                e.stopPropagation();

                // get mouse position

                mouseX=parseInt(e.clientX-offsetX);

                mouseY=parseInt(e.clientY-offsetY);

                // is the mouse inside the transformed rect?

                var rect1={x:30,y:30,w:50,h:35,matrix:new TransformationMatrix()};

                if(isPointInTransformedRect(rect1,mouseX,mouseY)){

                    alert('You clicked in the transformed Rect 1');

                }

               

               var rect2={x:150,y:30,w:50,h:35,matrix:new TransformationMatrix()};

                if(isPointInTransformedRect(rect2,mouseX,mouseY)){

                    alert('You clicked in the transformed Rect 2');

                }

             

         

            }

         

            // Demo: redraw transformed rect without using

            //       context transformation commands

            function drawTransformedRect(r,color){

                var m=r.matrix;

                var tl=m.getTransformedPoint(r.x,r.y);

                var tr=m.getTransformedPoint(r.x+r.w,r.y);

                var br=m.getTransformedPoint(r.x+r.w,r.y+r.h);

                var bl=m.getTransformedPoint(r.x,r.y+r.h);

                ctx.beginPath();

                ctx.moveTo(tl.x,tl.y);

                ctx.lineTo(tr.x,tr.y);

                ctx.lineTo(br.x,br.y);

                ctx.lineTo(bl.x,bl.y);

                ctx.closePath();

                ctx.strokeStyle=color;

                ctx.stroke();

            }

         

        }); // end window.onload

        </script>

        </head>

        <body>

            <canvas id="canvas" width=600 height=250></canvas>

        </body>

        </html>

指向旋轉的矩形

注意這個答案使用統一變換。 (傾斜不起作用,x 和 y 軸必須具有相同的刻度);

為了盡可能簡單,矩形(框)由其中心pos及其寬度和高度size定義。

const Point = (x = 0, y = 0) => ({x, y});
const Size = (w = 0, h = 0) => ({w, h});
const Rect = (pos, size) = ({pos, size});

我們可以通過根據需要創建變換來創建旋轉(變換)的rect (比創建DOMMatrix或自定義矩陣更快)

function pathRect(rect, ang, scale) { // ang in radians
    const xax = Math.cos(ang) * scale;
    const xay = Math.sin(ang) * scale;
    ctx.setTransform(xax, xay, -xay, xax, rect.pos.x, rect.pos.y);
    ctx.rect(-rect.size.w * 0.5, -rect.size.h * 0.5, rect.size.w, rect.size.h);
}

為了找到相對於rect的點,我們應用應用於rect的變換的逆(反向)。

relPoint轉換為框相對單位值。 也就是說,如果該點位於(旋轉矩形的)左上角,則relPoint將為 {x:0, y:0},位於中心 {x: 0.5, y: 0.5},右下角為 {x: 1, y :1}。 這簡化了邊界測試(見演示)

    function pointRelative2Rect(point, relPoint, rect, ang, scale) {
        const xax = Math.cos(-ang) / scale;
        const xay = Math.sin(-ang) / scale;
        const x = point.x - rect.pos.x;
        const y = point.y - rect.pos.y;
        relPoint.x = (xax * x - xay * y) / rect.size.w + 0.5;
        relPoint.y = (xay * x + xax * y) / rect.size.h + 0.5;
    }

因此,如果我們有一個mouse位置,那么我們可以找到它何時在rect上使用

    pointRelative2Rect(mouse, mouseRel, rect, rot, scale);
    if (mouseRel.x < 0 || mouseRel.x > 1 || mouseRel.y < 0 || mouseRel.y > 1) {
        // mouse outside rect
    } else {
        // mouse over rect
    }

演示

演示創建一個隨機矩形,該矩形動畫隨時間旋轉和縮放。 它使用上面概述的方法來測試鼠標是否在rect內。 當鼠標懸停時,該框將變為紅色。

 const ctx = canvas.getContext("2d"); const w = 256, h = 256; const Point = (x = 0, y = 0) => ({x, y}); const Size = (w = 0, h = 0) => ({w, h}); const Rect = (pos, size) => ({pos, size}); const randI = (min, max) => Math.random() * (max - min) + min; const mouse = Point(), mouseRel = Point(); document.addEventListener("mousemove", e => { mouse.x = e.pageX; mouse.y = e.pageY; }); const randRect = () => Rect( Point(randI(40, 210), randI(40, 210)), Size(randI(10, 30), randI(10, 30)) ); const rect = randRect(); function pathRect(rect, ang, scale) { // ang in radians const xax = Math.cos(ang) * scale; const xay = Math.sin(ang) * scale; ctx.setTransform(xax, xay, -xay, xax, rect.pos.x, rect.pos.y); ctx.rect(-rect.size.w * 0.5, -rect.size.h * 0.5, rect.size.w, rect.size.h); } function pointRelative2Rect(point, resPoint, rect, ang, scale) { const xax = Math.cos(-ang) / scale; const xay = Math.sin(-ang) / scale; const x = point.x - rect.pos.x; const y = point.y - rect.pos.y; resPoint.x = (xax * x - xay * y) / rect.size.w + 0.5; resPoint.y = (xay * x + xax * y) / rect.size.h + 0.5; } requestAnimationFrame(renderLoop); function renderLoop(time) { ctx.setTransform(1,0,0,1,0,0); ctx.clearRect(0, 0, w, h); const rot = time / 3000; const scale = Math.sin(time / 2777) * 0.5 + 2.1; ctx.beginPath(); pointRelative2Rect(mouse, mouseRel, rect, rot, scale); if (mouseRel.x < 0 || mouseRel.x > 1 || mouseRel.y < 0 || mouseRel.y > 1) { canvas.style.cursor = "default"; ctx.strokeStyle = "#000"; } else { canvas.style.cursor = "pointer"; ctx.strokeStyle = "#F00"; } pathRect(rect, rot, scale); ctx.lineWidth = 1 / scale; ctx.stroke(); requestAnimationFrame(renderLoop); }
 canvas { position: absolute; top: 0px; left: 0px; }
 <canvas id="canvas" width="256" height="256"><canvas>

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM