KineticJS - dynamically create array of shapes and using events - javascript

I have managed to dynamically create an array of shapes, and they are nicely placed at different coordinates.
However, when I try to assign an event within that loop, the result of click is always the same. As if the click event is still referencing the last iteration of my loop.
What am I doing wrong? Thanks!
EDIT: Actually, re-produced this behaviour in an isolated environment:
var stage = new Kinetic.Stage({
container: 'container',
width: 1024,
height: 768
});
var layer = new Kinetic.Layer();
singleSegment=40;
for (var i = 0; i < 4; i++) {
depth=singleSegment+(singleSegment*i);
dotLabel = new Kinetic.Text({
x: depth,
y: depth,
text: "test"
});
dotLabel.on('click', function(evt){
console.log(this.x);
});
layer.add(dotLabel);
}
stage.add(layer);
How do I add different events to these four labels?

You are doing everything correct, I think. but because of this;
console.log(i);
The last value of i is array.length-1, and when it is clicked, it shows that value, which does not change because it's outside of loop when it is clicked.
This will show different value.
console.log(this.attrs.x);

I just had to deal with this same issue. I solved it by storing to each shape its location.
for (var axisItem=0;axisItem<innerCircleXAxisArray.length;axisItem++)
{
var arc = new Kinetic.Shape({
drawFunc: function(canvas){
var allAttrs = this.getAttrs();
var start = allAttrs['start'];
var end = allAttrs['end'];
var context = canvas.getContext();
context.strokeStyle = 'red';
var centerOfCanvasX = canvasWidth / 2;
var centerOfCanvasY = canvasHeight / 2;
context.translate(centerOfCanvasX, centerOfCanvasY);
context.lineWidth = 15;
context.beginPath();
context.arc(0, 0, 284, start , end, true);
canvas.stroke(this); // Fill the path
context.closePath();
context.translate(-centerOfCanvasX, -centerOfCanvasY);
},
fill: 'red',
stroke: 'red',
strokeWidth: 15
});
arc.setAttrs({'start': innerCircleXAxisArray[axisItem]['start'], 'end': innerCircleXAxisArray[axisItem]['end']});
layer.add(arc);
}
stage.add(layer);
When the object is created, I use setAttrs to store the object's location - a start and end angle since these are arcs, but it could just as easily be an x and y point. Then in the drawFunc I use getAttrs to retrieve that data and to draw the object.

Related

KineticJS, Paint like program, brush gaps

