canvas 圖片拖拽旋轉之二——canvas狀態保存(save和restore)


引言

在上一篇日志“canvas 圖片拖拽旋轉之一”中,對坐標轉換有了比較深入的了解,但是僅僅利用坐標轉換實現的拖拽旋轉,會改變canvas坐標系的狀態,從而影響畫布上其他元素的繪制。因此,這個時候需要用到一對canvas方法,在變換坐標系前保存canvas狀態,在變換並繪制完成之后,恢復canvas狀態,即save()和restore()。

 [備注]

這篇文章只是記錄分享下解決問題的過程,找我要demo的,或者問我什么東西怎么做的,就不要加我了。你可以加一個canvas相關的交流群,或者如果需要用到KineticJS/FabricJS的話,可以加群251572039。

 

 一、理解save和restore的操作對象

對於save和restore方法,有一個誤解就是,認為每一步都save之后restore就等同於ctrl+z。其實save保存的只是CanvasRenderingContext2D 對象的狀態以及對象的所有屬性,並不包括這個對象上繪制的圖形。引用一段w3school上的解釋:

save() 和 restore() 方法允許你保存和恢復一個 CanvasRenderingContext2D 對象的狀態。save() 把當前狀態推入到棧中,而 restore() 從棧的頂端彈出最近保存的狀態,並且根據這些存儲的值來設置當前繪圖狀態。

CanvasRenderingContext2D 對象的所有屬性(除了畫布的屬性是一個常量)都是保存的狀態的一部分。變換矩陣和剪切區域也是這個狀態的一部分,但是當前路徑和當前點並不是。

也就是說,save()可以保存canvas的狀態(比如坐標系的狀態)以及fillStyle、strokeStyle、lineWidth 等等屬性。

 

基於這一點,我們就可以在變換坐標系之前save(),變換並繪制完成后restor(),這樣就可以保證圖形發生了旋轉偏移而canvas坐標系仍然是屏幕坐標系的狀態(類似於拿了一把標尺畫完圖之后又把標尺放回了原位)。關鍵代碼如下:

ctx.save();
ctx.translate(PO.x,PO.y);//坐標原點移動到圖片中心點
ctx.rotate(now_rotate_radian);//旋轉畫布 在屏幕坐標系基礎上旋轉 now_rotate_radian
ctx.drawImage(img,-imgW/2,-imgH/2);    
drawRotateCtrl();        
ctx.restore();//還原畫布坐標系

 

二、實現思路

因為涉及到旋轉,所以不管是進行拖拽還是旋轉,每次繪制都是先將canvas坐標系原點translate到圖片中心點,然后rotate旋轉的角度。

根據坐標變換原則,在旋轉時計算角度變化(鼠標相對於圖片中心點與屏幕坐標系y軸負方向的夾角),拖拽時記錄圖片中心點偏移。

 

三、代碼實現

  demo鏈接:http://youryida.duapp.com/demo_canvas/img_move_rotate.html

  

<!doctype html>
<html>
<head>
    <title> </title>
    <meta http-equiv="X-UA-Compatible" content="IE=9"> 
    <meta charset="utf-8" />
    <meta http-equiv="pragma" content="no-cache">
    <meta http-equiv="cache-control" content="no-cache">
    <meta http-equiv="expires" content="0">
    <style>
        #canvas{border:1px solid #ccc;}
    </style>
