21

I have the following which doesn't work correctly:

var canvas = new fabric.Canvas('canvas');


canvas.observe('mouse:down', function(e) { mousedown(e); });
canvas.observe('mouse:move', function(e) { mousemove(e); });
canvas.observe('mouse:up', function(e) { mouseup(e); });


var started = false;


var x = 0;
var y = 0;


/* Mousedown */
function mousedown(e) {

    var mouse = canvas.getPointer(e.memo.e);

    started = true;

    x = mouse.x;
    y = mouse.y;    

    var square = new fabric.Rect({ 

        width: 1, 
        height: 1, 
        left: mouse.x, 
        top: mouse.y, 
        fill: '#000'

    });


    canvas.add(square); 
    canvas.renderAll();
    canvas.setActiveObject(square); 

}


/* Mousemove */
function mousemove(e) {

    if(!started) {

        return false;

    }

    var mouse = canvas.getPointer(e.memo.e);

    var x = Math.min(mouse.x,  x),
    y = Math.min(mouse.y,  y),
    w = Math.abs(mouse.x - x),
    h = Math.abs(mouse.y - y);

    if (!w || !h) {

        return false;

    }

    var square = canvas.getActiveObject(); 

    square.set('top', y).set('left', x).set('width', w).set('height', h);

    canvas.renderAll(); 

}


/* Mouseup */
function mouseup(e) {

    if(started) {

        started = false;    

    }   

 }

The above logic is from a simple rectangle drawing system I used without fabric.js so I know it works, just not with fabric.js.

It seems the maths is off or I'm setting the incorrect params with the width/height/x/y values, as when you draw the rectangle does not follow the cursor correctly.

Any help is much appreciated, thanks in advance :)

kangax
  • 38,898
  • 13
  • 99
  • 135
jahilldev
  • 3,520
  • 4
  • 35
  • 52
  • I don't understand why fabric doesn't have an event like "selection:completed" which passes to handler the selection bounds, it already handles this logic internally. I was trying to achieve the same and ended up adding this event to the source code. – krulik Mar 10 '12 at 09:02

6 Answers6

33

I have written an example for you. Please follow the link below:

http://jsfiddle.net/a7mad24/aPLq5/

var canvas = new fabric.Canvas('canvas', { selection: false });

var rect, isDown, origX, origY;

canvas.on('mouse:down', function(o){
    isDown = true;
    var pointer = canvas.getPointer(o.e);
    origX = pointer.x;
    origY = pointer.y;
    var pointer = canvas.getPointer(o.e);
    rect = new fabric.Rect({
        left: origX,
        top: origY,
        originX: 'left',
        originY: 'top',
        width: pointer.x-origX,
        height: pointer.y-origY,
        angle: 0,
        fill: 'rgba(255,0,0,0.5)',
        transparentCorners: false
    });
    canvas.add(rect);
});

canvas.on('mouse:move', function(o){
    if (!isDown) return;
    var pointer = canvas.getPointer(o.e);

    if(origX>pointer.x){
        rect.set({ left: Math.abs(pointer.x) });
    }
    if(origY>pointer.y){
        rect.set({ top: Math.abs(pointer.y) });
    }

    rect.set({ width: Math.abs(origX - pointer.x) });
    rect.set({ height: Math.abs(origY - pointer.y) });


    canvas.renderAll();
});

canvas.on('mouse:up', function(o){
  isDown = false;
});
Lee Taylor
  • 7,761
  • 16
  • 33
  • 49
user2256662
  • 441
  • 4
  • 6
  • 1
    but why is the object not movable even after removing the selection:false property. – Shamis Shukoor Sep 22 '15 at 09:02
  • 5
    If you want the object to be movable, you should add rect.setCoords(); in the "mouse:up" event handler – dinesh ygv Jul 10 '16 at 06:35
  • 2
    DO NOT forget to set remove the event listeners after you are done drawing, otherwise you will get duplicate shapes each time; I used ```canvas.off('mouse:down').off('mouse:move').off('mouse:up')``` inside the ```mouse:up``` event after all is done and it worked for me. – Hooman Askari Jan 14 '17 at 09:15
