最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

javascript - Fabricjs snap to grid on resize - Stack Overflow

programmeradmin0浏览0评论

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

Here's my fiddle: /

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.

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.

Share Improve this question asked May 24, 2017 at 2:04 A. LA. L 12.6k29 gold badges98 silver badges178 bronze badges
Add a comment  | 

5 Answers 5

Reset to default 10

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>

First of all neither of all answers from above worked with rotated objects and it was a big deal for me. I spent few day on a grid snapping and finally I found a solution that works well, at least it fits my needs. On the first time I did calculations with school math: Math.tan + Pythagorean theorem, but it was too hard and I was pretty sure that I'm doing something wrong.

First of all find and check how wrapWithFixedAnchor function works. Basically you can change any attribute of your target: width, height, scaleX, scaleY and then wrapWithFixedAnchor will help you to translate your target to the anchor point.

Here is the working example: http://jsfiddle.net/kod57cwb

I don't think that calling it as a 'snapping' is a good idea, because in reality it would snap to a grid only when objects are zero rotated. Currently it works pretty similar to http://draw.io snapping

This is an update to the accepted answer.

target.getWidth() and target.getHeight() don't seem to work anymore, so I updated the fiddle from the accepted answer to replace them with target.width * target.scaleX and target.height * target.scaleY.

Here is the updated fiddle

The answers above don't seem to work anymore. When the object is resized fabric seems to update the scaleX/scaleY instead of the width/height.

Here is a new fiddle: http://jsfiddle.net/ej2hrqm8/

I disabled TL/TR/BL because I cannot get those three corners to work.

// Build using FabricJS v3.4

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

// create grid

for (var i = 0; i < (600 / gridSize); i++) {
  canvas.add(new fabric.Line([ i * gridSize, 0, i * gridSize, 600], { stroke: '#ccc', selectable: false }));
  canvas.add(new fabric.Line([ 0, i * gridSize, 600, i * gridSize], { 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
}));

function Snap(value)
{
  return Math.round(value / snapSize) * snapSize;
}

function SnapMoving(options)
{
  options.target.set({
    left: Snap(options.target.left),
    top: Snap(options.target.top)
  });
}

function SnapScaling(options)
{
  var target = options.target;
  var pointer = options.pointer;

  var px = Snap(pointer.x);
  var py = Snap(pointer.y);
  var rx = (px - target.left) / target.width;
  var by = (py - target.top) / target.height;
  var lx = (target.left - px + (target.width * target.scaleX)) / (target.width);
  var ty = (target.top - py + (target.height * target.scaleY)) / (target.height);

  var a = {};

  // Cannot get snap to work on some corners :-(
  switch (target.__corner)
  {
    case "tl":
      // Not working
      //a = { scaleX: lx, scaleY: ty, left: px, top: py };
      break;
    case "mt":
      a = { scaleY: ty, top: py };
      break;
    case "tr":
      // Not working
      //a = { scaleX: rx, scaleY: ty, top: py  };
      break;
    case "ml":
      a = { scaleX: lx, left: px };
      break;
    case "mr":
      a = { scaleX: rx };
      break;
    case "bl":
      // Not working
      //a = { scaleX: lx, scaleY: by, left: px };
      break;
    case "mb":
      a = { scaleY: by };
      break;
    case "br":
      a = { scaleX: rx, scaleY: by };
      break;
  }

  options.target.set(a);
}

canvas.on({
  "object:moving": SnapMoving,
  "object:scaling": SnapScaling,
});

Please note that fabric.js internally computes the size by adding strokeWidth to height and width see. This prevents both the proposed solutions from working properly:

  • Etherman's solutions drift when resizing tl and bl corners.
  • GRUNT's one makes the lower right corner wobble while resizing (due to numeric error).

I while share what has worked for me (sorry is in TypeScript)


import { fabric } from 'fabric';

export class GridSnapFabric extends fabric.Canvas {

  protected gridGranularity = 20;

  constructor(canvas: HTMLCanvasElement) {
    super(canvas);
    this.on('object:scaling', this.onFabricObjectScaling.bind(this));
  }

  private snapGrid(cord: number): number {
    return Math.round(cord / this.gridGranularity) * this.gridGranularity;
  }

  private onFabricObjectScaling(e: fabric.IEvent) {
    const active = this.getActiveObject();
    const [width, height] = [active.getScaledWidth(), active.getScaledHeight()];

    // X
    if (['tl', 'ml', 'bl'].indexOf(e.transform.corner) !== -1) {
      const tl = this.snapGrid(active.left);
      active.scaleX = (width + active.left - tl) / (active.width + active.strokeWidth);
      active.left = tl;
    } else if (['tr', 'mr', 'br'].indexOf(e.transform.corner) !== -1) {
      const tl = this.snapGrid(active.left + width);
      active.scaleX = (tl - active.left) / (active.width + active.strokeWidth);
    }

    // Y
    if (['tl', 'mt', 'tr'].indexOf(e.transform.corner) !== -1) {
      const tt = this.snapGrid(active.top);
      active.scaleY = (height + active.top - tt) / (active.height + active.strokeWidth);
      active.top = tt;
    } else if (['bl', 'mb', 'br'].indexOf(e.transform.corner) !== -1) {
      const tt = this.snapGrid(active.top + height);
      active.scaleY = (tt - active.top) / (active.height + active.strokeWidth);
    }

    // Avoid singularities
    active.scaleX = (active.scaleY >= 0 ? 1 : -1) * Math.max(Math.abs(active.scaleX), 0.001);
    active.scaleY = (active.scaleY >= 0 ? 1 : -1) * Math.max(Math.abs(active.scaleY), 0.001);
  }

}
发布评论

评论列表(0)

  1. 暂无评论