how to render an object reappeared on the canvas - fabricjs

Initially I instantiated a Rect object, by controlling the object's top and left values, making it beyond the canvas area, so that the Rect object will not be rendered on the canvas. After that, change the top and left values of the Rect to make it in the area of the canvas by the event handler and then how to render the Rect object on the canvas.
the following code is a demo:
<canvas id="canvas" width="800" height="600"></canvas>
<script src="js/fabric.js"></script>
<script>
(function () {
var canvas = this.__canvas = new fabric.Canvas('canvas');
fabric.Object.prototype.transparentCorners = false;
var targetLine = [], paramsG, paramsR;
for (var k = 0; k < 20; k++) {
paramsG = {
left: 200,
top: 530 - 100 * k,
width: 20,
height: 50,
visibile: false,
fill: '#62ab59',
hasBorders: false,
lockMovementX: true,
hasControls: false
};
paramsR = {
left: 200,
top: 580 - 100 * k,
width: 20,
height: 50,
visibile: false,
fill: '#ed5d5d',
hasBorders: false,
lockMovementX: true,
hasControls: false
};
canvas.add(new fabric.Rect(paramsG), new fabric.Rect(paramsR));
}
canvas.on('mouse:down', function (e) {
if (e.target) {
targetLine = getMemberByLeft(canvas._objects, e.target);
}
})
canvas.on('object:moving', function (e) {
targetLine.forEach(function (val) {
canvas._objects[val.index].set({top: e.e.movementY + canvas._objects[val.index].top});
})
canvas.renderAll();
})
function getMemberByLeft(arr, tar) {
var returnArr = [];
arr.forEach(function (value, key) {
if (value.left == tar.left && value != tar) {
returnArr.push({data: value, index: key});
}
})
return returnArr;
}
})();
</script>

Fabric has a function to skip object rendering if they are not visible on screen, to get some more speed.
If you change top and left by code, fabric will not understand that the object is again on screen unless you call object.setCoords()
If you do not want to have this behaviour automatic you can disable it using
canvas.skipOffscreen = false;

Related

Fabricjs - selection only via border