17

Looks like Fabric.js calculates everything from the origin. So, 'Top' and 'Left' are a bit misleading. Check the following link: Canvas Coordinates Have Offset. Also, I've changed a bit of your code:

var canvas = new fabric.Canvas('canvas');
canvas.observe('mouse:down', function(e) { mousedown(e); });
canvas.observe('mouse:move', function(e) { mousemove(e); });
canvas.observe('mouse:up', function(e) { mouseup(e); });

var started = false;
var x = 0;
var y = 0;

/* Mousedown */
function mousedown(e) {
    var mouse = canvas.getPointer(e.memo.e);
    started = true;
    x = mouse.x;
    y = mouse.y;

    var square = new fabric.Rect({ 
        width: 0, 
        height: 0, 
        left: x, 
        top: y, 
        fill: '#000'
    });

    canvas.add(square); 
    canvas.renderAll();
    canvas.setActiveObject(square); 

}


/* Mousemove */
function mousemove(e) {
    if(!started) {
        return false;
    }

    var mouse = canvas.getPointer(e.memo.e);

    var w = Math.abs(mouse.x - x),
    h = Math.abs(mouse.y - y);

    if (!w || !h) {
        return false;
    }

    var square = canvas.getActiveObject(); 
    square.set('width', w).set('height', h);
    canvas.renderAll(); 
}

/* Mouseup */
function mouseup(e) {
    if(started) {
        started = false;
    }

    var square = canvas.getActiveObject();

    canvas.add(square); 
    canvas.renderAll();
 } 
Community
  • 1
  • 1
bchetty
  • 2,231
  • 1
  • 19
  • 26
  • Thanks for the help, you definatly put me in the right direction. I modified the code so the top/left position is recalculated to always remain in the first position clicked so you can drag out a nice rectangle. Something like: var x = Math.abs(w/2 + x) – jahilldev Feb 24 '12 at 16:54
  • @sirjamm what if you drag the mouse to the left instead of to the right? in this solution the rect will still grow to the right instead of to the mouse direction – krulik Mar 10 '12 at 09:21
  • @sirjamm you should do something like: Math.abs(w/2 + Math.min(x, mouse.x)) – krulik Mar 10 '12 at 09:30
  • I've just tried this technique, and I find that if my "first click point" is on top of another object, then that object moves with the pointer, which is not ideal. Any suggestions for a workaround? – Chris Mar 06 '13 at 14:19
  • 3
    Why are you doing `canvas.add(square);` both in `mousedown()` and `mouseup()`? It looks OK, but when I save the canvas to SVG I have all objects duplicated. – kolenda Sep 05 '13 at 14:40
7

Here is the detail blog with jsfiddle - https://blog.thirdrocktechkno.com/drawing-a-square-or-rectangle-over-html5-canvas-using-fabricjs-f48beeedb4d3

