hampi2017
hampi2017

Reputation: 721

To drag shapes drawn on canvas same as paint

I have tool similar to paint where i allow user to draw different shapes on canvas using mouse events.I want to allow the user to drag the shapes(same as paint) once they are drawn on canvas. Did anyone done like this before? I have already tried using OOPDragging but that didn't work in my case.Also my tool include all kind of shapes like Line,elbow connector,oval,text,image and not just circles and rectangles. Can anyone please suggest some easy to achieve solution for this as i need it ASAP. Thanks in advance.

Upvotes: 2

Views: 3942

Answers (2)

markE
markE

Reputation: 105015

A Demo: http://jsfiddle.net/m1erickson/JrzM2/

Assume you've created this triangle in your paint program

enter image description here

And the points for that triangle are in an array like this:

[{x:0,y:20},{x:30,y:0},{x:70,y:45}]

To move that triangle to [20,35], you need to first offset the triangle by x:20 & y:35

var myTriangle={
    x:20,
    y:35,
    points:[{x:0,y:20},{x:30,y:0},{x:70,y:45}]
}

Then you can draw the triangle at [20,35] like this:

Note that the offsets (20,35) are added to each point in the triangle's position

function draw(myTriangle){

    var x=myTriangle.x;
    var y=myTriangle.y;
    var points=myTriangle.points;

    ctx.beginPath();
    ctx.moveTo( x+points[0].x, y+points[0].y );
    for(var i=1;i<points.length;i++){
        ctx.lineTo( x+points[i].x, y+points[i].y );
    }
    ctx.closePath();
    ctx.fill();

}

To drag the triangle, you listen for mouse events

  • In mousedown, check if the mouse is over the triangle. If yes, start the drag.
  • In mousemove, add the distance the user dragged since the last mousemove to the triangle's position.
  • In mouseup, stop the drag

In mousedown

Canvas has a nice built-in function to test if any specified point is inside a path like the triangle.

This function is context.isPointInPath(mouseX,mouseY) and it tests if mouseX/mouseY is inside the last drawn path.

If the mouse was pressed over the triangle, we set the isSelected flag to indicate the triangle should be dragged with every mousemove.

So the mousedown function looks like this:

function handleMouseDown(e){

  // tell the browser we're using mousedown, 
  // so don't bother doing any browser stuff with this event
  e.preventDefault();

  // get the current mouseX,mouseY position
  mouseX=parseInt(e.clientX-offsetX);
  mouseY=parseInt(e.clientY-offsetY);

  // test if mouseX,mouseY is inside the triangle
  if(ctx.isPointInPath(startX,startY)){

      // if yes, set the "isSelected" flag
      // which indicates that the triangle should 
      // move with the mouse
      isSelected=true;
  }
}

In mousemove

The mousemove event is triggered about 20-30 times per second as the user moves the mouse.

In mousemove, if the triangle isSelected, we want to calculate how far the mouse has moved since the last mousemove event.

Then we want to change the x,y position of the triangle by the distance that the mouse has moved.

So the mousemove function looks like this:

function handleMouseMove(e){

  // if the triangle wasn't selected during mousedown
  // there's nothing to do, so just return
  if(!isSelected){return;}
  e.preventDefault();

  // get the current mouse position
  mouseX=parseInt(e.clientX-offsetX);
  mouseY=parseInt(e.clientY-offsetY);

  // calculate how far the mouse has moved since the last mousemove
  var dx=mouseX-startX;
  var dy=mouseY-startY;

  // for next mousemove, reset the starting XY to the current XY
  startX=mouseX;
  startY=mouseY;

  // move the triangle by the change in mouse position 
  myTriangle.x+=dx;
  myTriangle.y+=dy;

  // clear the canvas and 
  // redraw the triangle at its new position
  context.clearRect(0,0,canvas.width,canvas.height);
  draw(myTriangle);

}

In mouseup

In mouseup, the isSelected flag is cleared since the drag is over:

function handleMouseUp(e){
  e.preventDefault();
  isSelected=false;
}

Here's code for a more complex example with multiple shapes:

<!doctype html>
<html>
<head>
<link rel="stylesheet" type="text/css" media="all" href="css/reset.css" /> <!-- reset css -->
<script type="text/javascript" src="http://code.jquery.com/jquery.min.js"></script>
<style>
    body{ background-color: ivory; }
    #canvas{border:1px solid red;}