I'm using Fabric.js to draw some rectangles on a canvas. The default behavior is that clicking inside a rectangle selects it. How can I change the behavior such that it is only selected when clicking on the border of the rectangle?
Clicking inside the rectangle but not on the border should do nothing.
You can see this behavior by drawing a rectangle on a TradingView.com chart
It there an option for this in fabric, and if not how could I go around implementing it?
This approach overrides the _checkTarget method within FabricJS to reject clicks that are more than a specified distance from the border (defined by the clickableMargin variable).
//sets the width of clickable area
var clickableMargin = 15;
var canvas = new fabric.Canvas("canvas");
canvas.add(new fabric.Rect({
width: 150,
height: 150,
left: 25,
top: 25,
fill: 'green',
strokeWidth: 0
}));
//overrides the _checkTarget method to add check if point is close to the border
fabric.Canvas.prototype._checkTarget = function(pointer, obj, globalPointer) {
if (obj &&
obj.visible &&
obj.evented &&
this.containsPoint(null, obj, pointer)){
if ((this.perPixelTargetFind || obj.perPixelTargetFind) && !obj.isEditing) {
var isTransparent = this.isTargetTransparent(obj, globalPointer.x, globalPointer.y);
if (!isTransparent) {
return true;
}
}
else {
var isInsideBorder = this.isInsideBorder(obj);
if(!isInsideBorder) {
return true;
}
}
}
}
fabric.Canvas.prototype.isInsideBorder = function(target) {
var pointerCoords = target.getLocalPointer();
if(pointerCoords.x > clickableMargin &&
pointerCoords.x < target.getScaledWidth() - clickableMargin &&
pointerCoords.y > clickableMargin &&
pointerCoords.y < target.getScaledHeight() - clickableMargin) {
return true;
}
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/3.6.2/fabric.min.js"></script>
<canvas id="canvas" height="300" width="400"></canvas>
Fabric.js uses Object.containsPoint() to determine whether a mouse event should target the object. This method, in turn, calculates the object's edges via Object._getImageLines() and checks how many times the projection of a mouse pointer crossed those lines.
The solution below calculates additional inner edges based on the coordinates of each corner, therefore object scale and rotation are taken care of automatically.
const canvas = new fabric.Canvas('c', {
enableRetinaScaling: true
})
const rect = new fabric.Rect({
left: 0,
top: 0,
width: 100,
height: 100,
dragBorderWidth: 15, // this is the custom attribute we've introduced
})
function innerCornerPoint(start, end, offset) {
// vector length
const l = start.distanceFrom(end)
// unit vector
const uv = new fabric.Point((end.x - start.x) / l, (end.y - start.y) / l)
// point on the vector at a given offset but no further than side length
const p = start.add(uv.multiply(Math.min(offset, l)))
// rotate point
return fabric.util.rotatePoint(p, start, fabric.util.degreesToRadians(45))
}
rect._getInnerBorderLines = function(c) {
// the actual offset from outer corner is the length of a hypotenuse of a right triangle with border widths as 2 sides
const offset = Math.sqrt(2 * (this.dragBorderWidth ** 2))
// find 4 inner corners as offsets rotated 45 degrees CW
const newCoords = {
tl: innerCornerPoint(c.tl, c.tr, offset),
tr: innerCornerPoint(c.tr, c.br, offset),
br: innerCornerPoint(c.br, c.bl, offset),
bl: innerCornerPoint(c.bl, c.tl, offset),
}
return this._getImageLines(newCoords)
}
rect.containsPoint = function(point, lines, absolute, calculate) {
const coords = calculate ? this.calcCoords(absolute) : absolute ? this.aCoords : this.oCoords
lines = lines || this._getImageLines(coords)
const innerRectPoints = this._findCrossPoints(point, lines);
const innerBorderPoints = this._findCrossPoints(point, this._getInnerBorderLines(coords))
// calculate intersections
return innerRectPoints === 1 && innerBorderPoints !== 1
}
canvas.add(rect)
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/3.6.2/fabric.min.js"></script>
<canvas id="c" width="400" height="300"></canvas>
here is my approach, when rect is clicked I am calculating where it is clicked and
if it is not clicked on border I have to set canvas.discardActiveObject , see comments on code
var canvas = new fabric.Canvas('c', {
selection: false
});
var rect = new fabric.Rect({
left: 50,
top: 50,
width: 100,
height: 100,
strokeWidth: 10,
stroke: 'red',
selectable: false,
evented: true,
hasBorders: true,
lockMovementY: true,
lockMovementX: true
})
canvas.on("mouse:move", function(e) {
if (!e.target || e.target.type != 'rect') return;
// when selected event is fired get the click position.
var pointer = canvas.getPointer(e.e);
// calculate the click distance from object to be exact
var distanceX = pointer.x - rect.left;
var distanceY = pointer.y - rect.top;
// check if click distanceX/Y are less than 10 (strokeWidth) or greater than 90 ( rect width = 100)
if ((distanceX <= rect.strokeWidth || distanceX >= (rect.width - rect.strokeWidth)) || (distanceY <= rect.strokeWidth || distanceY >= (rect.height - rect.strokeWidth))) {
rect.set({
hoverCursor: 'move',
selectable: true,
lockMovementY: false,
lockMovementX: false
});
document.getElementById('result').innerHTML = 'on border';
} else {
canvas.discardActiveObject();
document.getElementById('result').innerHTML = 'not on border';
rect.set({
hoverCursor: 'default',
selectable: false,
lockMovementY: true,
lockMovementX: true
});
}
});
canvas.add(rect);
canvas.renderAll();
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/3.6.2/fabric.min.js"></script>
<div id="result" style="width: 100%; "></div>
<canvas id="c" width="600" height="200"></canvas>
<pre>
</pre>
ps: you can also set the rect property to selectable: false and call canvas.setActiveObject(this); to make it selection inside if statement.

Fabricjs mask object with transformation

I'm trying to mask an object using Fabric.js free drawing brush. It works fine if the object is in its default position and without any transformations. But once I add transformations to the object, the mask is placed in the wrong position. I'm not sure how to solve this. Can someone take a look?
I want to be able to apply any transformations, before or after the mask, without messing up the mask.
let canvas = new fabric.Canvas("canvas", {
backgroundColor: "lightgray",
width: 1280,
height: 720,
preserveObjectStacking: true,
selection: false,
stateful: true
});
canvas.isDrawingMode = true;
canvas.freeDrawingBrush.color = "black";
canvas.freeDrawingBrush.width = 2;
canvas.on("path:created", function(options) {
clip(options.path);
});
function clip(path) {
canvas.isDrawingMode = false;
canvas.remove(path);
let mask = new fabric.Path(path.path, {
top: object.top,
left: object.left,
objectCaching: false,
strokeWidth: 0,
pathOffset: {
x: 0,
y: 0
}
});
let originalObjLeft = object.left,
originalObjTop = object.top;
object.set({
clipTo: function(ctx) {
mask.set({
left: -object.width / 2 - mask.width / 2 - originalObjLeft,
top: -object.height / 2 - mask.height / 2 - originalObjTop,
objectCaching: false
});
mask.render(ctx);
}
});
canvas.requestRenderAll();
}
// image
let image = new Image();
let object;
image.onload = function() {
object = new fabric.Image(image, {
width: 500,
height: 500,
//scaleX: 0.8,
//scaleY: 0.8,
//angle: 45,
top: 50,
left: 300
});
canvas.add(object);
};
image.src = "http://i.imgur.com/8rmMZI3.jpg";
I implement an exemple with some transformations (scaleX,scaleY,left,top).
I'm strugle to find a solution when the inital object have an angle different than 0. For the current solution I need it to divide the maskscale with the object scale and also adjust the positions.
let canvas = new fabric.Canvas("canvas", {
backgroundColor: "lightgray",
width: 1280,
height: 720,
preserveObjectStacking: true,
selection: false,
stateful: true
});
canvas.isDrawingMode = true;
canvas.freeDrawingBrush.color = "black";
canvas.freeDrawingBrush.width = 2;
canvas.on("path:created", function(options) {
clip(options.path);
});
function clip(path) {
canvas.isDrawingMode = false;
canvas.remove(path);
let mask = new fabric.Path(path.path, {
top: object.top,
left: object.left,
objectCaching: false,
strokeWidth: 0,
scaleX : 1/object.scaleX,
scaleY : 1/object.scaleY,
pathOffset: {
x: 0,
y: 0
}
});
let originalObjLeft = object.left,
originalObjTop = object.top,
originalMaskScaleX = mask.scaleX,
originalMaskScaleY = mask.scaleY,
originalObjScaleX = object.scaleX,
originalObjScaleY = object.scaleY;
object.set({
clipTo: function(ctx) {
mask.set({
left: -object.width / 2 -( mask.width / 2 * originalMaskScaleX) - originalObjLeft/originalObjScaleX ,
top: -object.height / 2 -( mask.height / 2 * originalMaskScaleY) - originalObjTop/originalObjScaleY ,
objectCaching: false
});
mask.render(ctx);
}
});
canvas.requestRenderAll();
}
// image
let image = new Image();
image.onload = function() {
object = new fabric.Image(image, {
width: 500,
height: 500,
scaleX: 0.8,
scaleY: 0.8,
// angle: 45,
top: 50,
left: 100
});
canvas.add(object);
};
image.src = "http://i.imgur.com/8rmMZI3.jpg";
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/2.3.6/fabric.js"></script>
<div class="canvas__wrapper">
<canvas id="canvas" width="1280" height="720"></canvas>
</div>
You can check here for loadFromJSON support.
The only problem remains is when the object is rotated.
Basically whenever you set an angle, your context matrix has been transformed. In order to mask properly you need to return to initial state of the Transformation Matrices. Fabricjs handles first matrix with center point of an object (calculates center of an object with or without an angle). Second matrix is rotating matrix, and third - scaling.
To display image with all options which are set to an object, you need to multiply all Matrices:
(First Matrix * Second Matrix) * Third Matrix
So the idea of clipping will be reverse engineering of rotating context and multiplications of matrices:
difference between center points of regular object without rotation and center point of the same object but with rotation. After that take result of subtractions and divide by original object scale value.
let canvas = new fabric.Canvas("canvas", {
backgroundColor: "lightgray",
width: 1280,
height: 720,
preserveObjectStacking: true,
selection: false,
stateful: true
});
const angle = 45;
let objectHasBeenRotated = false;
canvas.isDrawingMode = true;
canvas.freeDrawingBrush.color = "black";
canvas.freeDrawingBrush.width = 2;
canvas.on("path:created", function (options) {
clip(options.path);
});
function clip(path) {
canvas.isDrawingMode = false;
canvas.remove(path);
let mask = new fabric.Path(path.path, {
top: 0,
left: 0,
objectCaching: false,
strokeWidth: 0,
scaleX: 1 / object.scaleX,
scaleY: 1 / object.scaleY,
pathOffset: {
x: 0,
y: 0,
}
});
let originalObjLeft = object.left,
originalObjTop = object.top,
originalMaskScaleX = mask.scaleX,
originalMaskScaleY = mask.scaleY,
originalObjScaleX = object.scaleX,
originalObjScaleY = object.scaleY,
transformedTranslate = object.translateToGivenOrigin({
x: object.left,
y: object.top
}, object.originX, object.originY, 'center', 'center'),
originalTransformLeft = transformedTranslate.x - object.getCenterPoint().x,
originalTransformTop = transformedTranslate.y - object.getCenterPoint().y;
object.set({
clipTo: function (ctx) {
ctx.save();
ctx.rotate(-angle * Math.PI / 180);
ctx.translate(originalTransformLeft / originalObjScaleX, originalTransformTop / originalObjScaleY)
mask.set({
left: -object.width / 2 - (mask.width / 2 * originalMaskScaleX) - originalObjLeft / originalObjScaleX,
top: -object.height / 2 - (mask.height / 2 * originalMaskScaleY) - originalObjTop / originalObjScaleY,
objectCaching: false
});
mask.render(ctx);
ctx.restore();
}
});
canvas.requestRenderAll();
}
// image
let image = new Image();
image.onload = function () {
object = new fabric.Image(image, {
width: 500,
height: 500,
scaleX: 0.8,
scaleY: 0.8,
angle: angle,
top: 50,
left: 300,
id: 'pug'
});
canvas.add(object);
};
image.src = "http://i.imgur.com/8rmMZI3.jpg";
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/2.3.6/fabric.js"></script>
<div class="canvas__wrapper">
<canvas id="canvas" width="1280" height="720"></canvas>
</div>

Proper way to approach loadable masks in Fabric.js

I have this bounty open Fabricjs mask object with transformation when trying to mask objects with Fabric.js.
The tool I'm developing should allow users to draw a mask over image objects, and apply transformations (skew scale rotate etc) to this object before or after the mask. I'm close to obtaining this result but objects with an angle are still not working.
I'm also trying to save this object to a database using toJSON and loadFromJSON, but after a few days trying to accomplish this I realize that this solution will not work because any references outside the ctx scope can't be accessed while loading from JSON, so they throw an error.
clipTo: function(ctx) {
mask.set({
left:
-object.width / 2 -
(mask.width / 2) * originalMaskScaleX -
originalObjLeft / originalObjScaleX,
top:
-object.height / 2 -
(mask.height / 2) * originalMaskScaleY -
originalObjTop / originalObjScaleY,
objectCaching: false
});
mask.render(ctx);
}
Is Fabric.js the proper solution to this problem? Should I be using something else? If this can be done with Fabric.js, what is the proper approach?
I extended fabric.Image with some custom attributes.
Also I attached the mask on fabric.Image.
For fabric.Image.fromObject after the image is loaded I need it to load also the mask( which I know is a path) and attach to image.
This is a fast implementation. I'm pretty sure this code can be simplified.
Please tell me know if something is not clear enougth
canvas = new fabric.Canvas("canvas", {
backgroundColor: "lightgray",
width: 1280,
height: 720,
preserveObjectStacking: true,
selection: false,
stateful: true
});
canvas.isDrawingMode = true;
canvas.freeDrawingBrush.color = "black";
canvas.freeDrawingBrush.width = 2;
canvas.on("path:created", function(options) {
clip(options.path);
});
function clip(path) {
canvas.isDrawingMode = false;
canvas.remove(path);
let mask = new fabric.Path(path.path, {
top: object.top,
left: object.left,
objectCaching: false,
strokeWidth: 0,
scaleX: 1 / object.scaleX,
scaleY: 1 / object.scaleY,
pathOffset: {
x: 0,
y: 0
}
});
object = canvas.getObjects()[0];
object.originalObjLeft = object.left,
object.originalObjTop = object.top,
object.originalMaskScaleX = mask.scaleX,
object.originalMaskScaleY = mask.scaleY,
object.originalObjScaleX = object.scaleX,
object.originalObjScaleY = object.scaleY;
var transformedTranslate = object.translateToGivenOrigin({
x: object.left,
y: object.top
}, object.originX, object.originY, 'center', 'center');
object.originalTransformLeft = transformedTranslate.x - object.getCenterPoint().x;
object.originalTransformTop = transformedTranslate.y - object.getCenterPoint().y;
object.originalAngle = object.angle;
object.clipMask = mask;
object.set({
clipTo: function(ctx) {
ctx.save();
ctx.rotate(-this.originalAngle * Math.PI / 180);
ctx.translate(this.originalTransformLeft / this.originalObjScaleX, this.originalTransformTop / this.originalObjScaleY)
this.clipMask.set({
left: -object.width / 2 - (this.clipMask.width / 2 * this.originalMaskScaleX) - this.originalObjLeft / this.originalObjScaleX,
top: -object.height / 2 - (this.clipMask.height / 2 * this.originalMaskScaleY) - this.originalObjTop / this.originalObjScaleY,
objectCaching: false
});
this.clipMask.render(ctx);
ctx.restore();
}
});
canvas.requestRenderAll();
}
// image
let image = new Image();
image.onload = function() {
object = new fabric.Image(image, {
width: 500,
height: 500,
scaleX: 0.8,
scaleY: 0.8,
angle: 45,
top: 50,
left: 100
});
canvas.add(object);
};
image.src = "http://i.imgur.com/8rmMZI3.jpg";
fabric.util.object.extend(fabric.Image.prototype, {
clipMask: null,
originalObjLeft: 0,
originalObjTop: 0,
originalMaskScaleX: 1,
originalMaskScaleY: 1,
originalObjScaleX: 1,
originalObjScaleY: 1,
originalAngle:0,
originalTransformLeft:0,
originalTransformTop:0
});
fabric.Image.prototype.toObject = (function(toObject) {
return function(propertiesToInclude) {
return fabric.util.object.extend(toObject.call(this, propertiesToInclude), {
clipMask: this.clipMask ? this.clipMask.toObject(propertiesToInclude) : null,
originalObjLeft: this.originalObjLeft,
originalObjTop: this.originalObjTop,
originalMaskScaleX: this.originalMaskScaleX,
originalMaskScaleY: this.originalMaskScaleY,
originalObjScaleX: this.originalObjScaleX,
originalObjScaleY: this.originalObjScaleY,
originalAngle:this.originalAngle,
originalTransformLeft:this.originalTransformLeft,
originalTransformTop:this.originalTransformTop
});
}
})(fabric.Image.prototype.toObject);
fabric.Image.fromObject = (function(fromObject) {
return function(_object, callback) {
fromObject.call(this, _object, (function(callback, _object) {
return function(image) {
if (image.clipMask) {
fabric.Path.fromObject(image.clipMask, (function(callback) {
return function(path) {
path.pathOffset.x = 0;
path.pathOffset.y = 0;
image.clipMask = path;
callback(image);
}
})(callback))
} else {
callback(image);
}
}
})(callback, _object));
return;
}
})(fabric.Image.fromObject)
$("#button1").on('click', function() {
let dataJSON = canvas.toJSON();
canvas.clear();
canvas.loadFromJSON(
dataJSON,
canvas.renderAll.bind(canvas));
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/2.3.6/fabric.js"></script>
<script src="https://code.jquery.com/jquery-2.2.4.min.js" integrity="sha256-BbhdlvQf/xTY9gja0Dq3HiwQF8LaCRTXxZKRutelT44=" crossorigin="anonymous"></script>
<button id="button1">SAve/Load JSON</button>
<div class="canvas__wrapper">
<canvas id="canvas" width="1280" height="720"></canvas>
</div>
UPDATE
I updated the code to fix the problem with angle from here:

How to resize svg shape based on text content

I would like to have svg shape scale based on text content of text area or text-input. As the text content increases, the size of the underlying svg element should increase as well
This is what I have so far:
var graph = new joint.dia.Graph;
var paper = new joint.dia.Paper({
el: $('#myholder'),
width: 1330,
height: 660,
model: graph,
gridSize: 1,
defaultLink: new joint.dia.Link({
attrs: {'.marker-target': {d: 'M 10 0 L 0 5 L 10 10 z'}}
}),
validateConnection: function (cellViewS, magnetS, cellViewT, magnetT, end, linkView) {
// Prevent linking from input ports.
if (magnetS && magnetS.getAttribute('type') === 'input')
return false;
// Prevent linking from output ports to input ports within one element.
if (cellViewS === cellViewT)
return false;
// Prevent loop linking
return (magnetS !== magnetT);
// Prevent linking to input ports.
return magnetT && magnetT.getAttribute('type') === 'input';
},
// Enable marking available cells & magnets
markAvailable: true,
//Enable link snapping within 75px lookup radius
// snapLinks: {radius: 75},
interactive: function (cellView, methodName)
{
if (cellView.model.get('isInteractive') === false)
return false;
// return true;
}
});
joint.shapes.devs.CircleModel = joint.shapes.devs.Model.extend({
markup: '<g class="rotatable"><g class="scalable"><circle class="body"/></g><text class="label"/><g class="inPorts"/><g class="outPorts"/></g>',
// portMarkup: '<g class="port port<%=1%>"><rect class="port-body"/><text class="port-label"/></g>',
defaults: joint.util.deepSupplement({
type: 'devs.CircleModel',
attrs: {
'.body': {r: 50, cx: 50, stroke: '', fill: 'white'},
'.label': {text: '', 'ref-y': 0.5, 'y-alignment': 'middle'},
'.port-body': {r: 3, width: 10, height: 10, x: -5, stroke: 'gray', fill: 'lightgray', magnet: 'active'}
}
}, joint.shapes.devs.Model.prototype.defaults)
});
joint.shapes.devs.CircleModelView = joint.shapes.devs.ModelView;
var rect = new joint.shapes.basic.Rect({
isInteractive: false,
position: {x: 10, y: 50},
size: {width: 51, height: 41},
attrs: {rect: {fill: '#D6F2FC', stroke: '#7E7E7E'}, '.': {magnet: false}}
});
// Create a custom element.
// ------------------------
joint.shapes.html = {};
joint.shapes.html.Element = joint.shapes.basic.Rect.extend({
defaults: joint.util.deepSupplement({
type: 'html.Element',
attrs: {
rect: {stroke: 'none', 'fill-opacity': 0}
}
}, joint.shapes.basic.Rect.prototype.defaults)
});
// Create a custom view for that element that displays an HTML div above it.
// -------------------------------------------------------------------------
joint.shapes.html.ElementView = joint.dia.ElementView.extend({
template: [
'<div class="html-element">',
'<button class="delete">x</button>',
'<span></span>', '<br/>',
// '<input type="text" value="" />',
'<textarea id="txt" type="text" rows="10" value="Start writing"></textarea>',
'</div>'
].join(''),
initialize: function () {
_.bindAll(this, 'updateBox');
joint.dia.ElementView.prototype.initialize.apply(this, arguments);
this.$box = $(_.template(this.template)());
// Prevent paper from handling pointerdown.
this.$box.find('input,select').on('mousedown click', function (evt) {
evt.stopPropagation();
});
this.$ruler = $('<span>', {style: 'visibility: hidden; white-space: pre'});
$(document.body).append(this.$ruler);
// This is an example of reacting on the input change and storing the input data in the cell model.
this.$box.find('textarea').on('input', _.bind(function (evt) {
var val = $(evt.target).val();
this.model.set('textarea', val);
this.$ruler.html(val);
var width = this.$ruler[0].offsetWidth;
var height = this.$ruler[0].offsetHeight;
var area = width * height;
height = area / 150;
width = 150;
if ((area > 9000))
{
this.model.set('size', {width: width + 50, height: height + 80});
this.$box.find('textarea').css({width: width, height: height + 30});
// this.$box.find('.color-edit').css({width: width + 50, height: height + 80});
this.$box.find('.in').css({top: height + 75});
}
}, this));
this.$box.find('textarea').on('click', _.bind(function () {
this.$box.find('.delete').css({opacity: 1});
this.$box.find('textarea').css({opacity: 1});
}, this));
this.$box.find('textarea').on('blur', _.bind(function () {
this.$box.find('.delete').css({opacity: 0});
this.$box.find('textarea').css({opacity: 0});
}, this));
this.$box.find('.delete').on('click', _.bind(this.model.remove, this.model));
// Update the box position whenever the underlying model changes.
this.model.on('change', this.updateBox, this);
// Remove the box when the model gets removed from the graph.
this.model.on('remove', this.removeBox, this);
this.updateBox();
this.listenTo(this.model, 'process:ports', this.update);
joint.dia.ElementView.prototype.initialize.apply(this, arguments);
},
render: function () {
joint.dia.ElementView.prototype.render.apply(this, arguments);
this.paper.$el.prepend(this.$box);
this.updateBox();
return this;
},
updateBox: function ()
{
// Set the position and dimension of the box so that it covers the JointJS element.
var bbox = this.model.getBBox();
// Example of updating the HTML with a data stored in the cell model.
this.$box.find('label').text(this.model.get('label'));
this.$box.find('span').text(this.model.get('select'));
this.$box.css({width: bbox.width + 6, height: bbox.height, left: bbox.x, top: bbox.y, transform: 'rotate(' + (this.model.get('angle') || 0) + 'deg)'});
},
removeBox: function (evt) {
this.$ruler.remove();
this.$box.remove();
}
});
paper.on('cell:pointerdblclick', function (cellView, evt, x, y)
{
var clone = cellView.model.clone();
if (rect.id === cellView.model.id)
{
clone = new joint.shapes.html.Element({
position: {x: 100, y: 60},
size: {width: 81, height: 69},
inPorts: [''],
outPorts: [''],
attrs: {
'.': {magnet: true},
'.label': {text: '', 'ref-x': .4, 'ref-y': .2},
'.inPorts circle': {type: 'input'},
'.outPorts circle': {type: 'output'},
'.port-body': {r: 3}
}
});
// clone.resize(2*81,2*39)
graph.addCell(clone);
}
});
// // First, unembed the cell that has just been grabbed by the user.
paper.on('cell:pointerdown', function (cellView, evt, x, y) {
var cell = cellView.model;
if (!cell.get('embeds') || cell.get('embeds').length === 0) {
// Show the dragged element above all the other cells (except when the
// element is a parent).
cell.toFront();
_.invoke(graph.getConnectedLinks(cell), 'toFront');
}
if (cell.get('parent')) {
graph.getCell(cell.get('parent')).unembed(cell);
}
});
// When the dragged cell is dropped over another cell, let it become a child of the
//element below.
paper.on('cell:pointerup', function (cellView, evt, x, y) {
if (cellView.model.isLink())
return;
var cell = cellView.model;
var cellViewsBelow = paper.findViewsFromPoint(cell.getBBox().center());
if (cellViewsBelow.length) {
// Note that the findViewsFromPoint() returns the view for the `cell` itself.
var cellViewBelow = _.find(cellViewsBelow, function (c) {
return c.model.id !== cell.id;
});
// Prevent recursive embedding.
if (cellViewBelow && cellViewBelow.model.get('parent') !== cell.id) {
cellViewBelow.model.embed(cell);
}
}
});
graph.addCells([rect]);
Could not find a solution elsewhere. Any help would be appreciated. thanks
You have to make the HTML Input resize based on the text inside.
Auto-scaling input[type=text] to width of value?
The ElementView has to listen to the HTML Input changes (input event) and update the size of the model based on the width and height of the HTML Input.
Example:
function onTextInput(evt) {
var $input = $(evt.target);
// 1. auto-scaling the input based on the text inside.
$input.attr('size', Math.max($input.val().length, 10));
// 2. resizing the model to the size of the input + padding.
model.resize($input.outerWidth() + 5, $input.outerHeight() + 40);
}
$('input').on('input', onTextInput);
JS Fiddle: http://jsfiddle.net/kumilingus/Lrffgvqn/
Similar with HTML TextArea, where the only difference will be the way how you auto-scale it based on the text inside.

programatically add images to canvas in fabricJS

I have an image to be added to a fabricJS canvas. The user will specify the number on times the same image should be added on the canvas.
When I loop through the number of times the user entered and use fabric.Image.fromURL to add, it gets added just once and only to the last location.
EDIT:
Here is the jsfiddle on what I am trying to do
http://jsfiddle.net/apsixion/9g0Lfydw/3/
Here is how I solved it today..
Moved the code to draw the image to a different function outside and called the function in a look. May not be the most optimal solution, but works for me.
// create a wrapper around native canvas element (with id="c")
var canvas = new fabric.Canvas('c', {
selection: false
});
function drawRectangles(number) {
i = 0;
var pic =
"http://depts.washington.edu/hplab/wordpress/wp-content/uploads/2014/10/smiley-e1415084033498.jpeg";
for (var i = 0; i < number; i++) {
var rect = new fabric.Rect({
left: 5 + i * 160,
top: 10,
width: 150,
height: 150,
lockRotation: true,
lockMovementX: false,
lockMovementY: false
});
canvas.add(rect);
}
for (var k = 0; k < number; k++) {
drawStars(k);
}
}
function drawStars(k) {
var picStars =
"http://reviews.babymonitorsdirect.co.uk/images/5-stars.jpg";
fabric.Image.fromURL(picStars, function (img2) {
img2.set({
lockRotation: true,
lockMovementX: true,
lockMovementY: true,
left: 5 + k * 160,
top: 10,
width: 50,
height: 50,
lockScalingX: true,
lockScalingY: true,
selectable: true,
hasControls: false
});
canvas.add(img2);
});
}
drawRectangles(5);
http://jsfiddle.net/apsixion/z6dq0dp9/2/

Resources