EnglishAdam
EnglishAdam

Reputation: 1390

How to scale a canvas during an animation?

I have two buttons that allow zoom in and zoom out of my canvas animation

<button id="zoomInButton">Zoom ++</button>
<button id="zoomOutButton">Zoom -- </button>

 <canvas id="myCanvasmatchPage" > 
        Sorry, your browser doesn't support canvas technology
</canvas>

And I have an animation loop (omitted) using setInterval and canvas. In order to not have the scaling happen each setInterval it has to be out of the draw function loop. In fact the ctx.scale is reset on button click.

$('#myCanvasmatchPage').attr({width:100,height:200}).css({width:'100px',height:'200px'});       

var canvas = document.getElementById("myCanvasmatchPage");
ctx = canvas.getContext("2d");
ctx.translate(0.5,0.5);

var nextFrame =0;
var animationData = [];

var canvasZoom = 1; //initial is scale 100%
var incrementZoom = 0.05;
$("#zoomInButton").click(function ()
   {
     canvasZoom += incrementZoom;
     ctx.scale(canvasZoom, canvasZoom);
   });
 $("#zoomOutButton").click(function ()
   {
     if (canvasZoom > 1){//i dont want to allow a less than 100% scale zoom
           canvasZoom -= incrementZoom; 
           ctx.scale(canvasZoom, canvasZoom);
    }
   });  


var draw = function(){
    //cannot set scale within animation loop! keeps resetting!
    ctx.clearRect(0, 0, canvas.width, canvas.height);
    ctx.drawImage(images['background'], 0, 0, 622, 924, 0, 0, 311, 462);

                if (nextFrame<=animationData.length){
                    //animation loop
                }else{
                    clearTimeout(draw);//get out of the loop
                    return;
                }

                nextFrame++;    
    }
    setInterval(draw,200);  

My current experience with this code is that zoomIn 'works' but zoomOut doesn-t. Console logging has shown me that the canvasZoom does go up and down by 0.05 consistently (also 0.05000001 ha ha sometimes) but the canvas will not zoom negatively.

It makes me think that the problem is in the clearing of the canvas and not the scaling.

Upvotes: 1

Views: 3072

Answers (2)

GameAlchemist
GameAlchemist

Reputation: 19294

You made the quite common mistake to think that scale will set the scale, when the Canvas's context2D is a state machine, which will cumulate each change on top of each other (expl scale X2 then scale x2 == scale X4 ).

The common answer to this issue is to save() your context, do your transforms, do your drawings, then to restore() it.

So just to give you direction (untested) :

function Transform() {
   this.scale = 1;
   this.rotation = 0;
   this.translation = {x:0, y:0 };
   this.zoomIn = function () {
    this.scale+=0.5;
   };
   this.zoomOut = function () {
    this.scale-=0.5;
   };
   this.translate(x,y) = function (x,y)  { 
     this.translation.x+=x;  // OR +=x*currentZoom;
     this.translation.y+=y;  // OR +=y*currentZoom;
   };
   this.rotate = function (angle) {
      this.rotation+=angle;
   };
   this.applyTransform = function (ctx) {
       ctx.translate(translation.x, translation.y);  // ?? order ??
       ctx.scale(currentZoom, currentZoom);          // ?? order ??
       ctx.rotate(rotation);                   
   }
}

var imageTransform = new Transform();

function drawImage(img) { 
  ctx.save();
  ctx.translate(img.width/2, img.height/2);
  imageTransform.applyTransform(ctx);
  ctx.drawImage(img, -img.wdth/2, -img.height/2);
  ctx.restore();
}

The order in which you translate/scale/rotate depends in fact of your intent : are you moving the camera, or moving the image ? Are you scaling the camera or the image ? (question relevant only if there's more than one item to show).

Hope this will help.

Upvotes: 1

EnglishAdam
EnglishAdam

Reputation: 1390

While I look into @GameAlchemist 's more thorough solution, here would be a solution for a simple transformation problem. Thanks to GameAlchemist for pointing out (very importantly!) that scale changes are cumulative!

In the click function, it's important to keep a clean copy of the base to scale against (here initialZoom)

    var initialZoom = 1;    //100% same as container (less border)
    var zoomCounter = 1;
    var incrementZoom = 0.05;
    var canvasZoom = 1;//just to declare

 $("#zoomInButton").click(function ()
   {
     canvasZoom = initialZoom + incrementZoom;
    zoomCounter = zoomCounter + incrementZoom;//so i can track compared to original and block cumulative < 100&
     ctx.scale(canvasZoom, canvasZoom);
   });
 $("#zoomOutButton").click(function ()
   {
     if (zoomCounter > 1){//i dont want to allow a less than 100% scale zoom
           canvasZoom = initialZoom - incrementZoom;
           zoomCounter = zoomCounter - incrementZoom;
           ctx.scale(canvasZoom, canvasZoom);
    }
   }); 

Now off to see if GameAlchemist-s solution allows me easier multi transforms!

Upvotes: 0

Related Questions