mirror of
https://github.com/Hopiu/fabric.js.git
synced 2026-05-09 22:34:43 +00:00
Reorganize fabric.Element in such way so that centering and aligning guidelines could work together.
`fabric.Element#onObjectMove` callback is gone, replaced by "object:moved" event (which allows subscription to the event by multiple parties). Similarly, `fabric.Element#onMouseUp` is replaced with "mouse:up" event, and `fabric.Element#afterRender` with "after:render" one. The drawback of these events, as of now, is that it's not possible to determine which canvas instance fired which event -- in case of multiple canvas instances in a document, this could get hairy. Will probably fix it by introducing some kind of `Observable` mixin, which would add "observe" and "fire" methods to `fabric.Element` itself.
This commit is contained in:
parent
33467d15a2
commit
33278ae20b
5 changed files with 144 additions and 102 deletions
36
dist/all.js
vendored
36
dist/all.js
vendored
|
|
@ -3574,10 +3574,6 @@ fabric.util.animate = animate;
|
|||
this.setOverlayImage(options.overlayImage);
|
||||
}
|
||||
|
||||
if (options.afterRender) {
|
||||
this.afterRender = options.afterRender;
|
||||
}
|
||||
|
||||
this._createCanvasBackground();
|
||||
this._createCanvasContainer();
|
||||
this._initEvents();
|
||||
|
|
@ -3671,19 +3667,6 @@ fabric.util.animate = animate;
|
|||
/* NOOP */
|
||||
},
|
||||
|
||||
/**
|
||||
* Callback; invoked every time active object is moved
|
||||
* @method onObjectMove
|
||||
* @param {fabric.Object} object that's being moved
|
||||
*/
|
||||
onObjectMove: null,
|
||||
|
||||
/**
|
||||
* Callback; invoked when a mouseup event occurs, and at the end of all other transformations
|
||||
* @method onMouseUp
|
||||
*/
|
||||
onMouseUp: null,
|
||||
|
||||
/**
|
||||
* Calculates canvas element offset relative to the document
|
||||
* This method is also attached as "resize" event handler of window
|
||||
|
|
@ -4019,9 +4002,7 @@ fabric.util.animate = animate;
|
|||
_this._setCursorFromEvent(e, target);
|
||||
}, 50);
|
||||
|
||||
if (this.onMouseUp) {
|
||||
this.onMouseUp();
|
||||
}
|
||||
fireEvent('mouse:up');
|
||||
},
|
||||
|
||||
_shouldClearSelection: function (e) {
|
||||
|
|
@ -4349,9 +4330,10 @@ fabric.util.animate = animate;
|
|||
}
|
||||
else {
|
||||
this._translateObject(x, y);
|
||||
if (this.onObjectMove) {
|
||||
this.onObjectMove(this._currentTransform.target);
|
||||
}
|
||||
|
||||
fireEvent('object:moved', {
|
||||
target: this._currentTransform.target
|
||||
});
|
||||
}
|
||||
this.renderAll();
|
||||
}
|
||||
|
|
@ -4659,9 +4641,7 @@ fabric.util.animate = animate;
|
|||
var elapsedTime = new Date() - startTime;
|
||||
this.onFpsUpdate(~~(1000 / elapsedTime));
|
||||
|
||||
if (this.afterRender) {
|
||||
this.afterRender();
|
||||
}
|
||||
fireEvent('after:render');
|
||||
|
||||
return this;
|
||||
},
|
||||
|
|
@ -4689,9 +4669,7 @@ fabric.util.animate = animate;
|
|||
activeGroup.render(this.contextTop);
|
||||
}
|
||||
|
||||
if (this.afterRender) {
|
||||
this.afterRender();
|
||||
}
|
||||
fireEvent('after:render');
|
||||
|
||||
return this;
|
||||
},
|
||||
|
|
|
|||
|
|
@ -126,10 +126,6 @@
|
|||
this.setOverlayImage(options.overlayImage);
|
||||
}
|
||||
|
||||
if (options.afterRender) {
|
||||
this.afterRender = options.afterRender;
|
||||
}
|
||||
|
||||
this._createCanvasBackground();
|
||||
this._createCanvasContainer();
|
||||
this._initEvents();
|
||||
|
|
@ -223,19 +219,6 @@
|
|||
/* NOOP */
|
||||
},
|
||||
|
||||
/**
|
||||
* Callback; invoked every time active object is moved
|
||||
* @method onObjectMove
|
||||
* @param {fabric.Object} object that's being moved
|
||||
*/
|
||||
onObjectMove: null,
|
||||
|
||||
/**
|
||||
* Callback; invoked when a mouseup event occurs, and at the end of all other transformations
|
||||
* @method onMouseUp
|
||||
*/
|
||||
onMouseUp: null,
|
||||
|
||||
/**
|
||||
* Calculates canvas element offset relative to the document
|
||||
* This method is also attached as "resize" event handler of window
|
||||
|
|
@ -579,9 +562,7 @@
|
|||
_this._setCursorFromEvent(e, target);
|
||||
}, 50);
|
||||
|
||||
if (this.onMouseUp) {
|
||||
this.onMouseUp();
|
||||
}
|
||||
fireEvent('mouse:up');
|
||||
},
|
||||
|
||||
_shouldClearSelection: function (e) {
|
||||
|
|
@ -935,9 +916,10 @@
|
|||
}
|
||||
else {
|
||||
this._translateObject(x, y);
|
||||
if (this.onObjectMove) {
|
||||
this.onObjectMove(this._currentTransform.target);
|
||||
}
|
||||
|
||||
fireEvent('object:moved', {
|
||||
target: this._currentTransform.target
|
||||
});
|
||||
}
|
||||
// only commit here. when we are actually moving the pictures
|
||||
this.renderAll();
|
||||
|
|
@ -1253,9 +1235,7 @@
|
|||
var elapsedTime = new Date() - startTime;
|
||||
this.onFpsUpdate(~~(1000 / elapsedTime));
|
||||
|
||||
if (this.afterRender) {
|
||||
this.afterRender();
|
||||
}
|
||||
fireEvent('after:render');
|
||||
|
||||
return this;
|
||||
},
|
||||
|
|
@ -1286,9 +1266,7 @@
|
|||
activeGroup.render(this.contextTop);
|
||||
}
|
||||
|
||||
if (this.afterRender) {
|
||||
this.afterRender();
|
||||
}
|
||||
fireEvent('after:render');
|
||||
|
||||
return this;
|
||||
},
|
||||
|
|
|
|||
|
|
@ -1,3 +1,8 @@
|
|||
/**
|
||||
* Should objects by aligned by a bounding box?
|
||||
* [Bug] Scaled objects sometimes can not be aligned by edges
|
||||
*
|
||||
*/
|
||||
function initAligningGuidelines(canvas) {
|
||||
|
||||
var ctx = canvas.getContext(),
|
||||
|
|
@ -35,6 +40,8 @@ function initAligningGuidelines(canvas) {
|
|||
}
|
||||
|
||||
function isInRange(value1, value2) {
|
||||
value1 = Math.round(value1);
|
||||
value2 = Math.round(value2);
|
||||
for (var i = value1 - aligningLineMargin, len = value1 + aligningLineMargin; i <= len; i++) {
|
||||
if (i === value2) {
|
||||
return true;
|
||||
|
|
@ -43,15 +50,21 @@ function initAligningGuidelines(canvas) {
|
|||
return false;
|
||||
}
|
||||
|
||||
canvas.onObjectMove = function(activeObject) {
|
||||
var verticalLines = [ ],
|
||||
horizontalLines = [ ];
|
||||
|
||||
fabric.util.observeEvent('object:moved', function(e) {
|
||||
|
||||
var canvasObjects = canvas.getObjects(),
|
||||
var activeObject = e.memo.target,
|
||||
canvasObjects = canvas.getObjects(),
|
||||
activeObjectLeft = activeObject.get('left'),
|
||||
activeObjectTop = activeObject.get('top'),
|
||||
activeObjectHeight = activeObject.get('height'),
|
||||
activeObjectWidth = activeObject.get('width'),
|
||||
verticalLines = [ ],
|
||||
horizontalLines = [ ];
|
||||
activeObjectHeight = activeObject.getHeight(),
|
||||
activeObjectWidth = activeObject.getWidth(),
|
||||
noneInTheRange = true;
|
||||
|
||||
// It should be trivial to DRY this up by encapsulating (repeating) creation of x1, x2, y1, and y2 into functions,
|
||||
// but we're not doing it here for perf. reasons -- as this a function that's invoked on every mouse move
|
||||
|
||||
for (var i = canvasObjects.length; i--; ) {
|
||||
|
||||
|
|
@ -62,8 +75,10 @@ function initAligningGuidelines(canvas) {
|
|||
objectHeight = canvasObjects[i].getHeight(),
|
||||
objectWidth = canvasObjects[i].getWidth();
|
||||
|
||||
// snap by the horizontal center line
|
||||
if (isInRange(objectLeft, activeObjectLeft)) {
|
||||
verticalLines.push({
|
||||
noneInTheRange = false;
|
||||
verticalLines.push({
|
||||
x: objectLeft,
|
||||
y1: (objectTop < activeObjectTop)
|
||||
? (objectTop - objectHeight / 2 - aligningLineOffset)
|
||||
|
|
@ -75,7 +90,39 @@ function initAligningGuidelines(canvas) {
|
|||
activeObject.set('left', objectLeft);
|
||||
}
|
||||
|
||||
// snap by the left edge
|
||||
if (isInRange(objectLeft - objectWidth / 2, activeObjectLeft - activeObjectWidth / 2)) {
|
||||
noneInTheRange = false;
|
||||
verticalLines.push({
|
||||
x: objectLeft - objectWidth / 2,
|
||||
y1: (objectTop < activeObjectTop)
|
||||
? (objectTop - objectHeight / 2 - aligningLineOffset)
|
||||
: (objectTop + objectHeight / 2 + aligningLineOffset),
|
||||
y2: (activeObjectTop > objectTop)
|
||||
? (activeObjectTop + activeObjectHeight / 2 + aligningLineOffset)
|
||||
: (activeObjectTop - activeObjectHeight / 2 - aligningLineOffset)
|
||||
});
|
||||
activeObject.set('left', objectLeft - objectWidth / 2 + activeObjectWidth / 2);
|
||||
}
|
||||
|
||||
// snap by the right edge
|
||||
if (isInRange(objectLeft + objectWidth / 2, activeObjectLeft + activeObjectWidth / 2)) {
|
||||
noneInTheRange = false;
|
||||
verticalLines.push({
|
||||
x: objectLeft + objectWidth / 2,
|
||||
y1: (objectTop < activeObjectTop)
|
||||
? (objectTop - objectHeight / 2 - aligningLineOffset)
|
||||
: (objectTop + objectHeight / 2 + aligningLineOffset),
|
||||
y2: (activeObjectTop > objectTop)
|
||||
? (activeObjectTop + activeObjectHeight / 2 + aligningLineOffset)
|
||||
: (activeObjectTop - activeObjectHeight / 2 - aligningLineOffset)
|
||||
});
|
||||
activeObject.set('left', objectLeft + objectWidth / 2 - activeObjectWidth / 2);
|
||||
}
|
||||
|
||||
// snap by the vertical center line
|
||||
if (isInRange(objectTop, activeObjectTop)) {
|
||||
noneInTheRange = false;
|
||||
horizontalLines.push({
|
||||
y: objectTop,
|
||||
x1: (objectLeft < activeObjectLeft)
|
||||
|
|
@ -87,17 +134,54 @@ function initAligningGuidelines(canvas) {
|
|||
});
|
||||
activeObject.set('top', objectTop);
|
||||
}
|
||||
|
||||
// snap by the top edge
|
||||
if (isInRange(objectTop - objectHeight / 2, activeObjectTop - activeObjectHeight / 2)) {
|
||||
noneInTheRange = false;
|
||||
horizontalLines.push({
|
||||
y: objectTop - objectHeight / 2,
|
||||
x1: (objectLeft < activeObjectLeft)
|
||||
? (objectLeft - objectWidth / 2 - aligningLineOffset)
|
||||
: (objectLeft + objectWidth / 2 + aligningLineOffset),
|
||||
x2: (activeObjectLeft > objectLeft)
|
||||
? (activeObjectLeft + activeObjectWidth / 2 + aligningLineOffset)
|
||||
: (activeObjectLeft - activeObjectWidth / 2 - aligningLineOffset)
|
||||
});
|
||||
activeObject.set('top', objectTop - objectHeight / 2 + activeObjectHeight / 2);
|
||||
}
|
||||
|
||||
// snap by the bottom edge
|
||||
if (isInRange(objectTop + objectHeight / 2, activeObjectTop + activeObjectHeight / 2)) {
|
||||
noneInTheRange = false;
|
||||
horizontalLines.push({
|
||||
y: objectTop + objectHeight / 2,
|
||||
x1: (objectLeft < activeObjectLeft)
|
||||
? (objectLeft - objectWidth / 2 - aligningLineOffset)
|
||||
: (objectLeft + objectWidth / 2 + aligningLineOffset),
|
||||
x2: (activeObjectLeft > objectLeft)
|
||||
? (activeObjectLeft + activeObjectWidth / 2 + aligningLineOffset)
|
||||
: (activeObjectLeft - activeObjectWidth / 2 - aligningLineOffset)
|
||||
});
|
||||
activeObject.set('top', objectTop + objectHeight / 2 - activeObjectHeight / 2);
|
||||
}
|
||||
}
|
||||
|
||||
canvas.afterRender = function() {
|
||||
for (var i = verticalLines.length; i--; ) {
|
||||
drawVerticalLine(verticalLines[i]);
|
||||
}
|
||||
for (var i = horizontalLines.length; i--; ) {
|
||||
drawHorizontalLine(horizontalLines[i]);
|
||||
}
|
||||
};
|
||||
};
|
||||
if (noneInTheRange) {
|
||||
verticalLines.length = horizontalLines.length = 0;
|
||||
}
|
||||
});
|
||||
|
||||
fabric.util.observeEvent('after:render', function() {
|
||||
for (var i = verticalLines.length; i--; ) {
|
||||
drawVerticalLine(verticalLines[i]);
|
||||
}
|
||||
for (var i = horizontalLines.length; i--; ) {
|
||||
drawHorizontalLine(horizontalLines[i]);
|
||||
}
|
||||
});
|
||||
|
||||
fabric.util.observeEvent('mouse:up', function() {
|
||||
verticalLines.length = horizontalLines.length = 0;
|
||||
canvas.renderAll();
|
||||
});
|
||||
}
|
||||
|
|
@ -43,32 +43,37 @@ function initCenteringGuidelines(canvas) {
|
|||
ctx.restore();
|
||||
}
|
||||
|
||||
canvas.onObjectMove = function(object) {
|
||||
var isInVerticalCenter = object.get('left') in canvasWidthCenterMap,
|
||||
isInHorizontalCenter = object.get('top') in canvasHeightCenterMap;
|
||||
var observeEvent = fabric.util.observeEvent,
|
||||
afterRenderActions = [ ],
|
||||
isInVerticalCenter,
|
||||
isInHorizontalCenter;
|
||||
|
||||
observeEvent('object:moved', function(e) {
|
||||
object = e.memo.target;
|
||||
|
||||
if (isInVerticalCenter || isInHorizontalCenter) {
|
||||
canvas.afterRender = function() {
|
||||
if (isInHorizontalCenter) {
|
||||
showHorizontalCenterLine();
|
||||
}
|
||||
if (isInVerticalCenter) {
|
||||
showVerticalCenterLine();
|
||||
}
|
||||
};
|
||||
if (isInHorizontalCenter) {
|
||||
object.set('top', canvasHeightCenter);
|
||||
}
|
||||
if (isInVerticalCenter) {
|
||||
object.set('left', canvasWidthCenter);
|
||||
}
|
||||
isInVerticalCenter = object.get('left') in canvasWidthCenterMap,
|
||||
isInHorizontalCenter = object.get('top') in canvasHeightCenterMap;
|
||||
|
||||
if (isInHorizontalCenter) {
|
||||
object.set('top', canvasHeightCenter);
|
||||
}
|
||||
else {
|
||||
canvas.afterRender = null;
|
||||
if (isInVerticalCenter) {
|
||||
object.set('left', canvasWidthCenter);
|
||||
}
|
||||
};
|
||||
canvas.onMouseUp = function() {
|
||||
canvas.afterRender = null;
|
||||
});
|
||||
|
||||
observeEvent('after:render', function() {
|
||||
if (isInVerticalCenter) {
|
||||
showVerticalCenterLine();
|
||||
}
|
||||
if (isInHorizontalCenter) {
|
||||
showHorizontalCenterLine();
|
||||
}
|
||||
});
|
||||
|
||||
observeEvent('mouse:up', function() {
|
||||
// clear these values, to stop drawing guidelines once mouse is up
|
||||
isInVerticalCenter = isInHorizontalCenter = null;
|
||||
canvas.renderAll();
|
||||
};
|
||||
});
|
||||
}
|
||||
|
|
@ -332,9 +332,6 @@
|
|||
}, 100);
|
||||
|
||||
initCenteringGuidelines(canvas);
|
||||
|
||||
if (document.location.search.indexOf('align') > -1) {
|
||||
initAligningGuidelines(canvas);
|
||||
}
|
||||
initAligningGuidelines(canvas);
|
||||
|
||||
})(this);
|
||||
Loading…
Reference in a new issue