Related
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
I am inspired with KonvaJS tutorial Modify Curves with Anchor Points to make my own example which is to create multiple custom arrows.
on click on the selectionBox create an anchor.
on the creation of the third anchor create the curved arrow.
on the fourth click reset all variables in order to draw a new curved arrow.
var width = window.innerWidth;
var height = window.innerHeight;
// globals
var selectionBoxLayer, curveLayer, lineLayer, anchorLayer, quad, bezier;
function updateDottedLines() {
var q = quad;
var quadLine = lineLayer.get('#quadLine')[0];
quadLine.setPoints([q.start.attrs.x, q.start.attrs.y, q.control.attrs.x, q.control.attrs.y, q.end.attrs.x, q.end.attrs.y]);
lineLayer.draw();
}
function buildAnchor(x, y) {
var anchor = new Konva.Circle({
x: x,
y: y,
radius: 20,
stroke: '#666',
fill: '#ddd',
strokeWidth: 2,
draggable: true
});
// add hover styling
anchor.on('mouseover', function() {
document.body.style.cursor = 'pointer';
this.setStrokeWidth(4);
anchorLayer.draw();
});
anchor.on('mouseout', function() {
document.body.style.cursor = 'default';
this.setStrokeWidth(2);
anchorLayer.draw();
});
anchor.on('dragend', function() {
drawCurves();
updateDottedLines();
});
anchorLayer.add(anchor);
anchorLayer.draw();
return anchor;
}
function drawCurves() {
var context = curveLayer.getContext();
var arrowLine = new Konva.Shape({
sceneFunc: function(context){
debugger;
// draw quad
context.beginPath();
context.moveTo(quad.start.attrs.x, quad.start.attrs.y);
context.quadraticCurveTo(quad.control.attrs.x, quad.control.attrs.y, quad.end.attrs.x, quad.end.attrs.y);
//Draw Arrow Head
var headlen = 10; // length of head in pixels
var angle = Math.atan2(quad.end.attrs.y - quad.control.attrs.y, quad.end.attrs.x - quad.control.attrs.x);
context.lineTo(quad.end.attrs.x-headlen*Math.cos(angle-Math.PI/6), quad.end.attrs.y-headlen*Math.sin(angle-Math.PI/6));
context.moveTo(quad.end.attrs.x, quad.end.attrs.y);
context.lineTo(quad.end.attrs.x- headlen*Math.cos(angle+Math.PI/6), quad.end.attrs.y-headlen*Math.sin(angle+Math.PI/6));
context.fillStrokeShape(this);
},
stroke: 'black',
strokeWidth: 4
});
curveLayer.add(arrowLine);
curveLayer.draw();
}
var stage = new Konva.Stage({
container: 'container',
width: width,
height: height
});
selectionBoxLayer = new Konva.Layer();
anchorLayer = new Konva.Layer();
lineLayer = new Konva.Layer();
// curveLayer just contains a canvas which is drawn
// onto with the existing canvas API
curveLayer = new Konva.Layer();
var quadLine = new Konva.Line({
dash: [10, 10, 0, 10],
strokeWidth: 3,
stroke: 'black',
lineCap: 'round',
id: 'quadLine',
opacity: 0.3,
points: [0, 0]
});
// add dotted line connectors
lineLayer.add(quadLine);
quad = {};
// keep curves insync with the lines
anchorLayer.on('beforeDraw', function() {
if(quad.start && quad.control && quad.end){
drawCurves();
updateDottedLines();
}
});
var selectionBoxBackground = new Konva.Rect({
x: 0,
y: 0,
height:stage.height(),
width: stage.width(),
fill: 'transparent',
draggable: false,
name: 'selectionBoxBackground'
});
selectionBoxLayer.add(selectionBoxBackground);
var clickCounter = 0;
selectionBoxBackground.on("click", function(){
clickCounter +=1;
var mousePos = {};
switch(clickCounter){
case 1:
mousePos = stage.getPointerPosition();
quad.start = buildAnchor(mousePos.x, mousePos.y);
break;
case 2:
mousePos = stage.getPointerPosition();
quad.control = buildAnchor(mousePos.x, mousePos.y);
break;
case 3:
mousePos = stage.getPointerPosition();
quad.end = buildAnchor(mousePos.x, mousePos.y);
drawCurves();
updateDottedLines();
break;
default:
clickCounter = 0;
quad = {};
anchorLayer.destroyChildren();
anchorLayer.draw();
}
});
stage.add(curveLayer);
stage.add(lineLayer);
stage.add(selectionBoxLayer);
stage.add(anchorLayer);
body {
margin: 0;
padding: 0;
overflow: hidden;
background-color: #F0F0F0;
}
<script src="https://cdn.rawgit.com/konvajs/konva/0.11.1/konva.min.js"></script>
<body>
<div id="container"></div>
</body>
P.S Please note when I write in the browser console curveLayer.children, it will bring all created curved arrows.
Hint: I think on the creation of new Shape() the values of all created shapes will be changed to the new one.
I don't know what I am missing.
I have a very strange behaviour of my script: only from time to time (seldom 3-4 times at one time right after each other, but more likely every 7th to 150th trial) the skript loads, but I only see a white canvas and get the error message:
Uncaught TypeError: Cannot read property 'getParent' of
undefinedKonva.Util.addMethods.add # konva.min.js:44draw #
floorplansurvey.php:950(anonymous function) #
floorplansurvey.php:985images.(anonymous function).onload #
floorplansurvey.php:390
On reload it often works again...
I just have no idea at all what is happening here, the bug can't be forced/reproduced, I thank you so much, if you have anything you can help me with. Even a strategy for a clearer analysis would be helpful, sorry for being that unspecific and the long code
edit: I've made a jsfiddle under:
https://jsfiddle.net/17548hmv/1/
run it several times, until the error occures
these are the code snippets at:
# floorplansurvey.php:390:
function loadImages(sources, draw) {
//window.location.reload(true);
var images = {};
var loadedImages = 0;
var numImages = 0;
// get num of sources
for(var src in sources) {
numImages++;
}
for(var src in sources) {
images[src] = new Image();
images[src].onload = function() {
if(++loadedImages >= numImages) {
draw(images);
}
};
images[src].src = sources[src];
}
delete (loadedImages);
delete (numImages);
};
# floorplansurvey.php:985images.(anonymous function).onload:
loadImages(sources, function(images) {
draw(images);
});
# floorplansurvey.php:950(anonymous function):
bglayer.add(plan);
# konva.min.js:44draw:
I dont understand this one myself
add:function(t)
{if(arguments.length>1)
{for(var e=0;e<arguments.length;e++)
this.add(arguments[e]);
return this}if(t.getParent())return t.moveTo(this),this;
var n=this.children;
return
Var sources is defined like:
var sources = {
Cd: './graphics/Cd.png',
Cu: './graphics/Cu.png',
Ca: './graphics/Ca.png',
Cs: './graphics/Cs.png',
Cc: './graphics/Cc.png',
Ed: './graphics/Ed.png',
Eu: './graphics/Eu.png',
Ea: './graphics/Ea.png',
Es: './graphics/Es.png',
Ec: './graphics/Ec.png',
Hd: './graphics/Hd.png',
Hu: './graphics/Hu.png',
...and so on
and this is the function draw:
function draw(images) {
for (i=0, len=showdatax.length; i<=len-1; i++)
{
//window.location.reload(true);
var gridcell = new Konva.Rect({
x: parseInt((imagesizeX - showdatax[i])*scalar-(gridcellsize/2)),
y: parseInt((imagesizeY - showdatay[i])*scalar-(gridcellsize/2)),
offset: [0, 0],
width: gridcellsize,
height: gridcellsize,
//fill: 'white',
//stroke: 'grey',
//strokeWidth: 2,
draggable: false,
id: showdatav[i]
});
gridlayer.add(gridcell);
}
//defaultsettiongs
var init = new Array();
init['x'] = new Array();
init['y'] = new Array();
init['x']['c'] = 0*scalar;
init['y']['c'] = 170*scalar;
init['x']['e'] = 0*scalar;
init['y']['e'] = 320*scalar;
init['x']['h'] = 0*scalar;
init['y']['h'] = 470*scalar;
init['x']['l'] = 0*scalar;
init['y']['l'] = 620*scalar;
init['x']['s'] = 0*scalar;
init['y']['s'] = 770*scalar;
init['x']['w'] = 0*scalar;
init['y']['w'] = 920*scalar; var step = 0;
var count = new Array (
'c','e','h','l','s','w');
count['c'] = 0;
count['e'] = 0;
count['h'] = 0;
count['l'] = 0;
count['s'] = 0;
count['w'] = 0;
var starttime = new Date();
var loghistory = '';
//drag event functions
function mouseoverbox (box,active)
{
writeMessage('Click and hold the left mouse button and pull this activity icon to the floorplan.');
box.setFillPatternImage(active);
box.shadowColor('blue');
box.moveTo(templayer);
badgelayer.draw();
templayer.draw();
}
function dragstarttouchstartbox (box,pos,kind)
{
//count[kind]++;
writeMessage('dragstart' + count[kind]);
var boxx = box.x;
box.x(pos.x-1.5*gridcellsize);
var boxy = box.y;
box.y(pos.y-1.5*gridcellsize);
var boxrshadowoffsetx = box.shadowOffset();
box.shadowOffset({x:0.25*gridcellsize,y:0.25*gridcellsize});
box.moveTo(templayer);
badgelayer.draw();
templayer.draw();
}
function dragmovebox (box,pos,kind,success,active,caution)
{
writeMessage('Your outside of the floorplan. Dropping the activity icon will reset it to its initial position.');
box.moveTo(templayer);
var boxx = box.x;
box.x(pos.x-1.5*gridcellsize);
var boxy = box.y;
box.y(pos.y-1.5*gridcellsize);
var shape = gridlayer.getIntersection(pos);
if (shape)
{
if (!badgelayer.getIntersection(pos))
{
writeMessage('Release the mouse button to place this activity icon on position (' + shape.x() + '|' + shape.y() + ')');
box.x(shape.x()-gridcellsize);
box.y(shape.y()-gridcellsize);
box.setFillPatternImage(success);
box.shadowColor('green');
badgelayer.draw();
}
else
{
writeMessage('Position (' + shape.x() + '|' + shape.y() + ') is in use!!! Can\'t allocate second activiy icon here.');
var boxx = box.x;
box.x(pos.x-1.5*gridcellsize);
var boxy = box.y;
box.y(pos.y-1.5*gridcellsize);
box.setFillPatternImage(caution);
box.shadowColor('red');
badgelayer.draw();
}
}
else
{
box.setFillPatternImage(active);
box.shadowColor('blue');
templayer.draw();
badgelayer.draw();
}
}
function dragendtouchendbox (box,pos,kind,caution,defaultimage)
{
var shape = gridlayer.getIntersection(pos);
box.moveTo(badgelayer);
templayer.draw();
if (shape && !(badgelayer.getIntersection(pos)))
{
writeMessage('Activity icon successfully placed at position (' + shape.x() + '|' + shape.y() + ').');
box.shadowOffset({x:0,y:0});
box.shadowColor('green');
}
else
{
box.shadowColor('red');
box.setFillPatternImage(caution);
badgelayer.draw();
var fromouttween = new Konva.Tween
({
node: box,
x: init['x'][kind]+imagesizeX*scalar+ gridcellsize,
y: init['y'][kind],
easing: Konva.Easings['EaseOut'],
duration: 0.3
});
if (!shape)
{
writeMessage('Please drop the activity icons inside the floorplan.');
}
else if (badgelayer.getIntersection(pos))
{
writeMessage('Please don\'t drop the activity icons onto a used place.');
}
fromouttween.play();
box.shadowColor('black');
box.setFillPatternImage(defaultimage);
box.shadowOffset({x:0,y:0});
badgelayer.draw();
pos.x=-100;
pos.y=-100;
}
count['kind']++;
step++;
writeResultToForm(pos,count,kind,step,loghistory,starttime);
badgelayer.draw();
templayer.draw();
//writeMessage('dragend');
}
function mouseoutbox (box,defaultimage)
{
box.setFillPatternImage(defaultimage);
box.moveTo(badgelayer);
box.shadowColor('black');
if (!validateForm(false))
{
writeMessage('There are still activity icons left to place.');
}
else
{
writeMessage('All activity icons are placed successfully. You can click "continue" now or change your placements.');
}
badgelayer.draw();
templayer.draw();
}
function alertbox (box,caution)
{
box.setFillPatternImage(caution);
box.shadowcolor('red');
badgelayer.draw;
}
//cbox and text
var textc = new Konva.Text({
x: init['x']['c']+imagesizeX*scalar+gridcellsize*5,
y: init['y']['c']+gridcellsize,
text: 'Cooking',
align: 'left',
width: badgetextwidth
});
var boxc = new Konva.Rect({
x: init['x']['c']+imagesizeX*scalar+gridcellsize,
y: init['y']['c'],
offset: [0, 0],
width: 3*gridcellsize,
height: 3*gridcellsize,
fillPatternImage: images.Cd,
fillPatternScaleX: scalar,
fillPatternScaleY: scalar,
stroke: 'black',
strokeWidth: 4,
draggable: true,
cornerRadius: gridcellsize/2,
shadowColor: 'black',
shadowBlur: 10,
shadowOffset: {x : 0, y : 0},
shadowOpacity: 0.5
});
var boxcd = new Konva.Rect({
x: init['x']['c']+imagesizeX*scalar+gridcellsize,
y: init['y']['c'],
offset: [0, 0],
width: 3*gridcellsize,
height: 3*gridcellsize,
fillPatternImage: images.Cu,
fillPatternScaleX: scalar,
fillPatternScaleY: scalar,
stroke: 'grey',
strokeWidth: 2,
draggable: false,
cornerRadius: gridcellsize/2,
});
boxc.on('mouseover ', function() {
mouseoverbox (this,images.Ca);
});
boxc.on('dragstart', function() {
var pos = stage.getPointerPosition();
dragstarttouchstartbox (this,pos,'c');
});
boxc.on('dragmove', function () {
var pos = stage.getPointerPosition();
dragmovebox(this,pos,'c',images.Cs,images.Ca,images.Cc);
});
boxc.on('dragend', function() {
var pos = stage.getPointerPosition();
dragendtouchendbox (this,pos,'c',images.Cc,images.Cd);
});
boxc.on('mouseout ', function() {
mouseoutbox (this,images.Cd)
});
boxc.on('foo', function() {
alertbox (this,images.Cd)
});
//ebox and text
var texte = new Konva.Text({
x: init['x']['e']+imagesizeX*scalar+gridcellsize*5,
...and so on for all the boxes addes here (5 times as long):
defaultlayer.add(boxcd);
badgelayer.add(boxc);
defaultlayer.add(textc);
defaultlayer.add(boxed);
badgelayer.add(boxe);
defaultlayer.add(texte);
defaultlayer.add(boxhd);
badgelayer.add(boxh);
defaultlayer.add(texth);
defaultlayer.add(boxld);
badgelayer.add(boxl);
defaultlayer.add(textl);
defaultlayer.add(boxsd);
badgelayer.add(boxs);
defaultlayer.add(texts);
defaultlayer.add(boxwd);
badgelayer.add(boxw);
defaultlayer.add(textw);
stage.add(bglayer);
stage.add(gridlayer);
stage.add(defaultlayer);
stage.add(badgelayer);
stage.add(templayer);
}
OK, sorry that it took me so long:
the solution wasn't that easy to find. I found that the script was loaded asynchronously and the png wasn't yet available by using chromes timeline analysis. so i went to my code and had to distinguish between the things, that really should be in the draw function and which not. i added the plan to the array of images (sources) that is loaded (src= ...) before executing draw() and then added
sources.onload= loadImages(sources, function(images) {
draw(images);
});
at the end of my file... no it works without any problem. konva.js had nothing to do with it and is up to now (I'm almost ready) like designed for my project
thanks for the tip #lavrton, it took me on the right way
and you can just put like this:
var imageObj = new Image();
imageObj.src = 'http://localhost:8000/plugins/kamiyar/logo.png';
imageObj.onload = function() {
var img = new Konva.Image({
image: imageObj,
x: stage.getWidth() / 2 - 200 / 2,
y: stage.getHeight() / 2 - 137 / 2,
width: 200,
height: 137,
draggable: true,
stroke: 'blue',
strokeWidth: 1,
dash: [1,5],
strokeEnabled: false
});
layer.add(img);
layer.draw();
};
sorry for my bad English ;)
var stages = new Array() ;
var limites = new Array() ;
numStage=0;
r = {
'x':65,
'y':120,
'xwidth':335,
'yheight':210
};
limites.push(r);
stages[numStage] = new Kinetic.Stage({
container: 'cmg_canvas_'+numStage,
width: 450,
height: 450
});
//creation image
obj = new Image();
obj.src = 'http://i.imgur.com/zFZgKuS.jpg';
image = new Kinetic.Image({
image: obj,
width: 450,
height: 450
});
// add image to calque
layer = new Kinetic.Layer();
layer.add(image);
stages[numStage].add(layer); //add image to canvas
layer = new Kinetic.Layer();
//set limit x y h l
/*var rect = new Kinetic.Rect({
name: 'limite',
x: limites[numStage].x,
y: limites[numStage].y,
width: limites[numStage].xwidth,
height: limites[numStage].yheight,
stroke: 'black',
strokeWidth: 0.5
});*/
//layer.add(rect);// add to canvas
stages[numStage].add(layer);
$('.cmg_text').live('blur', function(){
idText = 'cmg_line0';
numStage = 0;
drawTextPath(numStage, idText,$(this).val(),50,22,numStage);
//text = getText(this).text;
});
function getSVG(x,y,w,verif) {
halfw = parseFloat((w/2).toFixed(2));
x1 = parseFloat((halfw/2).toFixed(2));
x2 = parseFloat(halfw + x1);
if(parseInt(verif))
{
y1 = parseFloat(y) * 2 +18;
y2 = parseFloat(y) * 2 +18;
}
else
{
y1 = -18;
y2 = -18;
}
str = 'M '+x+','+y+' C '+x1+','+y1+' '+x2+','+y2+' '+w+','+y;
return str;
}
function drawTextPath(numStage, textId,text,valueSlider, newFontSize,numStage){
//'M 0,115 C42,-18 126,-18 165,115';
//'M 0,115 C45,230 180,230 180,115';
var arcOnly = 0;
if(textId == 'cmg_line0')
{
console.log('limites[numStage].yheight/2'+limites[numStage].yheight/2);
console.log('limites[numStage].xwidth'+limites[numStage].xwidth);
svg = getSVG(0,valueSlider,valueSlider*6.3,0);
arcOnly = 0;
}
//alert(svg);
console.log(parseFloat(limites[numStage].y));
console.log(parseFloat(arcOnly));
console.log(parseFloat(limites[numStage].y - arcOnly));
var layer = new Kinetic.Layer({name:'textPathLayer',draggable:true});
var textpath = new Kinetic.TextPath({
name:'TextPath',
id: textId,
//x: 0,
//x: limites[numStage].x + limites[numStage].xwidth/2,
//y: 0,
//y: limites[numStage].y + limites[numStage].yheight/2,
x: limites[numStage].x ,
y: limites[numStage].y + limites[numStage].yheight/2,
fill: '#000',
fontSize: newFontSize,
fontFamily: 'Arial',
text: text,
//offsetX:0,
//offsetY:0,
draggable: true,
dragBoundFunc: function(pos){
p = textParams(this, pos);
return {x: p.newX, y: p.newY};
},
data: svg
});
//
layer.add(textpath);
stages[numStage].add(layer);
//layer.moveToTop();
//layer.draw();
//stages[0].draw();
}
<script src="http://cdnjs.cloudflare.com/ajax/libs/kineticjs/4.6.0/kinetic.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js"></script>
<div id="cmg_canvas_0"></div>
<input type='text' class='cmg_text' />
I have to draw a draggable textpath with kineticjs, with text given by an input text, the action is triggered in blur.
I have a stage that contain 3 layers;
Layer for the background, and one layer for the textpath.
My problem that the draggable in the textpath is not working,
i tried to set the text layer in the top, but i didn't get it draggable.
This is my jsfiddle
I have a doubt of inner layer problem.
Thanks in advance.
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);