</style>
<script>
$(function(){

    // vars related to canvas
    var canvas=document.getElementById("canvas");
    var ctx=canvas.getContext("2d");
    var $canvas=$("#canvas");
    var canvasOffset=$canvas.offset();
    var offsetX=canvasOffset.left;
    var offsetY=canvasOffset.top;
    var scrollX=$canvas.scrollLeft();
    var scrollY=$canvas.scrollTop();
    var cw=canvas.width;
    var ch=canvas.height;

    // vars related to dragging
    var isDown=false;
    var startX;
    var startY;

    // save your shape-points in
    var shapes=[];
    var selectedShape=null;

    // test shapes
    addShape(50,50,[{x:0,y:20},{x:30,y:0},{x:70,y:45}],"blue","red");
    addShape(100,100,
        [{x:0,y:10},{x:30,y:10},{x:30,y:0},
        {x:45,y:15},{x:30,y:30},{x:30,y:20},{x:0,y:20}],
        "green","red");

    // begin...
    drawAll();


    function addShape(x,y,points,fill,stroke){
        shapes.push({x:x,y:y,points:points,fill:fill,stroke:stroke});
    }

    function define(shape){
        var x=shape.x;
        var y=shape.y;
        var points=shape.points;
        ctx.beginPath();
        ctx.moveTo(x+points[0].x,y+points[0].y);
        for(var i=1;i<points.length;i++){
            ctx.lineTo(x+points[i].x,y+points[i].y);
        }
        ctx.closePath();
    }

    function draw(shape){
        define(shape);
        ctx.fillStyle=shape.fill;
        ctx.fill();
        ctx.strokeStyle=shape.stroke;
        ctx.stroke();
    }

    function drawAll(){
        ctx.clearRect(0,0,cw,ch);
        for(var i=0;i<shapes.length;i++){
            draw(shapes[i]);
        }
    }

    function handleMouseDown(e){
      e.preventDefault();
      startX=parseInt(e.clientX-offsetX);
      startY=parseInt(e.clientY-offsetY);
      for(var i=0;i<shapes.length;i++){
          define(shapes[i]);
          if(ctx.isPointInPath(startX,startY)){
              selectedShape=shapes[i];
              isDown=true;
          }
      }
    }

    function handleMouseUp(e){
      e.preventDefault();
      isDown=false;
      selectedShape=null;
    }

    function handleMouseOut(e){
      e.preventDefault();
      isDown=false;
      selectedShape=null;
    }

    function handleMouseMove(e){
      if(!isDown){return;}
      e.preventDefault();
      mouseX=parseInt(e.clientX-offsetX);
      mouseY=parseInt(e.clientY-offsetY);
      var dx=mouseX-startX;
      var dy=mouseY-startY;
      startX=mouseX;
      startY=mouseY;

      selectedShape.x+=dx;
      selectedShape.y+=dy;
      drawAll();

    }

    $("#canvas").mousedown(function(e){handleMouseDown(e);});
    $("#canvas").mousemove(function(e){handleMouseMove(e);});
    $("#canvas").mouseup(function(e){handleMouseUp(e);});
    $("#canvas").mouseout(function(e){handleMouseOut(e);});

}); // end $(function(){});
</script>

</head>
<body>
    <h4>Drag the shapes around the canvas</h4>
    <canvas id="canvas" width=300 height=300></canvas>
</body>
</html>

Upvotes: 4

Jonas Grumann
Jonas Grumann

Reputation: 10776

I had the same problem some time ago, but I only needed lines, but I think you can use my solution for all the different shapes.

Basically you'll need to overlapping canvases, one that listens to the mouse events and draws the temporary shape, once the user releases the mouse button, you get the values and draw the shape on the original canvas. Here's a link to what I did: http://www.zhereicome.com/experiments/statics/drawtogether/

and the main js file: http://www.zhereicome.com/experiments/statics/drawtogether/js/main.js

The important part is: temp_canvas.onmousemove = function(e) { //console.log(e); if ( self.mouseDown ) { self.drawTempLine(e.clientX, e.clientY); } }

    temp_canvas.onmousedown = function(e) {
        start = {
            x: e.clientX,
            y: e.clientY
        }
        self.mouseDown = true;
    }

    temp_canvas.onmouseup = function(e) {
        end = {
            x: e.clientX,
            y: e.clientY
        }
        self.mouseDown = false;
        self.sendToServer(); //When the user has finished drawing the line, send it to the server
    }

I have a function sendToServer() because my project was a realtime multiplayer drawing app. In your case you'll have to replace it with a drawOnFinalCanvas();

Upvotes: 0

Related Questions