I am trying to do something like paint with KineticJS. I am trying to draw the color with circles that originate from the mouse position. However the eventlistener of the mouse position seems too slow and when I move the mouse too fast the circles drawn are far from each other resulting this:
I have seen people filling array with points drawing lines between them, but I thought thats very bad for optimization because after dubbing the screen too much the canvas starts lagging because it has too much lines that it redraws every frame. I decided to cancel the cleaning of the layer and I am adding new circle at the current mouse position and I remove the old one for optimization. However since Im not drawing lines on fast mouse movement it leaves huge gaps. I would be very grateful if anyone can help me with this.
Here is my code:
(function() {
var stage = new Kinetic.Stage({
container: 'main-drawing-window',
width: 920,
height: 750
}),
workplace = document.getElementById('main-drawing-window'),
layer = new Kinetic.Layer({
clearBeforeDraw: false
}),
border = new Kinetic.Rect({
stroke: "black",
strokeWidth: 2,
x: 0,
y: 0,
width: stage.getWidth(),
height: stage.getHeight()
}),
brush = new Kinetic.Circle({
radius: 20,
fill: 'red',
strokeWidth: 2,
x: 100,
y: 300
});
Input = function() {
this.mouseIsDown = false;
this.mouseX = 0;
this.mouseY = 0;
this.offsetX = 0;
this.offsetY = 0;
};
var input = new Input();
document.documentElement.onmousedown = function(ev) {
input.mouseIsDown = true;
};
document.documentElement.onmouseup = function(ev) {
input.mouseIsDown = false;
};
document.documentElement.onmousemove = function(ev) {
ev = ev || window.event;
// input.mouseX = (ev.clientX - workplace.offsetLeft);
// input.mouseY = (ev.clientY - workplace.offsetTop);
input.mouseX = (ev.offsetX);
input.mouseY = (ev.offsetY);
};
function DistanceBetweenPoints(x1, y1, x2, y2) {
return Math.sqrt(((x2 - x1) * (x2 - x1)) + ((y2 - y1) * (y2 - y1)));
}
var canvasDraw = setInterval(function() {
// console.log(input);
if (input.mouseIsDown) {
workplace.style.cursor = "crosshair";
var currentBrushPosition = brush.clone();
currentBrushPosition.setX(input.mouseX);
currentBrushPosition.setY(input.mouseY);
// var distance = DistanceBetweenPoints(brush.getX(), brush.getY(), currentBrushPosition.getX(), currentBrushPosition.getY());
// if (distance > brush.getRadius() * 2) {
// var fillingLine = new Kinetic.Line({
// points: [brush.getX(), brush.getY(), currentBrushPosition.getX(), currentBrushPosition.getY()],
// stroke: 'yellow',
// strokeWidth: brush.getRadius()*2,
// lineJoin: 'round'
// });
// // layer.add(fillingLine);
// }
layer.add(currentBrushPosition);
brush.remove();
brush = currentBrushPosition;
layer.draw();
// if (fillingLine) {
// fillingLine.remove();
// }
}
if (!input.mouseIsDown) {
workplace.style.cursor = 'default';
}
}, 16);
layer.add(border);
stage.add(layer);
})();
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Coloring Game</title>
<script src="https://cdnjs.cloudflare.com/ajax/libs/kineticjs/5.2.0/kinetic.min.js"></script>
</head>
<body>
<div id="main-drawing-window"></div>
<script type="text/javascript" src="./JS files/canvas-draw.js"></script>
</body>
</html>
Don't use individual Kinetic.Circles for each mousemove. Every Kinetic object is a "managed" object and that management takes up a lot of resources. KineticJS will slow to a crawl as the number of circles increases with every mousemove.
Instead, use a Kinetic.Shape and draw you circles onto the canvas with
// This is Pseudo-code since I haven't worked with KineticJS in a while
shapeContext.beginPath();
shapeContext.arc(mouseX,mouseY,20,0,Math.PI*2);
shapeContext.fillStrokeShape(this);
This will probably clear your problem, but if the mouse is moved very far in a single mousemove then you might have to draw a lineTo (instead of arc) between the last mouse point and the current far-away mouse point.

How to draw one object within another and never move outside of that object

i am working with fabric js and i want to draw two objects within canvas and when second object is drawn it should be drawn inside first object and never move outside the first object.I have tried this code
$(document).ready(function () {
var canvas = new fabric.Canvas('demo');
var rec = new fabric.Rect({
width: 200,
height: 100,
top: 10,
fill: 'blue',
});
rec.name = 'aaa';
canvas.add(rec);
canvas.on('object:moving', function (e) {
var h = rec.top;
var w = rec.width;
var obj = e.target;
obj.setCoords();
var rect = obj.getBoundingRect;
if(rect > h){
rect.top = Math.max(rect.top, rect.top-rect.getBoundingRect().top);
// alert(rect.top);
}
});
var text=new fabric.IText('Jayesh');
canvas.add(text);
});
Thanks in advance.Pls help

Animating multiple items in Paper.js while anchored to a path