</head>
<body>
    <canvas id="canvas" width="500" height="300"></canvas>
    <br/>
    <pre>
    一、因為涉及到旋轉,所以不管是進行拖拽還是旋轉,每次繪制都是先將canvas坐標系原點移到圖片中心點。旋轉時記錄角度變化,拖拽時記錄圖片中心點偏移。
    二、為了不影響坐標系變換后其他圖形的繪制,使用ctx.save()保存旋轉前的坐標系,繪制完成后ctx.restore()恢復。
    QQ:1140215489
    </pre>
    <script>
    var cvs =document.getElementById("canvas");   
    var ctx =cvs.getContext("2d");
    var cvsH=cvs.height;
    var cvsW=cvs.width;
    var beginX,beginY;
    var LT={x:30,y:30};//圖片初始載入左上角坐標
    var PO;
    var Selected_Round_R=12;
    var now_rotate_radian=0;//記錄圖片的旋轉角度 初始為0
    var isDown=false;
    var moveAble=false,rotateAble=false;    
    var imgH,imgW;
    var img = new Image();    
    img.src ="img/niuniu.jpg";
    img.onload=function (){
        imgH=img.height;
        imgW=img.width;
        PO={x:LT.x+imgW/2,y:LT.y+imgH/2};
        onDraw();
    }
    function imgIsDown(x,y){
        var P={x:x,y:y};
        var A={x:PO.x-imgW/2,y:PO.y-imgH/2};
        var B={x:PO.x+imgW/2,y:PO.y-imgH/2};        
        var C={x:PO.x+imgW/2,y:PO.y+imgH/2};        
        var D={x:PO.x-imgW/2,y:PO.y+imgH/2};    
        A=getRotatedPoint(A,PO,now_rotate_radian);
        B=getRotatedPoint(B,PO,now_rotate_radian);
        C=getRotatedPoint(C,PO,now_rotate_radian);
        D=getRotatedPoint(D,PO,now_rotate_radian);
        var APB=getRadian(A,P,B);
        var BPC=getRadian(B,P,C);        
        var CPD=getRadian(C,P,D);
        var DPA=getRadian(D,P,A);
        var sum=(APB+BPC+CPD+DPA).toFixed(5);//某點與矩形四頂點夾角之和=360那么判斷點在矩形內
        return (sum==(2*Math.PI).toFixed(5));
    }
    function RTIsDown(x,y){
        var RT={x:PO.x,y:PO.y-imgH/2-Selected_Round_R};
        var rotate_top=getRotatedPoint(RT,PO,now_rotate_radian);
        var bool=(x-rotate_top.x)*(x-rotate_top.x)+(y-rotate_top.y)*(y-rotate_top.y)<=Selected_Round_R*Selected_Round_R;
        return bool;
    }
    function onDraw(){
        ctx.clearRect(0,0,cvsW,cvsH);
        ctx.save();
        ctx.translate(PO.x,PO.y);//坐標原點移動到圖片中心點
        ctx.rotate(now_rotate_radian);//旋轉畫布 在屏幕坐標系基礎上旋轉 now_rotate_radian
        ctx.drawImage(img,-imgW/2,-imgH/2);    
        drawRotateCtrl();        
        ctx.restore();//還原畫布坐標系
    }
    function drawRotateCtrl(){
        var rotate_top={x:0,y:-imgH/2-Selected_Round_R};
        ctx.beginPath();
        ctx.arc(rotate_top.x,rotate_top.y,Selected_Round_R,0,Math.PI*2,false);
        ctx.closePath();
        ctx.lineWidth=2;
        ctx.strokeStyle="#0000ff";
        ctx.stroke();
    }
    cvs.addEventListener("mousedown", startMove, false); 
    cvs.addEventListener("mousemove", moving, false);
    cvs.addEventListener("mouseup", endMove, false);         
    cvs.addEventListener("mouseout",endMove, false);
    function startMove(){
        preventDefault();
        isDown=true;
        var loc=getEvtLoc();
        var x=loc.x,y=loc.y;
        beginX=x,beginY=y;
        moveAble=imgIsDown(x,y);
        rotateAble=RTIsDown(x,y);
        if (moveAble) cvs.style.cursor="move";
        if (rotateAble) cvs.style.cursor="crosshair";
    }
    function moving(){
        preventDefault();
        if(isDown==false) return;
        var loc=getEvtLoc();
        var x=loc.x,y=loc.y;
        if(moveAble){
            var dx=x-beginX,dy=y-beginY;        
            PO.x=PO.x+dx;
            PO.y=PO.y+dy;
            onDraw();
        }
        if(rotateAble){
            var A={x:beginX-PO.x,y:beginY-PO.y};//轉換為以PO為原點的屏幕坐標系 計算鼠標move前后兩點與y軸反方向的角度
            var B={x:x-PO.x,y:y-PO.y};
            var a=Math.atan2(A.x,-A.y);
            var b=Math.atan2(B.x,-B.y);
            var θ=b-a;
            now_rotate_radian+=θ;    
            onDraw();
        }
        beginX=x,beginY=y;        
    }
    function endMove(){
        isDown=false;
        moveAble=rotateAble=false;
        cvs.style.cursor="auto";
    }
    function getEvtLoc(){
        return {x:event.offsetX,y:event.offsetY}
    }    
//取消瀏覽器默認事件
function preventDefault(){
    if(event.preventDefault){
        event.preventDefault();
    }else{
        event.returnValue = false;//注意加window
    }
}
//獲取三點角度
function getRadian(A,O,B) {
    var Xo=O.x,Yo=O.y;
    var Xa=A.x,Ya=A.y;
    var Xb=B.x,Yb=B.y;    
    var oa = Math.sqrt((Xo - Xa) * (Xo - Xa) + (Yo - Ya)* (Yo - Ya));
    var ob = Math.sqrt((Xo - Xb) * (Xo - Xb) + (Yo - Yb)* (Yo - Yb));
    var ab = Math.sqrt((Xa - Xb) * (Xa - Xb) + (Ya - Yb)* (Ya - Yb));
    var aob = Math.acos((oa * oa + ob * ob - ab * ab) / (2 * oa * ob)); // 弧度
    return aob;//
}    
//獲取繞點旋轉角度后的新點坐標
function getRotatedPoint(A,O,α){
    var dx =A.x-O.x;
    var dy =A.y-O.y;
    var x=Math.cos(α)*dx-Math.sin(α)*dy+O.x;
    var y=Math.cos(α)*dy+Math.sin(α)*dx+O.y;
    return {x:x,y:y};
}    

    </script>
</body>
</html>

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM