Performance of fabric.js with many items - javascript

I'm playing around with fabric.js and wonder how to tweak performance on handling object events when having a lot of items (more than 1500...).
I have build a little example:
https://codepen.io/skurrilewelt/pen/XWWvBoP
$(document).ready(function () {
var canvas = new fabric.Canvas('canvas');
var width = 10;
var height = 10;
var dist = 5;
function add(left, top) {
canvas.add(new fabric.Rect({
left: left,
top: top,
width: width,
height: height,
fill: 'green',
stroke: 'black',
strokeWidth: 1,
padding: 10,
draggable: false,
hasControls: false,
hasBorders: false,
}));
}
var col = 0;
var row = 0;
for (row = 0; row < 50; row++) {
for (col = 0; col < 30; col++) {
add(row * (width + dist), col * (height + dist));
}
}
canvas.renderOnAddRemove = false;
canvas.on({
'mouse:down': function (e) {
if (e.target) {
console.log(e.target);
canvas.getActiveObject().set("fill", '#f00');
canvas.renderAll();
}
}
});
canvas.renderAll();
});
If you click an rect you will have to wait approximatly 3 secs until the rect will change it's color.
Are there other approaches or can I enhance the performance of this? I will need up to 3500 objects...
Thanks in advance

Related

How can i keep the Konvas layer centered after resize?