I have five rectangles placed at different points along a circle like this - http://imgur.com/uVYkwl7.
Upon clicking any rectangle i want the circle to move to the left of the screen, gradually scaling down it's radius until the circle's center reaches x=0. I'd like the five rectangles to move along with the circle while its being scaled down and also adjust their own positions and scale on the circle so that they are within the view's bounds, like this - http://imgur.com/acDG0Aw
I'd appreciate any help on how to go about doing this. Heres my code for getting to the 1st image and animating the circle:
var radius = 300;
var center = view.center;
var circle = new Path.Circle({
center: view.center,
radius: radius,
strokeColor: 'black',
name: 'circle'
});
var path = new Path.Rectangle({
size: [230, 100],
fillColor: '#1565C0'
});
var rectText = ['Text 1',
'Text 2',
'Text 3',
'Text 4',
'Text 5'
];
var symbol = new Symbol(path);
var corners = [
new Point(center.x, center.y - radius),
new Point(center.x - radius, center.y - radius / 2),
new Point(center.x + radius, center.y - radius / 2),
new Point(center.x - radius, center.y + radius / 2),
new Point(center.x + radius, center.y + radius / 2)
];
var rectClicked = false;
var clickedRect = null;
var rectClick = function(event) {
rectClicked = true;
clickedRect = this;
};
function onFrame(event) {
// Your animation code goes in here
if (rectClicked) {
for (var i = 0; i < 1; i++) {
var item = project.activeLayer.children[i];
if (item.name == 'circle') {
if (item.position.x < 0) {
rectClicked = false;
} else {
item.position.x -= 10;
item.scale(1/1.01);
}
}
}
}
}
// Place the instances of the symbol:
for (var i = 0; i < corners.length; i++) {
var placedSymbol = symbol.place(corners[i]);
placedSymbol.onMouseDown = rectClick;
var rText = new PointText({
point: placedSymbol.bounds.topLeft + 20,
content: rectText[i],
fontSize: '20',
fillColor: 'white'
});
}
Paper.js provides rotations around a pivot out of the box.
var pivotPoint = new Point(10, 5);
circle.rotate(30,pivotPoint);
Here is the docs reference for this behaviour and here is a very basic Sketch example to illustrate this
The above snippet will rotate a circle(you can change this to rectangle in your case) by 30 degrees around a pivot point at coordinates 10,5 on the x/y axis.
Thus what you describe is certainly doable as long as the path that your elements will follow is always circular.
Bear in mind that in order for the pivot rotation to work the way you want them to you need to update the pivotPoint and reinitiate the rotation again.
Note: In case you want to move along an arbitrary shape instead of circular path, you should search for Paper.js animation-along-a-path which is something that I've seen been done before without much difficulty - e.g this simple Sketch by the creator of Paper.js himself.
The sketch I provided above is a basic example of rotation around a pivot point.
I'm dumping the Sketch code here in case the link goes dead:
//Create a center point
var centerCircle = new Path.Circle(paper.view.center, 100);
centerCircle.strokeColor = 'black';
centerCircle.dashArray = [10, 12];
//Create the circles
var circle1Radius = 30;
var circle1 = new Path.Circle((centerCircle.position-centerCircle.bounds.width/2)+circle1Radius, circle1Radius);
circle1.fillColor = '#2196F3';
var circle2Radius = 40;
var circle2 = new Path.Circle((centerCircle.position-centerCircle.bounds.width/2)+circle2Radius, circle2Radius);
circle2.fillColor = '#E91E63';
var circle3Radius = 40;
var circle3 = new Path.Circle((centerCircle.position-centerCircle.bounds.width/2)+circle2Radius, circle2Radius);
circle3.fillColor = '#009688';
var i=0;
var animationGap = 125; //how long to move before animating the next circle
var rotationSpeed = 2;
function onFrame(event) {
circle1.rotate(rotationSpeed,centerCircle.position);
if(i>animationGap)
circle2.rotate(rotationSpeed,centerCircle.position);
if(i>animationGap*2)
circle3.rotate(rotationSpeed,centerCircle.position);
i++;
}

fabricjs How can i keep fixed size on group elements while others scaling?

