I have a problem getting my KineticJS animations to stop().
I have three KineticJS image objects on the same layer, and I have added a KineticJS animation to each of those KineticJS image objects. However only one of the image objects shows any animation. It also won't stop animating in response to anim.stop(), unless I stop() the animations for all three objects (including the ones which are not actually visually animating).
My question is: is it even possible to add multiple independent animations on seprate objects/images/shapes to a single layer and still be able to start() / stop() each animation individually? Or do I need to create an individual layer for each KineticJS image object?
In a nutshell (hacked down version), my code is as follows:
stage = new Kinetic.Stage({container: "container", width: windowWidth, height: windowHeight});
layer = new Kinetic.Layer();
var kinObjArr = [];
for (var i=0; i < 3; i++) {
kinObjArr[i] = new Kinetic.Image({image: myHTMLImage});
kinObjArr[i].anim = new Kinetic.Animation({
func: function(frame) {
kinObjArr[i].setX(30 * Math.sin(frame.time * 2 * Math.PI / 500) + 100);
},
node: layer
});
kinObjArr[i].anim.start();
kinObjArr[i].on('touchstart', function(){
this.anim.stop(); // <----- Doesn't stop
layer.draw();
});
} // for
stage.add(layer);
Basically only the last KineticJS image in the list will be animated and it can only be stopped when all 3 images have been touched/clicked.
classic closure problem. you need to use an anonymous function to induce scope, or break out all of your logic into its own function. Here's an article about Javascript closure:
How do JavaScript closures work?
KineticJS supports an unlimited number of Animations (until of course, you run out of memory)
Thanks for the response Eric. I read that article and it bent my head out of shape, but I think I got it in the end. I choose your second option and it works like a charm:
(again, this is a hacked version of my actual code, haven't tested this snippet)
stage = new Kinetic.Stage({container: "container", width: windowWidth, height: windowHeight});
layer = new Kinetic.Layer();
var kinObjArr = [];
function myFunc(i) {
kinObjArr[i] = new Kinetic.Image({image: myHTMLImage});
kinObjArr[i].anim = new Kinetic.Animation({
func: function(frame) {
kinObjArr[i].setX(30 * Math.sin(frame.time * 2 * Math.PI / 500) + 100);
},
node: layer
});
kinObjArr[i].anim.start();
kinObjArr[i].on('touchstart', function(){
this.anim.stop(); // <----- Stops now
layer.draw();
});
} // function myFunc
for (var i=0; i < 3; i++) {
myFunc(i);
} // for
stage.add(layer);
Related
I am using easeljs to create a stage and then i am putting tiles on the stage on random locations. When the user clicks on a tile, the tile must be removed from the stage.
There are two problems that i am facing. First was that the mouse event was not working. The code "tile.onPress = (tileOnPress).bind(this);" so i used the addEventListener method instead. Now although the function is being called as i am getting the output on the console, the tile is not being removed from the stage.
Here is the code:
c99.Game = (function(){
function Count99Game() {
console.log("Count 99 game starts");
this.canvas = document.getElementById('game-canvas');
this.stage = new createjs.Stage(this.canvas);
var totalTiles = 10;
var tileOnPress = function(event) {
console.log("Pressed");
this.stage.removeChild(event.target);
this.stage.update();
};
for (var i = totalTiles; i > 0; i--) {
var tile = new c99.Tile(i);
this.stage.addChild(tile);
tile.x = Math.random()*(this.canvas.width - tile.width);
tile.y = Math.random()*(this.canvas.height - tile.height);
//tile.onPress = (tileOnPress).bind(this);
tile.addEventListener("click", tileOnPress.bind(this));
}
this.stage.update();
}
return Count99Game;
})();
I would appreciate if someone could tell me why "tile.onPress = (tileOnPress).bind(this);" isn't working and also what changes need to be done in the tileOnPress function so that the tile that is pressed can be removed from the stage.
The complete code can be found at https://github.com/ZANTGames/count99/blob/master/js/count99-game.js
Try this
this.stage.removeChild(event.target.parent)
This is because event.target is shape and its parent is tile and basically you want to remove the tile so this is working
For your future reference I have found it from this documentation
http://www.createjs.com/docs/easeljs/classes/Stage.html
I try to program an online version of Kara (the little ladybug you can program to find the leafs laying in the grid). For that, I programmed some functions to draw the needed objects into the grid. Those functions are:
Kara.prototype.draw_object = function (x, y, asset) {
pos = this.calc_pos(x,y);
var img = new Image();
img.onload = function() {
c.drawImage(img, pos.x, pos.y, 40, 40);
}
img.src = asset;
}
Kara.prototype.draw_tree = function(x,y) {
this.draw_object(x,y,this.assets.asset_tree);
}
Kara.prototype.draw_leaf = function(x,y) {
this.draw_object(x,y,this.assets.asset_leaf);
}
Kara.prototype.draw_mush = function(x,y) {
this.draw_object(x,y,this.assets.asset_mush);
}
The asset object is defined in the init functions and point to png files like img/tree.png. calc_pos calculates the absolute x and y position depending on the position in the grid.
There is a lot work to do. Because I would like to draw some sample content every time the browser reloads, I wrote a sample function:
Kara.prototype.sample = function() {
kara = this;
kara.draw_kara(1,1); // Not listed above. Draws the little laddybug
setTimeout(function() {
kara.draw_tree(2,1);
}, 4);
setTimeout(function() {
kara.draw_mush(3,1);
}, 8);
setTimeout(function() {
kara.draw_leaf(4,1);
}, 12);
}
Without the timeout everything is drawn to [4][1]. Because it worked when I typed every single command in the Developer console I tried to find the root of the bug by placing some waiting time between the draw commands.
If there are 0.5 seconds between the commands everything works perfect. With 4 miliseconds it could work properly or some objects are placed above each other.
Does anybody know an issue where this occured or can give me a hint for the origion of this bug?
First thing, excuse my absolute lack of knowledge in JavaScript. I'm looking for the best approach for this problem, but after 3 days I think it may be wrong.
I need to write some code to draw moving rectangles in different rows in a canvas. In the future I will need to detect when 2 rectangles are in the same X coordinate, so it's important to keep track of the X values. Coming from Java I thought the best would be to create some rectangle "objects" and with each instance a draw method.
What is causing me trouble is that I thought about calling the draw function with setInterval(), but it appears that every time the function draw is called, the values are not the same.
This is my definition of the Rectangle class:
function Rectangle(x,y,width,height) {
var x=x;
var y= y;
var width= width;
var height= height;
this.getX = function(){
return x;
}
this.setX = function (value) {
x = value;
}
this.getY = function(){
return y;
}
this.setY = function (value) {
y = value;
}
this.getWidth = function(){
return width;
}
this.setWidth = function (value) {
width = value;
}
this.getHeight = function(){
return height;
}
this.setHeight = function (value) {
height = value;
}
this.draw = function(){
if(this.getX() <=canvas.width){
clearContext(this.getX() - 30,this.getY(),this.getWidth(),this.getHeight());
var temp= this.getX()+1;
this.setX(temp);
ctx.fillRect(temp,this.getY(),this.getWidth(),this.getHeight());
}else{
clearInterval(this.draw(),speed);
}
}
}
Then I have a function formSubmit where I create the Rectangles instances when the button is pressed and call respectively the function draw with setInterval():
function formSubmit(){
number=parseInt(document.getElementById("nummerT").value);
rate=parseInt(document.getElementById("rate").value);
speed=parseInt(document.getElementById("speed").value);
confirm(speed);
myRectangle= new Rectangle(0,0,30,30);
myRectangle2 = new Rectangle(0,60,30,30);
setInterval(myRectangle.draw(),speed);
}
The problem is that setInterval(myRectangle.draw(),speed); doesn't do what you think it does. You are calling draw one time, and then the interval is calling the result of draw. You'll need something like:
interval = setInterval(function() {
myRectangle.draw();
}, speed);
You'll note, I set the return value of setInterval to a variable because that is how you'll clear the interval later. You just call
clearInterval(interval);
I don't know if that's going to solve all your problems, but it should at least you get to something that will give you some more information.
A Demo: http://jsfiddle.net/m1erickson/SdPPa/
Your instinct of creating rectangle objects to define what is drawn on the canvas is indeed the common standard.
Unlike Java, JavaScript does not have true classes, but you can create a pseudo-class as you have done in your question.
At it's simplest a Rectangle "class" needs these properties:
x, y
width, height
If you want to animate those rectangles on the canvas you might add:
velocityX, directionY
velocityY, direction
These new properties allow you to move the rectangles like this:
this.x += this.directionX * this.velocityX;
this.y += this.directionY * this.velocityY;
Hint: Html5 now has an excellent animation handler: requestAnimationFrame. You might want to use this instead of setInterval or setTimeout because it gives better performance by integrating itself with the refresh cycle of the browser.
Hint: JavaScript is a prototypal language so you can extend your "class" with methods. The best way to add methods to a "class" is to add them to the classes prototype. That way the methods are created once and shared by all instances of the class rather than having every method recreated on every instance.
So a method to allow a rectangle instance to draw itself to the canvas might look like this:
// draw this rect on the canvas
Rectangle.prototype.render=function(){
ctx.fillStyle=this.color;
ctx.fillRect(this.x,this.y,this.width,this.height);
return(this);
}
Hint: JavaScript "class" methods can be chained if you always return(this). A good use of chaining might be calling a move method on an instance and then chaining on the render method.
rectangle1.move().render();
There's lots to learn about javascript "classes".
Here's annotated code to start with:
Good luck with your project!
<!doctype html>
<html>
<head>
<link rel="stylesheet" type="text/css" media="all" href="css/reset.css" /> <!-- reset css -->
<script type="text/javascript" src="http://code.jquery.com/jquery.min.js"></script>
<style>
body{ background-color: ivory; }
canvas{border:1px solid red;}
</style>
<script>
$(function(){
// canvas related variables
var canvas=document.getElementById("canvas");
var ctx=canvas.getContext("2d");
// an array to hold all rectangle objects
var rectangles=[];
// a rectangle pseudo-class (javascript does not have actual classes)
function Rectangle(stdProperties) {
addProperties(this,stdProperties);
this.color=randomColor();
};
//
// Add methods that apply to all instance rectangles
// to Rectangle.prototype so those methods are
// created once for all instances instead of
// repeatedly for every instance.
//
// set x,y,width,height of this rectangle
Rectangle.prototype.init=function(x,y,width,height){
this.x=x;
this.y=y;
this.width=width;
this.height=height;
return(this);
};
// move this rectangle by its preset delta-x and delta-y
Rectangle.prototype.move=function(){
var maxRight=canvas.width-this.width;
var maxBottom=canvas.height-this.height;
this.x+=this.directionX*this.velocityX;
if(this.x<0){ this.x=0; this.directionX*=-1}
if(this.x>maxRight){ this.x=maxRight; this.directionX*=-1}
this.y+=this.directionY*this.velocityY;
if(this.y<0){ this.y=0; this.directionY*=-1}
if(this.y>maxBottom){ this.y=maxBottom; this.directionY*=-1}
return(this);
};
// draw this rect on the canvas
Rectangle.prototype.render=function(){
ctx.fillStyle=this.color;
ctx.fillRect(this.x,this.y,this.width,this.height);
return(this);
}
// create a new rectangle object from the Rectangle "class"
function newRect(x,y,width,height){
// define default properties for Rectangle
var DefaultRectangleProperties={
x:0,y:0,width:10,height:10,
velocityX:1,velocityY:1,directionX:1,directionY:1,
color:"black",
}
// new-up a Rectangle
var rect = new Rectangle(DefaultRectangleProperties);
// set the x,y,width,height & draw it on the canvas
rect.init(x,y,width,height).render();
// return(this) to allow chaining
return(rect);
}
// TESTING
// create 5 rectangles with some randomness
for(var i=0;i<5;i++){
var rect=newRect(Math.random()*200,Math.random()*200,40,50);
rect.velocityX=Math.random()*2;
rect.velocityY=Math.random()*3;
rectangles.push(rect);
}
// animate the rectangles using requestAnimationFrame
animate();
// the animation loop
function animate(t){
// request another animation frame
requestAnimationFrame(animate);
// clear the canvas
// move all the rectangles by their preset distance
// redraw all the rectangles
ctx.clearRect(0,0,canvas.width,canvas.height);
for(var i=0;i<rectangles.length;i++){
rectangles[i].move().render();
}
}
///////////////////////////////////
// Utilities
///////////////////////////////////
// create getters/setters on the specified object
// using the supplied properties object
//
function addProperties(object,properties){
for (var i in properties) {
(function(i) {
Object.defineProperty(object, i, {
get: function(){ return properties[i]; },
set: function(val){ properties[i] = val; }
})
})(i);
}
}
// generate a random color
function randomColor(){
return('#'+Math.floor(Math.random()*16777215).toString(16));
}
}); // end $(function(){});
</script>
</head>
<body>
<canvas id="canvas" width=300 height=300></canvas>
</body>
</html>
I have not so much to add to the other answers (+1 to both) but just a note on this part:
function Rectangle(x,y,width,height) {
var x=x;
var y= y;
var width= width;
var height= height;
...
When you do Rectangle(x, y, ...) the compiler/parser will actually do this for you (or rather, the equivalent of):
var x = arguments[0]; // x declared internally, no need to manually declare it
var y = arguments[1]; // y declared too, etc.
...
so you do not need to declare the variables in the function signature as they are already declared - or just leave the signature without any parameters and do the assigning manually (a tad slower but fully legal).
function Rectangle() {
var x = arguments[0]; // legal but not recommended (in most cases)
var y = arguments[1];
...
So, in conclusion - the recommended approach in this case would be:
function Rectangle(x,y,width,height) {
// no x,y, width and height decl. here - they're declared by signature
this.getX = function(){
return x;
}
...
Second issue: setInterval will need a reference to a function. As it is now the function will invoked due to placing the to parenthesis at the end and the result of that function will be handed as a reference instead.
You can call it like:
setInterval(myRectangle.draw, speed); // only a reference, no parenthesis
But in order to enable cancelling of it you need to store the timer ID:
var timerID; // global scope
...
timerID = setInterval(myRectangle.draw, speed);
Then use that request to cancel it later:
clearInterval(timerID);
I would too recommend using requestAnimationFrame as this is optimized for animation and monitor sync.
Contrary to setInterval you'll need to call it per frame inside your animation loop. You can use a flag/condition to not call it again when you want to end the animation.
You have also a clearContext method in there - I assume you have that defined elsewhere in the code, if not, check out context.clearRect().
My code is
canvas.clipTo = function (ctx) {
ctx.beginPath();
for (var i = 0; i < totalPrintArea; i++) {
ctx.save();
ctx.fillStyle = 'rgba(51,51,51,0)';
ctx.rect(clipLft[i], clipTp[i], clipW[i], clipH[i], 'rgba(51,51,51,1)', clipRtn[i]);
ctx.stroke();
ctx.restore();
}
ctx.closePath();
ctx.clip();
canvas.calcOffset();
};
canvas.renderAll();
I am taking values from the red dotted box and apply to clip where multiple masks are generating.
My issue is its taking all properties but not rotation for all.
I want to rotate all the rectangles.
I just get some code to change the rotation for the clip like ctx.rotate(50); but will not work as I want to make all rotate with their own values
Please guide me for the same.
On the original fabricJS github project I saw the comment: https://github.com/kangax/fabric.js/issues/932#issuecomment-27223912
and decided that I need to prevent making ctx.beginPath all the time:
canvas.clipTo = function(ctx) {
var skip = false;
// Workaround to make possible
// making clipTo with
// fabric.Group
var oldBeginPath = ctx.beginPath;
ctx.beginPath = function() {
if (!skip) {
oldBeginPath.apply(this, arguments);
skip = true;
setTimeout(function() {
skip = false;
}, 0);
}
}
group.render(ctx)
};
You can see my workaround to the problem described:
https://jsfiddle.net/freelast/6o0o07p7/
The workaround is not perfect, but hope it will help somebody.
I have tried using the Andrey's answer, but althouth there some interesting points, it didn't work.
If you try to clip the canvas to a single object (e.g. a circle or a rectangle), you can simply do this:
canvas.clipTo = function(ctx) {
shape.render(ctx); //shape is a circle, for instance
}
However, as explained by Kienz and butch2k in the aforementioned comment on GitHub, the problem is that you cannot use this solution with groups. In particular, if you use the following snippet:
canvas.clipTo = function(ctx) {
group.render(ctx);
}
you will only see one object of the group to be used for clipping.
The issue is due to the render method, which calls the ctx.beginPath() and ctx.closePath() for each object in the group. And because only the last couple of beginPath-closePath calls will affect the clipping, you need some workaround.
So in my solution, I have temporarily redefined the ctx.closePath and ctx.beginPath methods (after storing them in other two temporary variables, named oldBeginPath and oldClosePath) so that they do nothing. Then I call oldBeginPath at the beginning, and after rendering all the objects in the group I call the oldClosePath.
And now, here is the (working) snippet:
canvas.clipTo = function(ctx) {
var oldBeginPath = ctx.beginPath;
var oldClosePath = ctx.closePath;
ctx.beginPath = function() {}
ctx.closePath = function() {}
oldBeginPath.apply(ctx);
group.forEachObject(function(shape){
shape.render(ctx);
});
oldClosePath.apply(ctx);
ctx.beginPath = oldBeginPath;
ctx.closePath = oldClosePath;
};
Hope this will save someone's spare time in the future.
I try load my simple stage from json file, I load images but it's all in one layer, and image is not in group, so resize not working, i don't know how i should to load all elements with layers and group;
This is my function to load(it's only image for now)
function load(loadStage) {
counter = 2;
activeStage = stage;
stage = Kinetic.Node.create(loadStage,'right');
var background = stage.get('.background');
var backgroundImage = stage.get('.backgroundImage');
var clipartsImage = stage.get('.image');
var text = stage.get('.text');
console.log(stage.getChildren());
for(i=0;i<clipartsImage.length;i++){
counter++;
(function() {
var image = clipartsImage[i];
groups[counter] = image.parent.get;
//console.log(image);
var imageObj = new Image();
imageObj.onload = function() {
imageObj.src = image.attrs.src;
console.log(image);
image.setImage(imageObj);
image.on('mouseover', function() {
document.body.style.cursor = 'pointer';
});
image.on('mouseout', function() {
document.body.style.cursor = 'default';
});
image.on('click', function(evt) {
this.children[0].children[3].show();
var shape = evt.targetNode;
this.moveToTop();
active = layers[shape.getId()];
objectId = shape.getId();
});
image.getLayer().draw;
};
imageObj.src = image.attrs.src;
})();
stage.draw();
}
stage.draw();
}
And this is my json file, http://wklej.org/id/1105520/ this is middle stage. I'll keep trying, but if someone has had a similar problem and solved it, I would be grateful for your help :)
1)
What is going on with your JSON file? the base64 string has blown up into a million lines...
For starters, KineticJS.Layer cannot have a layer as a child. References:
https://github.com/ericdrowell/KineticJS/wiki/Change-Log#455-july-28-2013
new add() validation. An error is thrown if invalid children are added to a parent, for example, when adding a layer to another layer.
https://github.com/ericdrowell/KineticJS/wiki
KineticJS event not fired for child of a grouped layer
Inside your JSON file, you have tons of layers layered within each other, this is definitely going to cause a problem. Actually it looks like you have a layer for EACH group that you have! This is completely wrong KineticJS structure.
You should do some research on how and when to use layers and groups, here are some references:
What are the differences between group and layer in KineticJs
https://github.com/ericdrowell/KineticJS/wiki (again, the data structure hierarchy for KineticJS)
2)
No need to call stage.draw() twice.
3)
JSON is a data format, it only stores object attributes. This means it does not store any references to events attached to objects.
What this means is that when you load your JSON string, you'll have to rebind the updateAnchor function to all your anchors again. (Similar to how you are rebinding click function to your images onload)
You'll have to do something like this inside your load function, after you create the stage with your JSON file:
var children = layer.getChildren(); //Use the layer that has all your groups with images/anchors. *REMEMBER you can't layers within layers!
for (var i=0; i<children.length; i++) {
//If name starts with 'anchor'
if (children.getName().indexOf('anchor') !== -1) {
children[i].on('dragmove', function(evt) {
updateAnchor(this);
layer.draw();
});
}
}