So i'm using Konvajs, to draw segments, only vertical and horizontal. My problem is the following, when i start the debug, if the window is small and then i click to maximize, the layer will stay with same size as when small. Ex:
here it s the Javascript file for the Konva creation:
function setUpDesignMap(){
var width = $('#designContainer').width();
var height = $('#designContainer').height();
var blockSnapSize = gridSize;
var isDrawing = false;
var drawShape = null;
designStage = new Konva.Stage({
container: 'designContainer',
width: width,
height: height,
draggable: true,
name:'designStage'
// dragBoundFunc: function(pos) {
// var newY = pos.y < 0 ? 0 : pos.y;
// var newX = pos.x < 0 ? 0 : pos.x;
// return {
// x: newX,
// y: newY
// };
// }
});
/*Set up grid*/
var gridLayer = new Konva.Layer();
var tipLayer = new Konva.Layer();
drawLayer = new Konva.Layer();
segGrp = new Konva.Group({name:'segments'});
/* draw grid */
for (var i = 0; i < 240; i++) {
gridLayer.add(new Konva.Line({
points: [Math.round(i * blockSnapSize), 0, Math.round(i * blockSnapSize), 120*blockSnapSize],
stroke: '#888',
strokeWidth: 0.5,
}));
}
gridLayer.add(new Konva.Line({points: [0,0,10,10]}));
for (var j = 0; j < 120; j++) {
gridLayer.add(new Konva.Line({
points: [0, Math.round(j * blockSnapSize), 240*blockSnapSize, Math.round(j * blockSnapSize)],
stroke: '#888',
strokeWidth: 0.5,
}));
}
/* Stage initial position*/
zoomVars_design = {scale: 1, factor: 1.1, origin: {x:-100*gridSize,y:-50*gridSize}};
designStage.position(zoomVars_design.origin);
designStage.scale({ x: 1, y: 1 });
/*Set up mouse tip*/
var mouseTip = new Konva.Rect({
width: 6,
height: 6,
opacity:0,
fill: '#FFCC00',
stroke: '#FFCC00'
});
tipLayer.add(mouseTip);
/* Set up length indicator */
var lenShape = new Konva.Line({
points:[],
opacity: 1,
strokeWidth: 1,
stroke: '#000',
dash:[7,5]
});
gridLayer.add(lenShape);
/* Set up length text*/
var lenText = new Konva.Text({
align: 'center',
verticalAlign: 'middle',
fontSize: 12
});
gridLayer.add(lenText);
var lenSide = new Konva.Line({
points:[],
opacity: 1,
strokeWidth: 1,
stroke: '#000',
dash:[7,5]
});
gridLayer.add(lenSide);
/*Mouse handlers*/
//remove default behaviour for the container
$("#designContainer").on('mousedown', function(e){
e.preventDefault();
});
$("#designContainer").on('contextmenu', function(e){
e.preventDefault();
});
//mouse handlers on stage
designStage.on('contentMousedown', function (e) {
if(e.evt.button == 0){
designStage.draggable(false);
}
else{
designStage.draggable(true);
return;
}
if(mouseMode == 0){ //draw mode
var startPoint = {x:(mouseTip.attrs.x+3), y:(mouseTip.attrs.y+3)};
if(!isDrawing){
if(!isIntersection(startPoint)){ //cannot be intersection to avoid crosses
//check if there's an intersection on the begining
if(isCorner(startPoint)){
intersectingSeg = false;
intersectingCorner = true;
console.log('intersectingSeg = false')
}
else{
//check if its on an existing segment
var isonseg = isOnSegment(startPoint);
if(isonseg.b){
//validate resulting segments length
var intercected = getSegmentsArray(isonseg.d)[isonseg.i];
var res = breakSegAtIntersection(startPoint,intercected);
if(isValidLength(res[0].start,res[0].end) && isValidLength(res[1].start,res[1].end)){
intersectingSeg = true;
intersectingCorner = false;
console.log('>>>>>>>Intersecting Segment at start')
}
else{
consoleAdd('Resulting segments are too small to you');
return;
}
}
}
//start drawing
drawShape = new Konva.Line({
points: [startPoint.x, startPoint.y],
strokeWidth: 15,
stroke: 'black',
opacity: 0.5,
dir:'h'
});
drawShape.on('mouseover', function (e) {
if(mouseMode == 1){
this.stroke('#031C4D');
drawLayer.draw();
}
});
drawShape.on('mouseout', function (e) {
if(mouseMode == 1){
this.stroke('black');
drawLayer.draw();
}
});
drawShape.on('mouseup', function (e) {
if(mouseMode == 1){
this.stroke('#092C70');
drawLayer.draw();
}
});
segGrp.add(drawShape);
drawLayer.add(segGrp);
drawLayer.draw();
isDrawing = true;
}
else{
consoleAdd('Cannot start on an intersection');
}
gridLayer.draw();
}
}
});
So i want the layer not to be that small, always refitting. Thank you all for your attention!
You need to manually resize your stage when the size of container element is changed.
window.addEventListener('resize', () => {
var width = $('#designContainer').width();
var height = $('#designContainer').height();
designStage.width(width);
designStage.height(height);
})
You may also need to apply the scale to the stage.
Take a look into related demo: https://konvajs.org/docs/sandbox/Responsive_Canvas.html

fabric js Polygon setCoords

I've got a question about fabric.js and the polygon-object.
I have an example of my problem in this fiddle:
Click me
First 4 fabric.Circle subobjects called linePoint are drawn.
The linePoint objects have an extra x(same as left) and y(same as top) coordinate and a reference to which polygon they belong to:
fabric.LinePoint = fabric.util.createClass(fabric.Circle,
{
initialize: function (options) {
options || (options = {});
this.callSuper('initialize', options);
options &&
this.set('type', 'line_point'),
this.set('x', this.left),
this.set('y', this.top),
this.set('polygon', options.polygon)
},
setPointCoordinates: function(new_left, new_top) {
this.set('x', new_left);
this.set('y', new_top);
this.set('left', new_left);
this.set('top', new_top);
}
With the now given x and y coordinates there is a Polygon drawn between the Points.
The problem is now, when you move the Circles, the Polygon is moved correctly, but its border (or I don't know how to exactly call it) will stay the same small rectangle as it was.
I want to update the polygon Coords too, I tried .setCoords(), but nothing happened.
Maybe you can help me. :) Thanks!
In reply also to:
https://groups.google.com/forum/#!topic/fabricjs/XN1u8E0EBiM
This is your modified fiddle:
https://jsfiddle.net/wum5zvwk/2/
fabric.LinePoint = fabric.util.createClass(fabric.Circle,
{
initialize: function (options) {
options || (options = {});
this.callSuper('initialize', options);
this.set('type', 'line_point'),
this.set('x', this.left),
this.set('y', this.top),
this.set('polygon', options.polygon)
},
setPointCoordinates: function(new_left, new_top) {
this.set('x', new_left);
this.set('y', new_top);
this.set('left', new_left);
this.set('top', new_top);
}
});
var canvas = new fabric.Canvas('canvas');
fabric.Object.prototype.originX = fabric.Object.prototype.originY = 'center';
document.getElementById("canvas").tabIndex = 1000;
drawPolygonToCanvas();
canvas.on('object:moving', function(event) {
var object = event.target;
switch(object.type) {
case 'line_point':
//move polygon
object.setPointCoordinates(object.left, object.top);
object.polygon.setCoords();
object.polygon.initialize(object.polygon.points);
object.polygon.top += object.polygon.height / 2;
object.polygon.left += object.polygon.width / 2;
canvas.renderAll();
break;
}
});
function drawPolygonToCanvas()
{
//creting end_points and set them
old_position = canvas.getPointer(event.e);
var end_point_1 = createLinePoint(100, 100);
var end_point_2 = createLinePoint(100, 150);
var end_point_3 = createLinePoint(150, 150);
var end_point_4 = createLinePoint(150, 100);
end_points_in_use = [end_point_1, end_point_2, end_point_3, end_point_4];
canvas.add(end_point_1, end_point_2, end_point_3, end_point_4);
drawPoly(end_points_in_use);
canvas.deactivateAll();
canvas.renderAll();
}
function drawPoly(point_array)
{
var poly = new fabric.Polygon(point_array, {
left: (100 + ((150 - 100) /2)),
top: (100 + ((150 - 100) /2)),
fill: 'lightblue',
lockScalingX: true,
lockScalingY: true,
lockMovementX: true,
lockMovementY: true,
perPixelTargetFind: true,
opacity: 0.5,
type: 'polygon'
});
for (var i = 0; i < point_array.length; i++) {
point_array[i].polygon = poly;
}
canvas.add(poly);
poly.sendToBack();
}
function createLinePoint(left, top) {
return new fabric.LinePoint({
left: left,
top: top,
strokeWidth: 2,
radius: 15,
fill: '#fff',
stroke: '#666',
related_poly_point: 0,
lockScalingX: true,
lockScalingY: true
});
}
<script src="https://rawgit.com/kangax/fabric.js/master/dist/fabric.js"></script>
<div id="canvas-wrapper" style="position:relative;width:704px;float:left;">
<canvas id="canvas" width="700" height="600" style="border:1px solid #000000;"></canvas>
</div>
Modifying the polygon points is not enough to have the bounding box adjusted. The easies thing i can think of is to re initialize the polygon with the new points coordinates.

Drag And Drop Image to Canvas (FabricJS)

The Problem
I want to do this with an image instead of a canvas object. Meaning you have to add the thing you want to add TO AND AS A PART of the canvas before you can add it. The images are actually part of the website so it doesn't need to do some intricate stuff. This code I found here only works for when it's an object not an actual element. And by the way I'm using FabricJS just to let you know that I'm not using the default HTML5 canvas stuff.
As for any alternatives that are possibly going to work without using my current code. Please do post it down below in the comments or in the answers. I would really love to see what you guys got in mind.
Summary
Basically I want to be able to drag and drop images through the canvas while retaining the mouse cursor position. For example if I drag and image and the cursor was at x: 50 y: 75 it would drop the image to that exact spot. Just like what the code I found does. But like the problem stated the CODE uses an object for you to drag it to the canvas then it clones it. I want this functionality using just plain old elements. E.g: <img>.
THE CODE -
JsFiddle
window.canvas = new fabric.Canvas('fabriccanvas');
var edgedetection = 8; //pixels to snap
canvas.selection = false;
window.addEventListener('resize', resizeCanvas, false);
function resizeCanvas() {
canvas.setHeight(window.innerHeight);
canvas.setWidth(window.innerWidth);
canvas.renderAll();
}
// resize on init
resizeCanvas();
//Initialize Everything
init();
function init(top, left, width, height, fill) {
var bg = new fabric.Rect({
left: 0,
top: 0,
fill: "#eee",
width: window.innerWidth,
height: 75,
lockRotation: true,
maxHeight: document.getElementById("fabriccanvas").height,
maxWidth: document.getElementById("fabriccanvas").width,
selectable: false,
});
var squareBtn = new fabric.Rect({
top: 10,
left: 18,
width: 40,
height: 40,
fill: '#af3',
lockRotation: true,
originX: 'left',
originY: 'top',
cornerSize: 15,
hasRotatingPoint: false,
perPixelTargetFind: true,
});
var circleBtn = new fabric.Circle({
radius: 20,
fill: '#f55',
top: 10,
left: 105,
});
var triangleBtn = new fabric.Triangle({
width: 40,
height: 35,
fill: 'blue',
top: 15,
left: 190,
});
var sqrText = new fabric.IText("Add Square", {
fontFamily: 'Indie Flower',
fontSize: 14,
fontWeight: 'bold',
left: 6,
top: 50,
selectable: false,
});
var cirText = new fabric.IText("Add Circle", {
fontFamily: 'Indie Flower',
fontSize: 14,
fontWeight: 'bold',
left: 95,
top: 50,
selectable: false,
});
var triText = new fabric.IText("Add Triangle", {
fontFamily: 'Indie Flower',
fontSize: 14,
fontWeight: 'bold',
left: 175,
top: 50,
selectable: false,
});
var shadow = {
color: 'rgba(0,0,0,0.6)',
blur: 3,
offsetX: 0,
offsetY: 2,
opacity: 0.6,
fillShadow: true,
strokeShadow: true
};
window.canvas.add(bg);
bg.setShadow(shadow);
window.canvas.add(squareBtn);
window.canvas.add(circleBtn);
window.canvas.add(triangleBtn);
window.canvas.add(sqrText);
window.canvas.add(cirText);
window.canvas.add(triText);
canvas.forEachObject(function (e) {
e.hasControls = e.hasBorders = false; //remove borders/controls
});
function draggable(object) {
object.on('mousedown', function() {
var temp = this.clone();
temp.set({
hasControls: false,
hasBorders: false,
});
canvas.add(temp);
draggable(temp);
});
object.on('mouseup', function() {
// Remove an event handler
this.off('mousedown');
// Comment this will let the clone object able to be removed by drag it to menu bar
// this.off('mouseup');
// Remove the object if its position is in menu bar
if(this.top<=75) {
canvas.remove(this);
}
});
}
draggable(squareBtn);
draggable(circleBtn);
draggable(triangleBtn);
this.canvas.on('object:moving', function (e) {
var obj = e.target;
obj.setCoords(); //Sets corner position coordinates based on current angle, width and height
canvas.forEachObject(function (targ) {
activeObject = canvas.getActiveObject();
if (targ === activeObject) return;
if (Math.abs(activeObject.oCoords.tr.x - targ.oCoords.tl.x) < edgedetection) {
activeObject.left = targ.left - activeObject.currentWidth;
}
if (Math.abs(activeObject.oCoords.tl.x - targ.oCoords.tr.x) < edgedetection) {
activeObject.left = targ.left + targ.currentWidth;
}
if (Math.abs(activeObject.oCoords.br.y - targ.oCoords.tr.y) < edgedetection) {
activeObject.top = targ.top - activeObject.currentHeight;
}
if (Math.abs(targ.oCoords.br.y - activeObject.oCoords.tr.y) < edgedetection) {
activeObject.top = targ.top + targ.currentHeight;
}
if (activeObject.intersectsWithObject(targ) && targ.intersectsWithObject(activeObject)) {
} else {
targ.strokeWidth = 0;
targ.stroke = false;
}
if (!activeObject.intersectsWithObject(targ)) {
activeObject.strokeWidth = 0;
activeObject.stroke = false;
activeObject === targ
}
});
});
}
More codes that I found but doesn't answer my problem:
var canvas = new fabric.Canvas('c');
canvas.on("after:render", function(){canvas.calcOffset();});
var started = false;
var x = 0;
var y = 0;
var width = 0;
var height = 0;
canvas.on('mouse:down', function(options) {
//console.log(options.e.clientX, options.e.clientY);
x = options.e.clientX;
y = options.e.clientY;
canvas.on('mouse:up', function(options) {
width = options.e.clientX - x;
height = options.e.clientY - y;
var square = new fabric.Rect({
width: width,
height: height,
left: x + width/2 - canvas._offset.left,
top: y + height/2 - canvas._offset.top,
fill: 'red',
opacity: .2
});
canvas.add(square);
canvas.off('mouse:up');
$('#list').append('<p>Test</p>');
});
});
This code adds a rectangle to the canvas. But the problem is this doesn't achieve what I want which is basically, as previously stated, that I want to be able to drag an img element and then wherever you drag that image on the canvas it will drop it precisely at that location.
CREDITS
Fabric JS: Copy/paste object on mouse down
fabric.js Offset Solution
it doesn't need to do some intricate stuff.
A logical framework under which to implement image dragging might be to monitor mouse events using event listeners.
On mouse down over an Image element within the page but not over the canvas, record which image element and the mouse position within the image. On mouse up anywhere set this record back to null.
On mouse over of the canvas with a non empty image record, create a Fabric image element from the Image element (as per documentation), calculate where it goes from the recorded image position and current mouse position, paste it under the mouse, make it draggable and simulate the effect of a mouse click to continue dragging it.
I have taken this question to be about the design and feasibility stages of program development.

object:scaling is not working with bounding object

I am new in Fabric js. can anyone suggest me for restrict scale object within bounding box ?
my java-script code is below
(function(global) {
var canvas = new fabric.Canvas('maincanvas');
var yourNameObjs = [];
var goodtop =358, goodleft=250, boundingObject;
var boundingObject = new fabric.Rect({
left: 100,
top: 90,
width: 200,
height: 250,
opacity: 4,
selectable:false,
fill: 'red',
});
canvas.add(boundingObject);
addText = function(){
var nametoprint = $("#nametoprint").val();
canvas.remove(yourNameObjs);
yourNameObjs = new fabric.Text(nametoprint, {
left: 150, //Take the block's position
opacity: 9,
top: 150,
fontFamily: 'Delicious_500',
}
);
canvas.add(yourNameObjs);
canvas.on("object:moving", function(){
var obj = yourNameObjs;
var bounds = boundingObject;
obj.setCoords();
if(!obj.isContainedWithinObject(bounds)){
obj.setTop(goodtop);
obj.setLeft(goodleft);
} else {
goodtop = obj.top;
goodleft = obj.left;
}
});
canvas.on("object:scaling", function(){
var obj = yourNameObjs;
var bounds = boundingObject;
obj.setCoords();
if(!obj.isContainedWithinObject(bounds)){
obj.setTop(goodtop);
obj.setLeft(goodleft);
} else {
goodtop = obj.top;
goodleft = obj.left;
}
});
canvas.renderAll();
};
})();
html code is below
<input type="text" name="nametoprint" id="nametoprint" value="alex" />
<input type= "button" name="addtxt" id="addtxt" onclick="addText()" value="add text" />
<canvas id="maincanvas" style="border:1px solid #000;" width="400" height="450" ></canvas>
also i added this in fiddle here http://jsfiddle.net/pfgm8myp/
i want only allow to scale text object within the red bound box.moving object is working fine.i also added code for scaling but its not working.
can any one suggest me how to restrict this scaling ?
thanks in advance.
i posted it here with my own logic. http://jsfiddle.net/9xojfmyt/
i added below javascript code
(function(global) {
var canvas = new fabric.Canvas('maincanvas');
var yourNameObjs = [];
var goodtop =358, goodleft=250, boundingObject;
var boundingObject = new fabric.Rect({
left: 100,
top: 90,
width: 200,
height: 250,
opacity: 4,
selectable:false,
fill: 'red',
});
canvas.add(boundingObject);
addText = function(){
var nametoprint = $("#nametoprint").val();
canvas.remove(yourNameObjs);
yourNameObjs = new fabric.Text(nametoprint, {
left: 150, //Take the block's position
opacity: 9,
top: 150,
fontFamily: 'Delicious_500',
}
);
canvas.add(yourNameObjs);
// canvas moving limit
canvas.observe("object:moving", function(e){
var obj = yourNameObjs;
var bounds = boundingObject;
var objw = parseInt(parseInt(obj.width) * obj.scaleX);
var objh = parseInt(parseInt(obj.height) * obj.scaleY);
//left
if(obj.left < bounds.left){
obj.setLeft(bounds.left);
}
//top
if(obj.top < bounds.top){
obj.setTop(bounds.top);
}
//right
if((parseInt(obj.left) + objw) > (parseInt(bounds.left)+parseInt(bounds.width))){
obj.setLeft(((parseInt(bounds.left)+parseInt(bounds.width)) - objw));
}
//bottom
if((parseInt(obj.top) + objh) > (parseInt(bounds.top)+parseInt(bounds.height))){
obj.setTop(((parseInt(bounds.top)+parseInt(bounds.height)) - objh));
}
});
// canvas scaling limit
canvas.observe("object:scaling", function(e){
var obj = yourNameObjs;
var bounds = boundingObject;
var objw = parseInt(parseInt(obj.width) * obj.scaleX);
var objh = parseInt(parseInt(obj.height) * obj.scaleY);
//left
if(obj.left < bounds.left || (parseInt(obj.left) + objw) > (parseInt(bounds.left)+parseInt(bounds.width))){
obj.setLeft(bounds.left);
obj.setScaleX((bounds.width/obj.width));
}
//top
if(obj.top < bounds.top || (parseInt(obj.top) + objh) > (parseInt(bounds.top)+parseInt(bounds.height))){
obj.setTop(bounds.top);
obj.setScaleY((bounds.height/obj.height));
}
});
canvas.renderAll();
};
})();
Bit modification compare to Albert's answer. If you want to restrict object within canvas then replace moving event with below code.
// canvas scaling limit
canvas.observe("object:scaling", function(e){
var obj = e.target;
var bounds = {
left : 5, //here it keep 5 px margin from all direction
top : 5,
width : canvas.width - 10,
height : canvas.height - 10
};
var objw = parseInt(parseInt(obj.width) * obj.scaleX);
var objh = parseInt(parseInt(obj.height) * obj.scaleY);
//left
if(obj.left < bounds.left || (parseInt(obj.left) + objw) > (parseInt(bounds.left)+parseInt(bounds.width))){
obj.setLeft(bounds.left);
obj.setScaleX((bounds.width/obj.width));
}
//top
if(obj.top < bounds.top || (parseInt(obj.top) + objh) > (parseInt(bounds.top)+parseInt(bounds.height))){
obj.setTop(bounds.top);
obj.setScaleY((bounds.height/obj.height));
}
});

Fabric.js Sticky Note type text wrap

I am trying to make a sticky note type utility with the fabric canvas. It will help to be used as annotators.
I want the text to wrap by itself at the given rectangle's width.
Can someone update my fiddle work??
Suggestions are appreciated. Regards...
The following is the link to a part of my fiddle:
http://jsfiddle.net/U7E9q/5/
var canvas = new fabric.Canvas('fabric-canvas');
canvas.hoverCursor = 'pointer';
var text = new fabric.IText("Enter Text Here ",{
fontSize: 20,
top: 100,
left: 100,
backgroundColor: '#faa',
lockScalingX: true,
lockScalingY: true,
selectable: true
});
//alert(text.text);
var rect = new fabric.Rect({
text_field: text,
width: 200,
height: 50,
fill: '#faa',
rx: 10,
ry: 10,
top: 100,
left: 100
});
canvas.add(rect);
canvas.add(text);
canvas.on('object:moving', function (event){
canvas.renderAll();
});
createListenersKeyboard();
function createListenersKeyboard() {
document.onkeydown = onKeyDownHandler;
//document.onkeyup = onKeyUpHandler;
}
function onKeyDownHandler(event) {
//event.preventDefault();
var key;
if(window.event){
key = window.event.keyCode;
}
else{
key = event.keyCode;
}
switch(key){
//////////////
// Shortcuts
//////////////
// Copy (Ctrl+C)
case 67: // Ctrl+C
if(ableToShortcut()){
if(event.ctrlKey){
event.preventDefault();
copy();
}
}
break;
// Delete (Ctrl+D)
case 127: // Ctrl+D
if(ableToShortcut()){
if(event.deleteKey){
delet();
}
}
break;
// Paste (Ctrl+V)
case 86: // Ctrl+V
if(ableToShortcut()){
if(event.ctrlKey){
event.preventDefault();
paste();
}
}
break;
default:
// TODO
break;
}
}
function ableToShortcut(){
/*
TODO check all cases for this
if($("textarea").is(":focus")){
return false;
}
if($(":text").is(":focus")){
return false;
}
*/
return true;
}
function copy(){
if(canvas.getActiveGroup()){
for(var i in canvas.getActiveGroup().objects){
var object = fabric.util.object.clone(canvas.getActiveGroup().objects[i]);
object.set("top", object.top+5);
object.set("left", object.left+5);
copiedObjects[i] = object;
}
}
else if(canvas.getActiveObject()){
var object = fabric.util.object.clone(canvas.getActiveObject());
object.set("top", object.top+5);
object.set("left", object.left+5);
copiedObject = object;
copiedObjects = new Array();
}
}
function paste(){
if(copiedObjects.length > 0){
for(var i in copiedObjects){
canvas.add(copiedObjects[i]);
}
}
else if(copiedObject){
canvas.add(copiedObject);
}
canvas.renderAll();
}
function delet(){
var activeObject = canvas.getActiveObject();
canvas.remove(activeObject);
console.log('after remove getActiveObject(): ', canvas.getActiveObject(), activeObject === canvas.getActiveObject());
canvas.renderAll();
}
If you manage the sticky note as a grouped rect and text you can improve the same behavior. When you need to edit the text inside the group, you just ungroup and clone the elements, append the cloned elements to the canvas and set text as editable.
You need to handle an event like double click to handle this behavior and then handle the mousedown or other interactivity with canvas to regroup them.
http://jsfiddle.net/4HE3U/1/
Above is one fiddle that can satisfy you
Basically i have made one group of Text and Rectangle and i have added it to canvas. There is only one change you need to make is that you can take one textbox to get current sticky note text content as we can not edit text of i-text online once we are adding it any group. Currently there is no way for IText to handle the events as they are not handed down to it if it's contained in a group. I think this is also the prefered way to handle that as it would confuse the user - what if he starts to edit multiple texts. This might end up in a mess. Maybe you can rework your script a little to workaround this problems.
I have added Text and Rectangle
var canvas = new fabric.Canvas('fabric-canvas');
canvas.hoverCursor = 'pointer';
var text = new fabric.IText("Enter Text Here ",{
fontSize: 20,
top: 100,
left: 100,
backgroundColor: '#faa',
lockScalingX: true,
lockScalingY: true,
selectable: true
});
//alert(text.text);
var rect = new fabric.Rect({
text_field: text,
width: 200,
height: 50,
fill: '#faa',
rx: 10,
ry: 10,
top: 100,
left: 100
});
var group = new fabric.Group([ rect, text ], {
left: 100,
top: 100,
lockScalingX: true,
lockScalingY: true,
hasRotatingPoint: false,
transparentCorners: false,
cornerSize: 7
});
canvas.add(group);
//canvas.add(text);
canvas.on('object:moving', function (event){
canvas.renderAll();
});
createListenersKeyboard();
function createListenersKeyboard() {
document.onkeydown = onKeyDownHandler;
//document.onkeyup = onKeyUpHandler;
}
function onKeyDownHandler(event) {
//event.preventDefault();
var key;
if(window.event){
key = window.event.keyCode;
}
else{
key = event.keyCode;
}
switch(key){
//////////////
// Shortcuts
//////////////
// Copy (Ctrl+C)
case 67: // Ctrl+C
if(ableToShortcut()){
if(event.ctrlKey){
event.preventDefault();
copy();
}
}
break;
// Delete (Ctrl+D)
case 127: // Ctrl+D
if(ableToShortcut()){
if(event.deleteKey){
delet();
}
}
break;
// Paste (Ctrl+V)
case 86: // Ctrl+V
if(ableToShortcut()){
if(event.ctrlKey){
event.preventDefault();
paste();
}
}
break;
default:
// TODO
break;
}
}
function ableToShortcut(){
/*
TODO check all cases for this
if($("textarea").is(":focus")){
return false;
}
if($(":text").is(":focus")){
return false;
}
*/
return true;
}
function copy(){
if(canvas.getActiveGroup()){
for(var i in canvas.getActiveGroup().objects){
var object = fabric.util.object.clone(canvas.getActiveGroup().objects[i]);
object.set("top", object.top+5);
object.set("left", object.left+5);
copiedObjects[i] = object;
}
}
else if(canvas.getActiveObject()){
var object = fabric.util.object.clone(canvas.getActiveObject());
object.set("top", object.top+5);
object.set("left", object.left+5);
copiedObject = object;
copiedObjects = new Array();
}
}
function paste(){
if(copiedObjects.length > 0){
for(var i in copiedObjects){
canvas.add(copiedObjects[i]);
}
}
else if(copiedObject){
canvas.add(copiedObject);
}
canvas.renderAll();
}
function delet(){
var activeObject = canvas.getActiveObject();
canvas.remove(activeObject);
console.log('after remove getActiveObject(): ', canvas.getActiveObject(), activeObject === canvas.getActiveObject());
canvas.renderAll();
}
<canvas id="fabric-canvas" width="400" height="400"></canvas>
Here is Sticky note functionality. Text wrap working and font size changes w.r.t sticky note width and height. Editing mode activates on double click.
export const createStickyNotes = (canvas, options) => {
fabric.StickyNote = fabric.util.createClass(fabric.Group, {
type: "StickyNote",
initialize: function (options) {
this.set(options);
var height = this.height;
var width = this.width;
this.rectObj = new fabric.Rect({
width: width,
height: height,
fill: this.rectObj?.fill ?? "rgba(251,201,112,1)",
originX: "center",
originY: "center",
objectCaching: false,
stateProperties: ["fill"],
});
this.textObj = new fabric.Textbox(this.textObj?.text ?? "Notes", {
originX: "center",
originY: "center",
textAlign: "center",
width: 100,
hasControls: false,
fontSize: this.textObj?.fontSize ?? 30,
lineHeight: 1,
stateProperties: ["text", "fontSize"],
scaleX: this.textObj?.scaleX ?? 1,
scaleY: this.textObj?.scaleY ?? 1,
objectCaching: false,
breakWords: true,
fontFamily: "Open Sans",
});
this._objects = [this.rectObj, this.textObj];
// this custom _set function will set custom properties value to object when it will load from json.
// at that time loadFromJson function will call this initialize function.
// this._setCustomProperties(this.options);
canvas.renderAll();
//evenet will fire if the object is double clicked by mouse
this.on("mousedblclick", (e) => {
var pasteFlag = false;
var scaling = e.target.getScaledWidth() / 100;
var textForEditing;
canvas.bringToFront(e.target);
e.target.selectable = false;
const [rectObj, textObj] = this.getObjects();
textObj.clone(function (clonedObj) {
clonedObj.set({
left: e.target.left,
top: e.target.top,
lockMovementY: true,
lockMovementX: true,
hasBorders: false,
scaleX: scaling,
scaleY: scaling,
breakWords: true,
width: textObj.width,
stateProperties: [],
});
textForEditing = clonedObj;
});
this.remove(textObj);
canvas.add(textForEditing);
canvas.setActiveObject(textForEditing);
textForEditing.enterEditing();
textForEditing.selectAll();
textForEditing.paste = (function (paste) {
return function (e) {
disableScrolling();
pasteFlag = true;
};
})(textForEditing.paste);
textForEditing.on("changed", function (e) {
var fontSize = textForEditing.fontSize;
var charCount = Math.max(textForEditing._text.length, 1);
var charWR =
(textForEditing.textLines.length * width) / (charCount * fontSize);
if (textForEditing.height < height - 15) {
fontSize = Math.min(
Math.sqrt(
((height - 10 - fontSize) / 1.16) *
(width / (charCount * charWR))
),
30
);
}
if (textForEditing.height > height - 15) {
fontSize = Math.sqrt(
((height - 10) / 1.16) * (width / (charCount * charWR))
);
}
if (pasteFlag) {
pasteFlag = false;
while (
textForEditing.height > height - 15 &&
textForEditing.fontSize > 0
) {
fontSize = textForEditing.fontSize -= 0.2;
canvas.renderAll();
}
}
textForEditing.fontSize = fontSize;
});
textForEditing.on("editing:exited", () => {
enableScrolling();
canvas.setActiveObject(textObj);
textObj.set({
text: textForEditing.text,
fontSize: textForEditing.fontSize,
visible: true,
});
this.add(textObj);
this.selectable = true;
canvas.remove(textForEditing);
canvas.discardActiveObject();
});
});
function disableScrolling() {
var x = window.scrollX;
var y = window.scrollY;
window.onscroll = function () {
window.scrollTo(x, y);
};
}
var _wrapLine = function (_line, lineIndex, desiredWidth, reservedSpace) {
var lineWidth = 0,
splitByGrapheme = this.splitByGrapheme,
graphemeLines = [],
line = [],
// spaces in different languges?
words = splitByGrapheme
? fabric.util.string.graphemeSplit(_line)
: _line.split(this._wordJoiners),
word = "",
offset = 0,
infix = splitByGrapheme ? "" : " ",
wordWidth = 0,
infixWidth = 0,
largestWordWidth = 0,
lineJustStarted = true,
additionalSpace = splitByGrapheme ? 0 : this._getWidthOfCharSpacing();
reservedSpace = reservedSpace || 0;
desiredWidth -= reservedSpace;
for (var i = 0; i < words.length; i++) {
// i would avoid resplitting the graphemes
word = fabric.util.string.graphemeSplit(words[i]);
wordWidth = this._measureWord(word, lineIndex, offset);
offset += word.length;
// Break the line if a word is wider than the set width
if (this.breakWords && wordWidth >= desiredWidth) {
if (!lineJustStarted) {
graphemeLines.push(line);
line = [];
lineWidth = 0;
lineJustStarted = true;
}
this.fontSize *= desiredWidth / (wordWidth + 1);
// Loop through each character in word
for (var w = 0; w < word.length; w++) {
var letter = word[w];
var letterWidth =
(this.getMeasuringContext().measureText(letter).width *
this.fontSize) /
this.CACHE_FONT_SIZE;
line.push(letter);
lineWidth += letterWidth;
}
word = [];
} else {
lineWidth += infixWidth + wordWidth - additionalSpace;
}
if (lineWidth >= desiredWidth && !lineJustStarted) {
graphemeLines.push(line);
line = [];
lineWidth = wordWidth;
lineJustStarted = true;
} else {
lineWidth += additionalSpace;
}
if (!lineJustStarted) {
line.push(infix);
}
line = line.concat(word);
infixWidth = this._measureWord([infix], lineIndex, offset);
offset++;
lineJustStarted = false;
// keep track of largest word
if (wordWidth > largestWordWidth && !this.breakWords) {
largestWordWidth = wordWidth;
}
}
i && graphemeLines.push(line);
if (largestWordWidth + reservedSpace > this.dynamicMinWidth) {
this.dynamicMinWidth =
largestWordWidth - additionalSpace + reservedSpace;
}
return graphemeLines;
};
fabric.util.object.extend(fabric.Textbox.prototype, {
_wrapLine: _wrapLine,
});
function enableScrolling() {
window.onscroll = function () {};
}
},
toObject: function (propertiesToInclude) {
// This function is used for serialize this object. (used for create json)
// not inlclude this.textObj and this.rectObj into json because when object will load from json, init fucntion of this class is called and it will assign this two object textObj and rectObj again.
var obj = this.callSuper(
"toObject",
[
"objectCaching",
"textObj",
"rectObj",
// ... property list that you want to add into json when this object is convert into json using toJSON() function. (serialize)
].concat(propertiesToInclude)
);
// delete objects array from json because then object load from json, Init function will call. which will automatically re-assign object and assign _object array.
delete obj.objects;
return obj;
},
});
fabric.StickyNote.async = true;
fabric.StickyNote.fromObject = function (object, callback) {
// This function is used for deserialize json and convert object json into button object again. (called when we call loadFromJson() fucntion on canvas)
return fabric.Object._fromObject("StickyNote", object, callback);
};
return new fabric.StickyNote(options);
};
//How to use
var options = {
width: 100,
height: 100,
originX: "center",
originY: "center",
};
var notes = StickyNotes(canvas, options);
canvas.add(notes);

Categories