Horai Nuri
Horai Nuri

Reputation: 5578

Snap shape to center with FabricJS

I created a Vertical line in the center of my canva, I'd like my shape to horizontaly align by it's center, right on the line when it comes close to it (the user can take it out if he doesn't want to align).

I'm having a problem with my JSFiddle : https://jsfiddle.net/z0u05hee/5/

The issue is that when the shape moves it stays block near the center of the line and I cannot get it out anymore.

Here is my FabricJS code :

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

var line9 = new fabric.Line([
  canvas.width / 2, 0,
  canvas.width / 2, canvas.width
],{
  stroke: 'green',
})

line9.selectable = false;
line9.evented = false;
canvas.add(line9);

canvas.add(new fabric.Circle({ 
  left: 10, 
  top: 100, 
  radius: 50, 
  fill: '#9f9', 
  originX: 'left', 
  originY: 'top',
  centeredRotation: true
}));

canvas.on('object:moving', function(options) {
    if (Math.round(options.target.left) < (canvas.width / 2) - 15 ||
    Math.round(options.target.left) > (canvas.width / 2) + 15) {
      options.target.set({
        left: Math.round(canvas.width / 2),
      }).setCoords();
    }
});

How should I fix my code ?


UPDATE :

canvas.on('object:moving', function(options) {
    if (Math.round(options.target.left) >= (canvas.getActiveObject().getWidth()) - 20 && Math.round(options.target.left) <= (canvas.getActiveObject().getWidth()) + 20) {
      options.target.set({
        left: (canvas.width / 2) - canvas.getActiveObject().getWidth() / 2,
      }).setCoords();
    }
});

This works but when I scale the shape the center point change it's coordination.

Upvotes: 1

Views: 2830

Answers (4)

Dmitry_j
Dmitry_j

Reputation: 11

I added some improvements to the Tim Harker code. This code solves several problems:

  1. Allows to get vertical and horizontal magnetic centering;
  2. Fixed "getWidth() - not a function" bug;
  3. Fixed centering bug for different objects with setted width or height (in example images from unsplash);
  4. Fixed bug with centering after objects scale;
  5. Added lines hiding feature after mouse up event.

var line9 = new fabric.Line([
    canvas.width / 2, 0,
    canvas.width / 2, canvas.width
], {
    strokeDashArray: [5, 5],
    stroke: 'red',
})

line9.selectable = false;
line9.evented = false;

var line10 = new fabric.Line([
    0, canvas.height / 2,
    canvas.width, canvas.height / 2

], {
    strokeDashArray: [5, 5],
    stroke: 'red',
    strokeWidth: 1,
})

line10.selectable = false;
line10.evented = false;



var snapZone = 15;

canvas.on('object:moving', function(options) {
    var objectMiddleHorizontal = options.target.left + (options.target.width * options.target.scaleX) / 2;

    if (objectMiddleHorizontal > canvas.width / 2 - snapZone &&
        objectMiddleHorizontal < canvas.width / 2 + snapZone) {
        options.target.set({
            left: canvas.width / 2 - (options.target.width * options.target.scaleX) / 2,
        }).setCoords();

        canvas.add(line9);

        document.addEventListener("mouseup", () => {
            canvas.remove(line9);
        });

    } else {
        canvas.remove(line9);
    }

    var objectMiddleVertical = options.target.top + (options.target.height * options.target.scaleY) / 2;

    if (objectMiddleVertical > canvas.height / 2 - snapZone &&
        objectMiddleVertical < canvas.height / 2 + snapZone) {
        options.target.set({
            top: canvas.height / 2 - (options.target.height * options.target.scaleY) / 2,
        }).setCoords();

        canvas.add(line10);

        document.addEventListener("mouseup", () => {
            canvas.remove(line10);
        });

    } else {
        canvas.remove(line10);
    }

});

https://jsfiddle.net/dmitryjast/wb3jmv9g/2/

Enjoy!

Upvotes: 1

amirking59
amirking59

Reputation: 9

Tims answer is correct but if you rotate object it will not work. i find a way to fix that problem:

  const objectMiddleFromLeft = object.getCenterPoint().x;
  const objectMiddleFromTop = object.getCenterPoint().y;

  object.setPositionByOrigin(
    {
      x:
        objectMiddleFromLeft > canvas.width / 2 - 15 &&
        objectMiddleFromLeft < canvas.width / 2 + 15
          ? canvas.width / 2
          : objectMiddleFromLeft,
      y:
        objectMiddleFromTop > canvas.height / 2 - 15 &&
        objectMiddleFromTop < canvas.height / 2 + 15
          ? canvas.height / 2
          : objectMiddleFromTop,
    },
    "center",
    "center"
  );

Upvotes: 0

user15443651
user15443651

Reputation:

Things are a little more complicated when you resize the canvas after you put an object on it. For whatever reason fabric js returns canvas.width in viewport coordinates, i.e. when you resize, width changes. object.width however is NOT affected by a canvas resize so stays the same. The following snippet works for me after resize of canvas and/or resize of objects. If anyone has a simpler solution to accomplish this - please share!

// Snapping while moving
canvas.on('object:moving', options => {
  const obj = options.target;

  // snap only if the object is upright
  if (obj.angle !== 0) {
    return;
  }
  
  // Capture area of snap
  let snap = 10;
  // Compensate snap size for transform
  const mySnap = snap / canvas.viewportTransform[0];

  // Sets corner position coordinates based on current angle, width and height
  obj.setCoords();

  // Snap left
  if (Math.abs(obj.oCoords.tl.x) < mySnap) {
    obj.left = 0;
  }
  // Snap right
  else if (Math.abs(obj.aCoords.tr.x - canvas.vptCoords.tr.x) < mySnap) {
    let canvasVptWidth = canvas.vptCoords.tr.x;
    obj.left = canvasVptWidth - obj.getScaledWidth();
  }
  // Snap center horizontally
  else if (Math.abs(obj.oCoords.mt.x - canvas.width / 2) < mySnap) {
    canvasVptWidth = canvas.vptCoords.br.x;
    obj.left = (canvasVptWidth - obj.getScaledWidth()) / 2;
  }

  // Snap top
  if (Math.abs(obj.aCoords.tl.y) < mySnap) {
    obj.top = 0;
  }
  // Snap bottom
  else if (Math.abs(obj.aCoords.br.y - canvas.vptCoords.br.y) < mySnap) {
    let canvasVptHeight = canvas.vptCoords.br.y;
    obj.top = canvasVptHeight - obj.getScaledHeight();
  }
  // Snap center vertically
  else if (Math.abs(obj.oCoords.ml.y - canvas.height / 2) < mySnap) {
    canvasVptHeight = canvas.vptCoords.br.y;
    obj.top = (canvasVptHeight - obj.getScaledHeight()) / 2;
  }
});

Upvotes: 2

Tim Harker
Tim Harker

Reputation: 2407

If I understood you correctly, this should do it:

fabricjs snap to grid

And a small tweak to your code (relevant part):

var snapZone = 15;
canvas.on('object:moving', function(options) {
  var objectMiddle = options.target.left + options.target.getWidth() / 2;
  if (objectMiddle > canvas.width / 2 - snapZone &&
    objectMiddle < canvas.width / 2 + snapZone) {
    options.target.set({
      left: canvas.width / 2 - options.target.getWidth() / 2,
    }).setCoords();
  }
});

And the all important JSFiddle update, https://jsfiddle.net/rekrah/9k9hd49u/.

Upvotes: 5

Related Questions