Fabricjs 在调整大小时捕捉到网格
Fabricjs snap to grid on resize
我在尝试让对象根据网格大小调整大小时遇到一些问题。
这是我的 fiddle:http://jsfiddle.net/csh6c6fw/1/
这是我正在应用的代码:
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 });
}
});
预期结果
它应该像运动一样捕捉到网格。
可能看起来很复杂,但是,以下将完成工作:
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);
});
这是一个工作示例:
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>
这是对已接受答案的更新。
target.getWidth()
和 target.getHeight()
似乎不再起作用,所以我从接受的答案中更新了 fiddle 以将它们替换为 target.width * target.scaleX
和 target.height * target.scaleY
.
上面的答案似乎不再有效。
调整对象大小时,织物似乎更新 scaleX/scaleY 而不是 width/height。
这是一个新的fiddle:http://jsfiddle.net/ej2hrqm8/
我禁用了 TL/TR/BL,因为我无法让那三个角工作。
// 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,
});
首先,上面的所有答案都不适用于旋转的对象,这对我来说很重要。我花了几天时间进行网格捕捉,最后我找到了一个效果很好的解决方案,至少它符合我的需要。我第一次用学校数学做计算:Math.tan + 勾股定理,但是太难了,我很确定我做错了什么。
首先找到并检查 wrapWithFixedAnchor
函数的工作原理。基本上,您可以更改目标的任何属性:宽度、高度、scaleX、scaleY,然后 wrapWithFixedAnchor
将帮助您将目标平移到锚点。
这是工作示例:http://jsfiddle.net/kod57cwb
我不认为将它称为 'snapping' 是个好主意,因为实际上它只会在对象零旋转时捕捉到网格。目前它的工作方式与 http://draw.io 捕捉
非常相似
请注意,fabric.js
通过将 strokeWidth
添加到 height
和 width
see 内部计算大小。这会阻止建议的解决方案正常工作:
- Etherman 的解决方案在调整
tl
和 bl
角的大小时发生漂移。
- GRUNT 的那个在调整大小时使右下角摆动(由于数字错误)。
我同时分享对我有用的东西(抱歉是在 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);
}
}
我在尝试让对象根据网格大小调整大小时遇到一些问题。
这是我的 fiddle:http://jsfiddle.net/csh6c6fw/1/
这是我正在应用的代码:
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 });
}
});
预期结果
它应该像运动一样捕捉到网格。
可能看起来很复杂,但是,以下将完成工作:
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);
});
这是一个工作示例:
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>
这是对已接受答案的更新。
target.getWidth()
和 target.getHeight()
似乎不再起作用,所以我从接受的答案中更新了 fiddle 以将它们替换为 target.width * target.scaleX
和 target.height * target.scaleY
.
上面的答案似乎不再有效。 调整对象大小时,织物似乎更新 scaleX/scaleY 而不是 width/height。
这是一个新的fiddle:http://jsfiddle.net/ej2hrqm8/
我禁用了 TL/TR/BL,因为我无法让那三个角工作。
// 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,
});
首先,上面的所有答案都不适用于旋转的对象,这对我来说很重要。我花了几天时间进行网格捕捉,最后我找到了一个效果很好的解决方案,至少它符合我的需要。我第一次用学校数学做计算:Math.tan + 勾股定理,但是太难了,我很确定我做错了什么。
首先找到并检查 wrapWithFixedAnchor
函数的工作原理。基本上,您可以更改目标的任何属性:宽度、高度、scaleX、scaleY,然后 wrapWithFixedAnchor
将帮助您将目标平移到锚点。
这是工作示例:http://jsfiddle.net/kod57cwb
我不认为将它称为 'snapping' 是个好主意,因为实际上它只会在对象零旋转时捕捉到网格。目前它的工作方式与 http://draw.io 捕捉
非常相似请注意,fabric.js
通过将 strokeWidth
添加到 height
和 width
see 内部计算大小。这会阻止建议的解决方案正常工作:
- Etherman 的解决方案在调整
tl
和bl
角的大小时发生漂移。 - GRUNT 的那个在调整大小时使右下角摆动(由于数字错误)。
我同时分享对我有用的东西(抱歉是在 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);
}
}