0
votes

I’m having some trouble incorporating pan/zoom behaviour with the ability to also drag-move some shapes around on the canvas, using EaselJS.

I want to be able to move the shape ONLY if I mousedown on it, but if I mousedown on the stage (i.e. not on a shape), then I want to be able to pan the stage. This behaviour needs to be consistent regardless of the ‘zoom’ level (which is altered by the mousewheel).

I have read this: How to stop the event bubble in easljs? Which suggests that the stage mousedown events will fire regardless of whether I have clicked on a shape or empty space, so it would be better to create a ‘background’ shape to capture my mousedown events that are not on a ‘proper’ shape.

This fiddle is how I have set it up: https://jsfiddle.net/hmcleay/mzheuLbg/

var stage = new createjs.Stage("myCanvas");
console.log('stage.scaleX: ', stage.scaleX);
console.log('stage.scaleY: ', stage.scaleY);

function addCircle(r,x,y){
    var g=new createjs.Graphics().beginFill("#ff0000").drawCircle(0,0,r);
    var s=new createjs.Shape(g)
    s.x=x;
    s.y=y;

  s.on('pressmove',  function(ev) {
    var localpos = stage.globalToLocal(ev.stageX, ev.stageY)
    s.x = localpos.x;
    s.y = localpos.y;
    stage.update();
    });

    stage.addChild(s);
    stage.update();
}

// create a rectangle 'background' Shape object to cover the stage (to allow for capturing mouse drags on anything except other shapes). 
bg = new createjs.Shape();
bg.graphics.beginFill("LightGray").drawRect(10, 10, stage.canvas.width - 20, stage.canvas.height - 20); //deliberately smaller for debugging purposes (easier to see if it moves). 
bg.x = 0;
bg.y = 0;
stage.addChild(bg);
stage.update();


//create a rectangle frame to represent the position of the stage. 
stageborder = new createjs.Shape();
stageborder.graphics.beginStroke("Black").drawRect(0, 0, stage.canvas.width, stage.canvas.height);
stageborder.x = 0;
stageborder.y = 0;
stage.addChild(stageborder);
stage.update();


// MOUSEWHEEL ZOOM LISTENER - anywhere on canvas.
var factor
canvas.addEventListener("wheel", function(e){
    if(Math.max(-1, Math.min(1, (e.wheelDelta || -e.detail)))>0){
        factor = 1.1;
    } else {
        factor = 1/1.1;
  }

  var local = stage.globalToLocal(stage.mouseX, stage.mouseY);
  stage.regX=local.x;
  stage.regY=local.y;
    stage.x=stage.mouseX;
    stage.y=stage.mouseY;   

    stage.scaleX = stage.scaleX * factor;
    stage.scaleY = stage.scaleY * factor;

  //re-size the 'background' shape to be the same as the canvas size.
  bg.graphics.command.w = bg.graphics.command.w / factor;
  bg.graphics.command.h = bg.graphics.command.h / factor;

  // re-position the 'background' shape to it's original position of (0,0) in the global space.  
  var localzero = stage.globalToLocal(0, 0);

  bg.x = localzero.x;
  bg.y = localzero.y;

    stage.update();
});

// listener to add circles to the canvas. 
canvas.addEventListener('dblclick', function(){
var localpos = stage.globalToLocal(stage.mouseX, stage.mouseY);
    addCircle(10, localpos.x, localpos.y);
});

bg.addEventListener("mousedown", function(ev1){
  // purpose of this listener is to be able to capture drag events on the 'background' to pan the whole stage. 
  // it needs to be a separate 'shape' object (rather than the stage itself), so that it doesn't fire when other shape objects are drag-moved around on the stage. 

  // get the initial positions of the stage, background, and mousedown. 
  var mousedownPos0 = {'x': ev1.stageX, 'y': ev1.stageY};
  var stagePos0 = {'x': stage.x, 'y': stage.y};
  var bgPos0 = {'x': bg.x, 'y': bg.y};

  bg.addEventListener('pressmove', function(ev2){
    //logic is to pan the stage, which will automatically pan all of it's children (shapes). 
    // except we want the 'background' shape to stay where it is, so we need to offset it in the opposite direction to the stage movement so that it stays where it is. 
    stageDelta = {'x': ev2.stageX - mousedownPos0.x, 'y': ev2.stageY - mousedownPos0.y};

    //adjust the stage position
    stage.x = stagePos0.x + stageDelta.x;
    stage.y = stagePos0.y + stageDelta.y;

    // return the 'background' shape to global(0,0), so that it doesn't move with the stage. 
    var localzero = stage.globalToLocal(0,0);
    bg.x = localzero.x;
    bg.y = localzero.y;

    stage.update();


  });

});

The grey box is my background shape. I have deliberately made it slightly smaller than the canvas, so that I can see where it is (useful for debugging). Double click anywhere on the canvas to add some red circles. If you drag a circle, it only moves that circle.

If you drag on the grey ‘background’ area in between circles, it moves the whole stage (and therefore all the child shapes belonging to the stage). Because the grey background is also a child of the stage, it wants to move with it. So I have included some code to always return that grey box back to where it started. The black border represents the position of the ‘stage’, I just added it to help visualise where the stage is.

The mousewheel zoom control is based on the answer to this question: EaselJS - broken panning on zoomed image

Similar to drag-panning, when zooming I have to adjust the size and position of the grey ‘background’ box so that it renders in the same position on the canvas. However, it doesn’t stay exactly where I want it to… it seems to creep up towards the top left corner of the canvas when I zoom out. I’ve spent quite some time trying to diagnose this behaviour and can’t find out why it’s happening. I suspect it may have something to do with rounding.. but I’m really not sure.

Can anyone explain why my grey box isn't staying stationary when I zoom in and out?

An alternative method would be to scrap the ‘background’ shape used for capturing mousedown events that aren’t on a ‘proper’ shape. Instead, it might be possible to use the ‘stage’ mousedown event, but prevent it from moving the stage if the mouse is over a ‘shape’. Would this be a better way of handling this behaviour? Any suggestions how to prevent it from moving the stage?

Thanks in advance, Hugh.

1

1 Answers

0
votes

Ok,

So as usually happens, after finally asking for help, I managed to work out the problem.

The issue was caused by making the background shape (grey rectangle) 10px smaller than the canvas, so that I could see its position more clearly (to assist with debugging). How ironic that this offset was causing the issue. The 10px offset was not being converted into the 'local' space when the zoom was applied. By making the grey rectangle's graphic position at (0,0) with width and height equal to that of the canvas, the problem went away!

Hope this is of use to someone at some point in time.

Cheers, Hugh.