var Rectangle = (function () {
    function Rectangle(canvas) {
        var inst=this;
        this.canvas = canvas;
        this.className= 'Rectangle';
        this.isDrawing = false;
        this.bindEvents();
    }

  Rectangle.prototype.bindEvents = function() {
    var inst = this;
    inst.canvas.on('mouse:down', function(o) {
      inst.onMouseDown(o);
    });
    inst.canvas.on('mouse:move', function(o) {
      inst.onMouseMove(o);
    });
    inst.canvas.on('mouse:up', function(o) {
      inst.onMouseUp(o);
    });
    inst.canvas.on('object:moving', function(o) {
      inst.disable();
    })
  }
    Rectangle.prototype.onMouseUp = function (o) {
      var inst = this;
      inst.disable();
    };

    Rectangle.prototype.onMouseMove = function (o) {
      var inst = this;
      

      if(!inst.isEnable()){ return; }
     
      var pointer = inst.canvas.getPointer(o.e);
      var activeObj = inst.canvas.getActiveObject();

      activeObj.stroke= 'red',
      activeObj.strokeWidth= 5;
      activeObj.fill = 'transparent';

      if(origX > pointer.x){
          activeObj.set({ left: Math.abs(pointer.x) }); 
      }
      if(origY > pointer.y){
          activeObj.set({ top: Math.abs(pointer.y) });
      }

      activeObj.set({ width: Math.abs(origX - pointer.x) });
      activeObj.set({ height: Math.abs(origY - pointer.y) });

      activeObj.setCoords();
      inst.canvas.renderAll();

    };

    Rectangle.prototype.onMouseDown = function (o) {
      var inst = this;
      inst.enable();

      var pointer = inst.canvas.getPointer(o.e);
      origX = pointer.x;
      origY = pointer.y;

     var rect = new fabric.Rect({
          left: origX,
          top: origY,
          originX: 'left',
          originY: 'top',
          width: pointer.x-origX,
          height: pointer.y-origY,
          angle: 0,
          transparentCorners: false,
          hasBorders: false,
          hasControls: false
      });

     inst.canvas.add(rect).setActiveObject(rect);
    };

    Rectangle.prototype.isEnable = function(){
      return this.isDrawing;
    }

    Rectangle.prototype.enable = function(){
      this.isDrawing = true;
    }

    Rectangle.prototype.disable = function(){
      this.isDrawing = false;
    }

    return Rectangle;
}());



var canvas = new fabric.Canvas('canvas');
var rect = new Rectangle(canvas);
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/1.7.17/fabric.min.js"></script>
 Please draw rectangle here

<div id="canvasContainer">
  <canvas id="canvas" width="400" height="400" style="border: solid 1px"></canvas>
</div>
krunal shah
  • 16,089
  • 25
  • 97
  • 143
  • 1
    Can't find the blog, But how ever thumbs up for the object oriented approach. – Gayan Kavirathne May 25 '20 at 13:09
  • Small bug: If the stroke is thick, as you resize the rectangle, you'll notice that the stroke of the rectangle is shown outside of the rectangle. To fix that you need to take into account the strokeWidth when setting the width/height property. – B B Jun 13 '20 at 23:52
  • I have same functionality but if i have an image already loaded on canvas. the image starts moving with mouse. How can I prevent image to move when drawing? – Najam Us Saqib Feb 21 '23 at 11:05
5

The answer above seems to be deprecated. I changed some things on the code below to fix that. And on the mousedown function I add the if to detect the active object to avoid creating a new rectangle when the user's move a selected object.

var canvas = new fabric.Canvas('canvas');

canvas.on('mouse:down', function(options) {
      if(canvas.getActiveObject()){
        return false;
      }

      started = true;
      x = options.e.clientX;
      y = options.e.clientY;

      var square = new fabric.Rect({ 
          width: 0, 
          height: 0, 
          left: x, 
          top: y, 
          fill: '#000'
      });

      canvas.add(square); 
      canvas.setActiveObject(square); 
  });

  canvas.on('mouse:move', function(options) {
    if(!started) {
        return false;
    }

    var w = Math.abs(options.e.clientX - x),
    h = Math.abs(options.e.clientY - y);

    if (!w || !h) {
        return false;
    }

    var square = canvas.getActiveObject(); 
    square.set('width', w).set('height', h);
  });

  canvas.on('mouse:up', function(options) {
    if(started) {
      started = false;
    }

    var square = canvas.getActiveObject();

    canvas.add(square); 
  });
Lucas Pedroza
  • 65
  • 1
  • 2
0

Slight modification on the mouse up event to allow the object to be selectable and moveable.

Note: adding the object to canvas again on the mouse:up even will add it twice to the canvase and it is the wrong thing to do.

canvas.on('mouse:up', function(o){
  isDown = false;
  var square = canvas.getActiveObject();
  square.setCoords();  //allows object to be selectable and moveable
});
jaxkewl
  • 195
  • 10
-1

you can use this simple code

canvas.add(new fabric.Rect({ left: 110, top: 110, fill: '#f0f', width: 50, height: 50 }));
let me down slowly
  • 822
  • 10
  • 27
Usman Yaqoob
  • 535
  • 5
  • 13