vue-fabric
Version:
fabric vue components
721 lines (604 loc) • 19.8 kB
JavaScript
/*
* fabric.js Controls Extension
* for fabric.js current build
* Simon Kunz 09.02.2016 for pixolith
* Licensed under the MIT license.
*/
'use strict';
(function (window) {
var fabric = window.fabric || (window.fabric = {}),
minExtCompat = '1.6.0',
isVML = function () {
return typeof G_vmlCanvasManager !== 'undefined';
},
degreesToRadians = fabric.util.degreesToRadians,
cursorOffset = {
mt: 0, // n
tr: 1, // ne
mr: 2, // e
br: 3, // se
mb: 4, // s
bl: 5, // sw
ml: 6, // w
tl: 7, // nw
};
if (minExtCompat.localeCompare(window.fabric.version) > -1) {
console.warn('this extension might not be fully compatible with your version ' +
'of fabric.js (' + window.fabric.version + ').' +
'Consider using the latest compatible build of fabric.js (> ' + minExtCompat + ')'
);
}
fabric.util.object.extend(fabric.Object.prototype, {
/**
* When true, image icons are loaded via the drawImage method
* @type Boolean
* @default false
*/
useCustomIcons: false,
/**
* Sets a background-color for drawImage operations with transparency
* @type string
* @default transparent
*/
cornerBackgroundColor: 'transparent',
/**
* Sets the shape of the background for drawImage operations with transparency
* @type string
* @default rect
*/
cornerShape: '',
/**
* Inner Padding between Shape Background and drawn Image
* @type int
* @default rect
*/
cornerPadding: 0,
/**
* Set a custom corner icon
* @param {Object} obj settings and icon url.
* @param callback function
*/
customiseCornerIcons: function (obj, callback) {
var setting,
cornerConfig;
for (setting in obj) {
if (obj.hasOwnProperty(setting)) {
cornerConfig = {};
if (obj[setting].cornerShape !== undefined) {
this.cornerShape = obj[setting].cornerShape;
}
if (obj[setting].cornerBackgroundColor !== undefined) {
this.cornerBackgroundColor = obj[setting].cornerBackgroundColor;
}
if (obj[setting].borderColor !== undefined) {
this.borderColor = obj[setting].borderColor;
}
if (obj[setting].cornerSize !== undefined) {
this.cornerSize = obj[setting].cornerSize;
}
if (obj[setting].cornerPadding !== undefined) {
this.cornerPadding = obj[setting].cornerPadding;
}
if (obj[setting].icon !== undefined || Object.keys(obj)[0] === 'settings') {
this.useCustomIcons = true;
if (obj[setting].settings !== undefined) {
cornerConfig.settings = obj[setting].settings;
}
if (obj[setting].icon !== undefined) {
cornerConfig.icon = obj[setting].icon;
this.loadIcon(setting, cornerConfig, function () {
if (callback && typeof (callback) === 'function') {
callback();
}
});
}
}
}
}
},
/**
* loads the icon image as an image src.
* @param {Object} corner to load an icon.
* @param cornerConfig as object containing icon url and corner specific settings
* @param callback function.
*/
loadIcon: function (corner, cornerConfig, callback) {
var self = this,
icon = new Image();
icon.onload = function () {
self[corner + 'Icon'] = this;
if (cornerConfig.settings) {
self[corner + 'Settings'] = cornerConfig.settings;
}
if (callback && typeof (callback) === 'function') {
callback();
}
};
icon.onerror = function () {
fabric.warn(this.src + ' icon is not an image');
};
if (cornerConfig.icon.match(/^http[s]?:\/\//) || cornerConfig.icon.substring(0, 2) === '//') {
icon.crossOrigin = 'Anonymous';
}
icon.src = cornerConfig.icon;
},
/**
* copy of the setter method for our american friends.
* @param {Object} obj containing corner icon urls and settings.
*/
customizeCornerIcons: function (obj) {
this.customiseCornerIcons(obj);
},
/**
* Draws corners of an object's bounding box.
* Requires public properties: width, height
* Requires public options: cornerSize, padding
* @param {CanvasRenderingContext2D} ctx Context to draw on
* @return {fabric.Object} thisArg
* @chainable
*/
drawControls: function (ctx) {
if (!this.hasControls) {
return this;
}
var wh = this._calculateCurrentDimensions(),
width = wh.x,
height = wh.y,
scaleOffset = this.cornerSize,
left = -(width + scaleOffset) / 2,
top = -(height + scaleOffset) / 2,
methodName;
if (!this.useCustomIcons) {
ctx.lineWidth = 1;
ctx.globalAlpha = this.isMoving ? this.borderOpacityWhenMoving : 1;
ctx.strokeStyle = ctx.fillStyle = this.cornerColor;
if (!this.transparentCorners) {
ctx.strokeStyle = this.cornerStrokeColor;
}
methodName = this.transparentCorners ? 'stroke' : 'fill';
} else {
methodName = 'drawImage';
}
ctx.save();
this._setLineDash(ctx, this.cornerDashArray, null);
// top-left
this._drawControl('tl', ctx, methodName,
left,
top,
this.tlIcon,
this.tlSettings
);
// top-right
this._drawControl('tr', ctx, methodName,
left + width,
top,
this.trIcon,
this.trSettings
);
// bottom-left
this._drawControl('bl', ctx, methodName,
left,
top + height,
this.blIcon,
this.blSettings
);
// bottom-right
this._drawControl('br', ctx, methodName,
left + width,
top + height,
this.brIcon,
this.brSettings
);
if (!this.get('lockUniScaling')) {
// middle-top
this._drawControl('mt', ctx, methodName,
left + width / 2,
top,
this.mtIcon,
this.mtSettings
);
// middle-bottom
this._drawControl('mb', ctx, methodName,
left + width / 2,
top + height,
this.mbIcon,
this.mbSettings
);
// middle-right
this._drawControl('mr', ctx, methodName,
left + width,
top + height / 2,
this.mrIcon,
this.mrSettings
);
// middle-left
this._drawControl('ml', ctx, methodName,
left,
top + height / 2,
this.mlIcon,
this.mlSettings
);
}
// middle-top-rotate
if (this.hasRotatingPoint) {
this._drawControl('mtr', ctx, methodName,
left + width / 2,
top - this.rotatingPointOffset,
this.mtrIcon,
this.mtrSettings
);
}
ctx.restore();
return this;
},
/** Draw controls either with background-shape and color (transparency) or plain image (modified core method)
* @private
* {string} icon url of the control
*/
_drawControl: function (control, ctx, methodName, left, top, icon, settings) {
if (!this.isControlVisible(control)) {
return;
}
var size = this.cornerSize,
cornerStroke = this.cornerStrokeColor || 'transparent',
cornerBG = this.cornerBackgroundColor || 'black',
cornerShape = this.cornerShape || 'rect',
cornerPadding = typeof this.cornerPadding === 'number' ? this.cornerPadding : 10;
if (settings) {
if (settings.cornerSize) {
// Set the size, and also recalc left and top
left = left + size / 2 - settings.cornerSize / 2;
top = top + size / 2 - settings.cornerSize / 2;
size = settings.cornerSize;
}
cornerShape = settings.cornerShape || cornerShape;
cornerBG = settings.cornerBackgroundColor || cornerBG;
cornerPadding = typeof settings.cornerPadding === 'number' ? settings.cornerPadding : cornerPadding;
cornerStroke = settings.cornerStrokeColor || cornerStroke;
}
//console.log(cornerBG);
if (this.useCustomIcons) {
if (cornerShape) {
ctx.globalAlpha = 1;
ctx.fillStyle = cornerBG;
ctx.lineWidth = 1;
ctx.strokeStyle = cornerStroke;
switch (cornerShape) {
case 'rect':
break;
ctx.fillRect(left, top, size, size);
if (cornerStroke) {
ctx.strokeRect(left, top, size, size);
}
break;
case 'circle':
break;
ctx.beginPath();
ctx.arc(left + size / 2, top + size / 2, size / 2, 0, 2 * Math.PI);
ctx.fill();
if (cornerStroke) {
ctx.stroke();
}
ctx.closePath();
break;
}
if (icon !== undefined) {
ctx[methodName](
icon,
left + cornerPadding / 2,
top + cornerPadding / 2,
size - cornerPadding,
size - cornerPadding
);
}
} else {
if (icon !== undefined) {
ctx[methodName](
icon,
left,
top,
size,
size
);
}
}
} else {
isVML() || this.transparentCorners || ctx.clearRect(left, top, size, size);
ctx[methodName + 'Rect'](left, top, size, size);
if (!this.transparentCorners && cornerStroke) {
ctx.strokeRect(left, top, size, size);
}
}
},
});
fabric.util.object.extend(fabric.Canvas.prototype, {
/**
* When true, actions can be overwritten
* @type Boolean
* @default false
*/
overwriteActions: false,
/**
* When true, cursors are fixed
* @type Boolean
* @default false
*/
fixedCursors: false,
/**
* setter Method for actions and cursors.
* @param {Object} obj containing corner action and cursor url/type.
*/
customiseControls: function (obj) {
var setting;
for (setting in obj) {
if (obj.hasOwnProperty(setting)) {
if (obj[setting].action !== undefined) {
this.overwriteActions = true;
this.setCustomAction(setting, obj[setting].action);
}
if (obj[setting].cursor !== undefined) {
this.fixedCursors = true;
this.setCustomCursor(setting, obj[setting].cursor);
}
}
}
},
/**
* loads the icon image as an image src.
* @param {Object} corner to load an icon.
* @param action as a string.
*/
setCustomAction: function (corner, action) {
this[corner + 'Action'] = action;
},
/**
* loads the icon image as an image src.
* @param {Object} corner to load an icon.
* @param cursorUrl as a string.
*/
setCustomCursor: function (corner, cursorUrl) {
this[corner + 'cursorIcon'] = cursorUrl;
},
/**
* copy of the setter method for our american friends.
* @param {Object} obj containing corner action and cursor url/type.
*/
customizeControls: function (obj) {
this.customiseControls(obj);
},
/**
* @private
*/
_getActionFromCorner: function (target, corner, e) {
if (!corner) {
return 'drag';
}
if (corner) {
if (this[corner + 'Action'] && this.overwriteActions) {
switch (corner) {
case 'mtr':
return this[corner + 'Action'] || 'rotate';
case 'ml':
case 'mr':
if (e[this.altActionKey]) {
return e[this.altActionKey] ? 'skewY' : 'scaleX';
}
return this[corner + 'Action'];
case 'mt':
case 'mb':
if (e[this.altActionKey]) {
return e[this.altActionKey] ? 'skewY' : 'scaleY';
}
return this[corner + 'Action'];
default:
return this[corner + 'Action'] || 'scale';
}
} else {
switch (corner) {
case 'mtr':
return 'rotate';
case 'ml':
case 'mr':
return e[this.altActionKey] ? 'skewY' : 'scaleX';
case 'mt':
case 'mb':
return e[this.altActionKey] ? 'skewX' : 'scaleY';
default:
return 'scale';
}
}
}
return false;
},
/**
* @private
* @param {Event} e Event object
* @param {fabric.Object} target
*/
_setupCurrentTransform: function (e, target) {
if (!target) {
return;
}
var pointer = this.getPointer(e),
corner = target._findTargetCorner(this.getPointer(e, true)),
action = this._getActionFromCorner(target, corner, e),
origin = this._getOriginFromCorner(target, corner);
if (typeof action === 'function') {
action.call(this, e, target);
// as of fabric 1.7.11 object cache will try to slice the action to check for scale so we need to convert this to a string
action = 'void';
}
this._currentTransform = {
target: target,
action: action,
corner: corner,
scaleX: target.scaleX,
scaleY: target.scaleY,
skewX: target.skewX,
skewY: target.skewY,
offsetX: pointer.x - target.left,
offsetY: pointer.y - target.top,
originX: origin.x,
originY: origin.y,
ex: pointer.x,
ey: pointer.y,
lastX: pointer.x,
lastY: pointer.y,
left: target.left,
top: target.top,
theta: degreesToRadians(target.angle),
width: target.width * target.scaleX,
mouseXSign: 1,
mouseYSign: 1,
shiftKey: e.shiftKey,
altKey: e[this.centeredKey],
};
this._currentTransform.original = {
left: target.left,
top: target.top,
scaleX: target.scaleX,
scaleY: target.scaleY,
skewX: target.skewX,
skewY: target.skewY,
originX: origin.x,
originY: origin.y,
};
if (action === 'remove') {
this._removeAction(e, target);
}
if (action === 'moveUp') {
this._moveLayerUpAction(e, target);
}
if (action === 'moveDown') {
this._moveLayerDownAction(e, target);
}
if (typeof action === 'object') {
if (Object.keys(action)[0] === 'rotateByDegrees') {
this._rotateByDegrees(e, target, action.rotateByDegrees);
}
}
this._resetCurrentTransform();
},
/**
* Custom remove object action
* @private
* @param {Event} e Event object
* @param {fabric.Object} target
*/
_removeAction: function (e, target) {
var _this = this;
if (this.getActiveGroup() && this.getActiveGroup() !== 'undefined') {
this.getActiveGroup().forEachObject(function (o) {
o.off();
o.remove();
});
this.discardActiveGroup();
// as of fabric 1.6.3 necessary for reasons..
setTimeout(function () {
_this.deactivateAll();
}, 0);
} else {
target.off();
target.remove();
setTimeout(function () {
_this.deactivateAll();
}, 0);
}
},
/**
* Custom move up object action
* @private
* @param {Event} e Event object
* @param {fabric.Object} target
*/
_moveLayerUpAction: function (e, target) {
if (this.getActiveGroup() && this.getActiveGroup() !== 'undefined') {
this.getActiveGroup().forEachObject(function (o) {
o.bringForward();
});
} else {
target.bringForward();
}
},
/**
* Custom move down object action
* @private
* @param {Event} e Event object
* @param {fabric.Object} target
*/
_moveLayerDownAction: function (e, target) {
if (this.getActiveGroup() && this.getActiveGroup() !== 'undefined') {
this.getActiveGroup().forEachObject(function (o) {
o.sendBackwards();
});
} else {
target.sendBackwards();
}
},
/**
* Custom move down object action
* @private
* @param {Event} e Event object
* @param {fabric.Object} target
* @param {Integer} value of rotation
*/
_rotateByDegrees: function (e, target, value) {
var angle = parseInt(target.getAngle()) + value,
needsOriginRestore = false;
if ((target.originX !== 'center' || target.originY !== 'center') && target.centeredRotation) {
this._setOriginToCenter(target);
needsOriginRestore = true;
}
angle = angle > 360 ? angle - 360 : angle;
if (this.getActiveGroup() && this.getActiveGroup() !== 'undefined') {
this.getActiveGroup().forEachObject(function (obj) {
obj
.setAngle(angle)
.setCoords();
});
} else {
target
.setAngle(angle)
.setCoords();
}
if (needsOriginRestore) {
this._setCenterToOrigin(target);
}
this.renderAll();
},
/**
* Sets either the standard behaviour cursors or if fixedCursors is true, tries to set a custom cursor
* either by using an icon or a build-in cursor. Cursor icon extensions are matched with a regular expression.
* @private
* {string} corner name
* {target} event handler of the hovered corner
*/
_setCornerCursor: function (corner, target, e) {
var iconUrlPattern = /\.(?:jpe?g|png|gif|jpg|jpeg|svg)$/;
if (this.fixedCursors && this[corner + 'cursorIcon']) {
if (this[corner + 'cursorIcon'].match(iconUrlPattern)) {
this.setCursor('url(' + this[corner + 'cursorIcon'] + '), auto');
} else {
if (this[corner + 'cursorIcon'] === 'resize') {
this.setCursor(this._getRotatedCornerCursor(corner, target, e));
} else {
this.setCursor(this[corner + 'cursorIcon']);
}
}
} else {
if (corner in cursorOffset) {
this.setCursor(this._getRotatedCornerCursor(corner, target, e));
} else if (corner === 'mtr' && target.hasRotatingPoint) {
this.setCursor(this.rotationCursor);
} else {
this.setCursor(this.defaultCursor);
return false;
}
}
return false;
},
});
if (typeof exports !== 'undefined') {
module.exports = this;
}
})(window);