Search code examples
javascriptjqueryfabricjs

Fabricjs snap to grid on resize


I'm having some issues trying to get the objects to resize according to the grid size.

Here's my fiddle: http://jsfiddle.net/csh6c6fw/1/

this is the code that I'm applying:

canvas.on('object:scaling', (options) => {
  var newWidth = (Math.round(options.target.getWidth() / grid)) * grid;
  var newHeight = (Math.round(options.target.getHeight() / grid)) * grid;

  if (options.target.getWidth() !== newWidth) {
    options.target.set({ width: newWidth, height: newHeight });
  }

});

Expected Result

It's supposed to snap to the grids like the movement.


Solution

  • Might seem pretty complicated but, the following will get the job done :

    canvas.on('object:scaling', options => {
       var target = options.target,
          w = target.width * target.scaleX,
          h = target.height * target.scaleY,
          snap = { // Closest snapping points
             top: Math.round(target.top / grid) * grid,
             left: Math.round(target.left / grid) * grid,
             bottom: Math.round((target.top + h) / grid) * grid,
             right: Math.round((target.left + w) / grid) * grid
          },
          threshold = grid,
          dist = { // Distance from snapping points
             top: Math.abs(snap.top - target.top),
             left: Math.abs(snap.left - target.left),
             bottom: Math.abs(snap.bottom - target.top - h),
             right: Math.abs(snap.right - target.left - w)
          },
          attrs = {
             scaleX: target.scaleX,
             scaleY: target.scaleY,
             top: target.top,
             left: target.left
          };
       switch (target.__corner) {
          case 'tl':
             if (dist.left < dist.top && dist.left < threshold) {
                attrs.scaleX = (w - (snap.left - target.left)) / target.width;
                attrs.scaleY = (attrs.scaleX / target.scaleX) * target.scaleY;
                attrs.top = target.top + (h - target.height * attrs.scaleY);
                attrs.left = snap.left;
             } else if (dist.top < threshold) {
                attrs.scaleY = (h - (snap.top - target.top)) / target.height;
                attrs.scaleX = (attrs.scaleY / target.scaleY) * target.scaleX;
                attrs.left = attrs.left + (w - target.width * attrs.scaleX);
                attrs.top = snap.top;
             }
             break;
          case 'mt':
             if (dist.top < threshold) {
                attrs.scaleY = (h - (snap.top - target.top)) / target.height;
                attrs.top = snap.top;
             }
             break;
          case 'tr':
             if (dist.right < dist.top && dist.right < threshold) {
                attrs.scaleX = (snap.right - target.left) / target.width;
                attrs.scaleY = (attrs.scaleX / target.scaleX) * target.scaleY;
                attrs.top = target.top + (h - target.height * attrs.scaleY);
             } else if (dist.top < threshold) {
                attrs.scaleY = (h - (snap.top - target.top)) / target.height;
                attrs.scaleX = (attrs.scaleY / target.scaleY) * target.scaleX;
                attrs.top = snap.top;
             }
             break;
          case 'ml':
             if (dist.left < threshold) {
                attrs.scaleX = (w - (snap.left - target.left)) / target.width;
                attrs.left = snap.left;
             }
             break;
          case 'mr':
             if (dist.right < threshold) attrs.scaleX = (snap.right - target.left) / target.width;
             break;
          case 'bl':
             if (dist.left < dist.bottom && dist.left < threshold) {
                attrs.scaleX = (w - (snap.left - target.left)) / target.width;
                attrs.scaleY = (attrs.scaleX / target.scaleX) * target.scaleY;
                attrs.left = snap.left;
             } else if (dist.bottom < threshold) {
                attrs.scaleY = (snap.bottom - target.top) / target.height;
                attrs.scaleX = (attrs.scaleY / target.scaleY) * target.scaleX;
                attrs.left = attrs.left + (w - target.width * attrs.scaleX);
             }
             break;
          case 'mb':
             if (dist.bottom < threshold) attrs.scaleY = (snap.bottom - target.top) / target.height;
             break;
          case 'br':
             if (dist.right < dist.bottom && dist.right < threshold) {
                attrs.scaleX = (snap.right - target.left) / target.width;
                attrs.scaleY = (attrs.scaleX / target.scaleX) * target.scaleY;
             } else if (dist.bottom < threshold) {
                attrs.scaleY = (snap.bottom - target.top) / target.height;
                attrs.scaleX = (attrs.scaleY / target.scaleY) * target.scaleX;
             }
             break;
       }
       target.set(attrs);
    });
    

    Here is a working example :

    var canvas = new fabric.Canvas('c', {
       selection: false
    });
    var grid = 50;
    
    // create grid
    for (var i = 0; i < (600 / grid); i++) {
       canvas.add(new fabric.Line([i * grid, 0, i * grid, 600], {
          stroke: '#ccc',
          selectable: false
       }));
       canvas.add(new fabric.Line([0, i * grid, 600, i * grid], {
          stroke: '#ccc',
          selectable: false
       }))
    }
    
    // add objects
    canvas.add(new fabric.Rect({
       left: 100,
       top: 100,
       width: 50,
       height: 50,
       fill: '#faa',
       originX: 'left',
       originY: 'top',
       centeredRotation: true
    }));
    
    canvas.add(new fabric.Circle({
       left: 300,
       top: 300,
       radius: 50,
       fill: '#9f9',
       originX: 'left',
       originY: 'top',
       centeredRotation: true
    }));
    
    // snap to grid
    canvas.on('object:moving', options => {
       options.target.set({
          left: Math.round(options.target.left / grid) * grid,
          top: Math.round(options.target.top / grid) * grid
       });
    });
    
    canvas.on('object:scaling', options => {
       var target = options.target,
          w = target.width * target.scaleX,
          h = target.height * target.scaleY,
          snap = { // Closest snapping points
             top: Math.round(target.top / grid) * grid,
             left: Math.round(target.left / grid) * grid,
             bottom: Math.round((target.top + h) / grid) * grid,
             right: Math.round((target.left + w) / grid) * grid
          },
          threshold = grid,
          dist = { // Distance from snapping points
             top: Math.abs(snap.top - target.top),
             left: Math.abs(snap.left - target.left),
             bottom: Math.abs(snap.bottom - target.top - h),
             right: Math.abs(snap.right - target.left - w)
          },
          attrs = {
             scaleX: target.scaleX,
             scaleY: target.scaleY,
             top: target.top,
             left: target.left
          };
       switch (target.__corner) {
          case 'tl':
             if (dist.left < dist.top && dist.left < threshold) {
                attrs.scaleX = (w - (snap.left - target.left)) / target.width;
                attrs.scaleY = (attrs.scaleX / target.scaleX) * target.scaleY;
                attrs.top = target.top + (h - target.height * attrs.scaleY);
                attrs.left = snap.left;
             } else if (dist.top < threshold) {
                attrs.scaleY = (h - (snap.top - target.top)) / target.height;
                attrs.scaleX = (attrs.scaleY / target.scaleY) * target.scaleX;
                attrs.left = attrs.left + (w - target.width * attrs.scaleX);
                attrs.top = snap.top;
             }
             break;
          case 'mt':
             if (dist.top < threshold) {
                attrs.scaleY = (h - (snap.top - target.top)) / target.height;
                attrs.top = snap.top;
             }
             break;
          case 'tr':
             if (dist.right < dist.top && dist.right < threshold) {
                attrs.scaleX = (snap.right - target.left) / target.width;
                attrs.scaleY = (attrs.scaleX / target.scaleX) * target.scaleY;
                attrs.top = target.top + (h - target.height * attrs.scaleY);
             } else if (dist.top < threshold) {
                attrs.scaleY = (h - (snap.top - target.top)) / target.height;
                attrs.scaleX = (attrs.scaleY / target.scaleY) * target.scaleX;
                attrs.top = snap.top;
             }
             break;
          case 'ml':
             if (dist.left < threshold) {
                attrs.scaleX = (w - (snap.left - target.left)) / target.width;
                attrs.left = snap.left;
             }
             break;
          case 'mr':
             if (dist.right < threshold) attrs.scaleX = (snap.right - target.left) / target.width;
             break;
          case 'bl':
             if (dist.left < dist.bottom && dist.left < threshold) {
                attrs.scaleX = (w - (snap.left - target.left)) / target.width;
                attrs.scaleY = (attrs.scaleX / target.scaleX) * target.scaleY;
                attrs.left = snap.left;
             } else if (dist.bottom < threshold) {
                attrs.scaleY = (snap.bottom - target.top) / target.height;
                attrs.scaleX = (attrs.scaleY / target.scaleY) * target.scaleX;
                attrs.left = attrs.left + (w - target.width * attrs.scaleX);
             }
             break;
          case 'mb':
             if (dist.bottom < threshold) attrs.scaleY = (snap.bottom - target.top) / target.height;
             break;
          case 'br':
             if (dist.right < dist.bottom && dist.right < threshold) {
                attrs.scaleX = (snap.right - target.left) / target.width;
                attrs.scaleY = (attrs.scaleX / target.scaleX) * target.scaleY;
             } else if (dist.bottom < threshold) {
                attrs.scaleY = (snap.bottom - target.top) / target.height;
                attrs.scaleX = (attrs.scaleY / target.scaleY) * target.scaleX;
             }
             break;
       }
       target.set(attrs);
    });
    canvas {border: 1px solid #ccc}
    <script src="https://rawgithub.com/kangax/fabric.js/master/dist/fabric.js"></script>
    <canvas id="c" width="600" height="600"></canvas>