Hello I use fabricjs to play with the html canvas.
I create the canvas and i add group of objects on it.
On a group of objects, I need to keep fixed width & height for some objects while I scale the object.
I use the 'object:scaling' event to get the active object when it changes size, I read each object of the group and I assign element[i].set({'radius':5}) on the group objects that I want to be unchanged.
But the result is that , all the group object to resize.
I show you the snippet of the object:scaling event :
canvas.on('object:scaling',function(e){
var activeObject1 = e.target;
var elements = e.target._objects;
var count_elements = elements.length;
for(var i = 0; i < count_elements; i++) {
var type = elements[i].typeCircle;
if(type == "parts"){
//elements[i].set({"radius":8,"fill":"#abcde2","stroke":"#367827"});
//elements[i].set('radius',8);
/*elements[i].setWidth(16);
elements[i].setHeight(16);
elements[i].currentWidth = 16;
elements[i].currentHeight = 16;
elements[i].scaleX = 1;
elements[i].scaleY = 1;
console.log(elements[i]);
canvas.renderAll();*/
}
}
});
What should I write into the for loop to keep fixed size on some objects?
everything that I used above, they don't work except for the "fill":"#abcde2","stroke":"#367827"
If anyone has faced something similar on fabricjs, please let me know .
You must use setScaleX() and setScaleY() methods.
Here is an example...
var Rect = new fabric.Rect({
width: 200,
height: 200,
top: 100,
left: 100,
fill: 'rgba(255,0,0,0.5)',
stroke: '#000',
strokeWidth: 1,
});
var Circle = new fabric.Circle({
left: 100,
top: 100,
fill: '#FF00FF',
stroke: 'red',
radius: 100,
opacity: 1,
});
var Group = new fabric.Group([Rect, Circle])
canvas.add(Group)
canvas.on({
'object:scaling': onChange
})
function onChange(obj) {
var circle = obj.target.item(1),
group = obj.target,
scaleX = circle.width / group.getWidth(),
scaleY = circle.height / group.getHeight();
circle.setScaleX(scaleX);
circle.setScaleY(scaleY);
}
JSFIDDLE
Using fabricjs2 to stop scaling of a text item I have modified Rafik Avtoyan's function to work well. I have locked Y scaling for my group so you will have to add this if required.
function handleScalingEvent(obj) {
var text = obj.target.item(1),
group = obj.target,
scaleX = group.width / (group.width * group.scaleX);
text.set('scaleX', scaleX);
}
Best way to make the objects the same size is to divide desired width by object width. For example, if you want all added objects to be 256px the code will look this way:
function selectImage(imagePath){
fabric.Image.fromURL(imagePath, function(oImg) {
canvas.add(oImg);
let zoom = 256 / oImg.width;
oImg.set({ scaleX: zoom, scaleY: zoom, });
oImg.center();
});
}
In the case when the image will be 64px it will scale it by 4 to 256px and in case the image is 512px it will scale it by 0.5 which will make the image smaller to 256px.

Making JS "this" work in prototype function

Being new to Javascript, my understanding of this is a bit shaky. I've read a few articles and SO posts and learned quite a bit, but I'm having issues implementing some code. I'm using the KineticJS library to implement some canvas functions. I was working with a fiddle and was able to get an image to resize with the mouse control, however, when I put the code into a prototype function, resizing no longer works.
Canvas.prototype.addImg = function(){
var self = this;
var image;
var imageObj = new Image();
imageObj.onload = function() {
image = new Kinetic.Image({
x: 200,
y: 50,
image: imageObj,
width: 106,
height: 118,
draggable: false
});
self.backgroundLayer.add(image);
self.stage.add(self.backgroundLayer);
};
imageObj.src = 'http://www.html5canvastutorials.com/demos/assets/yoda.jpg';
var circle1 = new Kinetic.Circle({
x: 150,
y: 150,
radius: 10,
fill: 'red',
stroke: 'black',
strokeWidth: 4,
draggable: false
});
circle1.isResizing = false;
circle1.on("click", function (e) {
// toggle resizing true/false
var isResizing = !this.isResizing;
this.setDraggable(isResizing);
self.backgroundLayer.setDraggable(!isResizing);
this.setFill((isResizing ? "green" : "red"));
this.isResizing = isResizing;
self.backgroundLayer.draw();
});
circle1.on("dragmove", function () {
if (this.isResizing) {
var pos = this.getPosition();
var x = pos.x;
var y = pos.y;
var rectX = image.getX();
var rectY = image.getY();
image.setSize(x - rectX, y - rectY);
self.backgroundLayer.draw();
}
});
self.backgroundLayer.add(circle1);
self.backgroundLayer.draw();
}
I am assuming the problem lies within the use of this in the function, but I'm not sure how to fix the problem. Could somebody tell me what the problem is and how I would fix this?
I can see a problem with the line that is setting the size of image:
image.setSize(x - rectX, y - rectY);
For example if x is 100 and rectX is 200, you end up with a width of '-100', which is not legal.
In the fiddle you provided, when I use constant values, the image re-sizes perfectly:
rect.setSize(200, 200);
Modified fiddle.
The solution I've come upon, is that it is not an issue of this, but the setSize() method. For some reason or another, it is not working in my local application. When I replace this for setWidth() and setHeight(), everything works the way that it should.

Categories