Why object does not see his method? - javascript

I am developing a game using the framework atomJS and library libCanvas. Here is the code where the error occurs:
var Planet=atom.Class({
//other code
clearLayer : function (layer) {
layer.ctx.clearRect(this.x, this.y, this.size, this.size);
},
colonize : function (layer, angle, color,ms) {
**this.clearLayer(layer);**
drawArc({
context: layer.ctx,
x: Math.round(this.x + this.size / 2),
y: Math.round(this.y + this.size / 2),
radius: this.radius + 5,
width: 4,
color: color,
opacity: 0.6,
angleFinish: angle
});
if (this.colonizing) {
//if (this.cursorOnPlanet()) this.context.fillText(COLONIZING, (this.x + this.size / 2) - 30, this.y + this.size - 2);
this.colonizingTimer = setTimeout(this.colonize, ms,layer, angle + 5, color,ms);
if (angle > 360) {
this.colonizing = false;
this.state = 1;
}
} else {
clearTimeout(this.colonizingTimer);
this.clearLayer(layer);
}
},
});
On this line, this.clearLayer(layer); the script terminates with an error Object [object DOMWindow] has no method 'clearLayer'.Tell me please what's the problem?
Thanks!

It's important to see how whateverObject.colonize() is actually getting called. Anyway, it's clear that the original object's method is being bound to a different object before getting called. This is fairly common in event handlers, for example, where this usually (but not always) ends up being the event target, not the method's original object.
It's common for developers to use a closure to ensure that they have a safe reference for the original this. For example, you might define colonize in a constructor that says var self=this;, which would guarantee the name self points to the original this even if this itself gets rebound.
Another approach is to use Function.prototype.bind (which you'd have to polyfill for old JS engines), which creates a new function with a this object guaranteed to be whatever you specify.

It sounds like the function is being called from the DOM window and not the local class. When the this object is a window, you'll inevitably have scoping issues.
Your problem is with the setTimeout function. When the timeout is called, it's telling the DOMWindow, not the local class, to call the function. To fix this, wrap the call into a function.
function(){<code>}
Edit: I'm not really sure of the purpose of the extra fields in the setTimeout, so I omitted my solution. If you wrap whatever you're doing in a function, it should work though.

change
this.colonizingTimer = setTimeout(this.colonize, ms,layer, angle + 5, color,ms);
to
var self = this;
this.colonizingTimer = setTimeout(function(){self.colonize.call(self);}, ms,layer, angle + 5, color,ms);
The thing is that because of the timeout, the this object is removed from your object scope and at execution time refers to the global object(window) which has no method named clearLayer.
Here's a simplified demo to see the difference.

& the most correct way is to use "delay":
this.colonizingTimer = this.colonize.delay(ms, this, [layer, angle + 5, color, ms]);
But, if i understand right you want to animate angle from zero to 360 degrees? Why dont you use "Animatable" & ".animate" ?
With every question about LibCanvas you can send me an email to shocksilien#gmail.com

Related

How to pass object to animation function in JavaScript?

I have been trying to make a JavaScript animation of moving circle in HTML Canvas without using global variables. I am using requestAnimationFrame function. Since JavaScript does not support passing variable by reference, I tried creating a Circle class:
class Circle{
constructor(x, y, dx, dy) //set initial position and velocity of circle
{
this.x = x;
this.y = y;
this.dx = dx;
this.dy = dy;
}
}
function moveCircle(circle, other variables)
{
//clear canvas
//calculate new position and velocity using circle.x, etc.
//save new values to the object
//draw new circle into the canvas
requestAnimationFrame(moveCircle);
}
function button()//function called after click on button
{
//initial settings of canvas, intial condition
circle = new Circle(x, y, dx, dy);
moveCircle(circle, other vars);
}
This makes one frame and then throws error "Cannot read property 'x' of undefined". What am I doing wrong? Is there any other way of doing this, while avoiding global variables?
First of all, you don't need to create a class you can just pass the coordinates in an object or as separate arguments.
Secondly you should use Function#bind on requestAnimationFrame to pass the same arguments to next call.
Example using object:
function moveCircle(circle) {
console.log(circle.x);
if (circle.x) {
circle.x -= circle.dx;
requestAnimationFrame(moveCircle.bind(null, circle));
}
}
function button() {
moveCircle({
x: 500,
y: 0,
dx: 20,
dy: 0
});
}
button();
Example without object:
function moveCircle(x, dx) {
console.log(x);
if (x) {
requestAnimationFrame(moveCircle.bind(null, x - dx, dx));
}
}
function button() {
moveCircle(500, 20);
}
button();
To keep things simple you could use a closure to create a handler that internally knows what your circle looks like and how it should move. A closure is simply a function that defines its own variables locally, then returns a function that can access those.
We want to return a function that only accepts one argument: time, since that is the argument passed into every handler in the AnimationFrame by the browser. Then we want the closure function to draw into the globally defined canvas. Have a look here:
const canvas = document.body.appendChild( document.createElement( 'canvas' ) );
function makeMovingCircle( canvas, x, y, radius, distance, duration ){
// Lets get the context to draw here so we only need to fetch it once and store it, saving some computing time in favour of storing into memory.
const ctx = canvas.getContext( '2d' );
// We need to return a named function here so it can interally call itself again in requestAnimationFrame
return function AnimationHandler( time ){
// Lets just calculate an offset here based on the distance and duration we passed in above.
const progress = (time % duration / duration) * distance;
ctx.clearRect( 0, 0, canvas.width, canvas.height );
ctx.beginPath();
ctx.arc( x + progress, y, radius, 0, Math.PI*2, true );
ctx.stroke();
// Now call the named handler for the next animationFrame
window.requestAnimationFrame( AnimationHandler );
}
}
// Now lets make an animation handler and add it to the animationFrame. If you ever want to cancel it, you might want to store it in a global variable though so you can call cancelAnimationFrame on it.
window.requestAnimationFrame(
makeMovingCircle( canvas, 15, 15, 10, 100, 2000 )
);
I did a quick example from my memory:
const circle = $('#circle');
class Main {
constructor() {
}
start() {
requestAnimationFrame(this.loop.bind(this))
}
loop() {
const pos = circle.position();
// speed is a constant 1,1. But you could replace it by a variable
circle.css({top: pos.top+1, left: pos.left+1, position:'absolute'});
requestAnimationFrame(this.loop.bind(this))
}
}
const main = new Main();
main.start();
<div>
<img id="circle" src="https://playcode.io/static/img/logo.png"
alt="PlayCode logo">
<h1 id="msg"></h1>
</div>
#circle {
position: absolute;
top: 0;
left: 0;
}
In js, object are passed by reference, and primitive types by value.
I think you should avoid having other variables in moveCircle function. That kind of function is usually called "loop" or "gameLoop" or "update". When your button is clicked, add a speed to the circle, without creating a new circle, something like myCircle.speed = {x:2, y:2}. In the gameloop, add the speed to the position each frame.
Think also about delta time, as requestAnimationFrame will be faster depending on the PC/Mobile that run it.
You can wrap your application into on Main class if you like. (like I did above)
Finnaly, if you insist passing parameters to moveCircle, you can use bind.
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_objects/Function/bind
function c(a,b,c) {
console.log(a); console.log(b); console.log(c);
}
c.bind(window, 4,3)
// function c() // bind will return a function ! In a class usually the context that you want to pass is `this`. If you are playing directly in the console, the context is window by default (not that it matter in this example)
c.bind(window, 4,3)()
// 4
// 3
// undefined
The first call to moveCircle (from what I understand, it is in button function), should also be a requestAnimationFrame like requestAnimationFrame(moveCircle.bind(window, other vars)), you could re-use that in moveCircle as well, to avoid any global variable. Still, I recommend making a class to wrap your application, so that you can use local variables to your class to save the current state of your game instead of having that same state living inside the function arguments only.

I'm having a lot of trouble with setInterval and class methods

I keep running into bizarre problems. I've been unable to find anything on them after doing some research, so I thought I'd come here to present them. I have a class which is rather long, but I'll include the relevant bits:
class AnimatedSnake {
constructor(canvasId, coordinates) {
this.coordinates = coordinates;
this.direction = 2;
this.ctx = document.getElementById(canvasId).getContext("2d");
// 0 - .99, describes how far along snake is between coordinates
this.progress = 0;
}
erase() {
for (let i = 0; i < this.coordinates.length; i++) {
let c1 = this.coordinates[i][0],
c2 = this.coordinates[i][1];
this.ctx.clearRect(c1 * 31, c2 * 31, 31, 31);
}
}
next() {
this.progress += 0.01;
if (this.progress >= 1) {
this.progress %= 1;
let nextCoord = this.coordinates[4].slice();
nextCoord[0] += ((this.direction % 2) * this.direction);
nextCoord[1] += ((!(this.direction % 2) * (this.direction / 2)));
this.coordinates.push(nextCoord);
this.coordinates.shift();
}
console.log(this.erase);
this.erase();
this.draw();
}
}
So far, I can call AnimatedSnake.next() indefinitely if I'm doing it manually (i.e. from the console). However, when I put the function in an interval or timeout - setInterval(AnimatedSnake.next, 100) - it all of a sudden, on the first run, claims that AnimatedSnake.erase is not a function. I tried putting AnimatedSnake.erase() directly in the interval, and when I do THAT, for some absurd reason it goes and tells me that it cannot take the length property of AnimatedSnake.coordinates, which it claims is undefined. Nowhere in my code to I redefine any of these things. coordinates is altered, but it should not be undefined at any point. And erase is of course a method that I never change. Does anyone have any insight into why, when these are called with setInterval or setTimeout weird things happen, but if I call the functions repeatedly (even in a for loop) without the JavaScript timing functions everything works out fine? I'm genuinely stumped.
Consider these two snippets:
animatedSnake.next()
And:
let method = animatedSnake.next;
method();
In the first snippet next is called as a member of animatedSnake object, so this within the context of next method refers to the animatedSnake object.
In the second snippet the next method is detached from the object, so this no longer refers to the animatedSnake instance when the method function is invoked. This is how passing a method to another function, like setInterval works. You can either use Function.prototype.bind method for setting the context manually:
setInterval(animatedSnake.next.bind(animatedSnake), 100)
or wrap the statement with another function:
setInterval(() => animatedSnake.next(), 100)

Why is this function declared twice? Where is the callback function declared?

http://codepen.io/Lewitje/pen/GjqbbA
Looking at this and trying to figure out what is happening is discouraging me. I've been doing a javascript deep dive lately and I understand objects, constructors, prototypes etc. and on css end I know animation with keyframes, canvas, coordinates etc yet looking at this I know maybe 40-50% of the javascript that is written there.
1) I know that a class and constructor is declared (this is relatively new to javascript?)
2) jQuery .each method is used to attach labels A1, A2 etc. with setupLabels()
3) A variety of different functions to display the number punched in on the dial, move the arm, move the can etc.
Now this is where it gets confusing for me:
setPosition(x, y, callback){
$('.hand').on('transitionend', ()=>{
$('.hand').off('transitionend');
setTimeout(function(){
callback();
}, 500);
});
this.calculateVelocity(x, y, (velX, velY)=>{
$('.arm').css({
'top': (y + 35) + 'px',
'transition-duration': velY + 's'
});
$('.hand').css({
'left': (x + 5) + 'px',
'transition-duration': velX + 's',
'transition-delay': velY + 's'
});
});
}
calculateVelocity(x, y, callback){
var currentX = $('.hand')[0].offsetLeft;
var currentY = $('.arm')[0].offsetTop;
var velX = Math.ceil((Math.max(currentX, x) - Math.min(currentX, x)) / 70);
var velY = Math.ceil((Math.max(currentY, y) - Math.min(currentY, y)) / 70);
callback(velX, velY);
}
}
I'm guessing the this.calculateVelocity is infact, calling the function and the what comes after => is defining the callback function?
If that is the case how does the callback in setPosition work as it hasn't been defined?
JavaScript starts by compiling a list of all the function names before it runs, so calculateVelocity has been defined. Additionally, you are correct, (velX, velY) => {} is the callback, and the function is being called. It's in the new Arrow function format.
Both of the following are identical:
this.calculateVelocity(x, y, (velX, velY) => {
...
});
this.calculateVelocity(x, y, function(velX, velY) {
...
});
Inside setPosition, this refers to the global object. In this case, calculateVelocity is a function on the global object, and so it can be called via this.calculateVelocity.
I should also note that the source JavaScript in the linked Codepen is actually TypeScript/ES7.

Tween.js Not Calling onUpdate Function

I am using tweenjs and Typescript to change the x and y coordinates of a three.js cube. I created the following tween to change the x position of an item of class "FallingItem".
this.movementArcData.horzTween = new TWEEN.Tween(this.movementArcData.pos.x)
.to(this.movementArcData.newPos.x, this.movementArcData.movementTime * 1000)
.onUpdate(this.updateXPos)
.onComplete(this.horzTweenComplete);
where "this.movementArcData" is an object containing the following:
horzTween - the tween itself
pos.x - the original position of the item
movementTime - the time it takes to complete the movement, 2000 milliseconds
updateXPos - a member function of the a FallingItem object with the following code:
updateXPos(){
this.mesh.position.x = this.movementArcData.pos.x;
console.log("update x: " + this.movementArcData.pos.x);
}
horzTweenComplete - a member funtion of the FallingItem object with the following code:
horzTweenComplete(){
this.movementArcData.horzTweenComplete = true;
}
Neither the updateXPos or horzTweenComplete callback is getting fired.
I am calling TWEEN.update in my render loop like so:
TWEEN.update(dt);
Since the tween's onComplete event never fires, the TWEEN.update is called constantly. What am I missing that is causing the tween not to work properly?
I had a similar case when TWEEN was not calling my onUpdate function. Found out I had to call window.requestAnimationFrame() in order to tell the browser that I, i.e. TWEEN, "want to perform an animation and requests that the browser call a specified function to update an animation before the next repaint."
function animate(time) {
window.requestAnimationFrame(animate);
TWEEN.update(time);
}
new TWEEN
.Tween({ y: 0 })
.to({y: 1000}, 700)
.easing(TWEEN.Easing.Exponential.InOut)
.onUpdate(function () {
window.scrollTo(0, this.y);
})
.start();
animate();
The above example was taken from https://github.com/tweenjs/tween.js/blob/master/examples/00_hello_world.html.
Tween.js needs to be passed the elapsed time, not a delta time. Passing a running elapsed time fixed the problem.
Also, it's supposed to be passed an object containing the value you want interpolated. It looks like passing the value itself doesn't work. I had success with this:
let tweenElement = {
x: this.tweenInfo.pos.x,
y: this.tweenInfo.pos.y,
item: this
}
this.tweenInfo.tweenUp = new TWEEN.Tween(tweenElement)
.to({y : this.tweenInfo.newPos.y}
, this.tweenInfo.movementTime * 0.5 * 1000)
.easing( TWEEN.Easing.Cubic.InOut )
.onUpdate(function(){
this.item.updateYPos(tweenElement, this)
})
.onComplete(function(){
this.item.tweenUpComplete();
});
Another potential cause (I know probably not in this particular case) is that TWEEN.removeAll() is being called somewhere else in the code. One sign of this happening is that other tweens are working perfectly fine but some are not.

Pass attribute, not value as parameter

I would like to make this happen in JavaScript:
function changeAttributeValue(theAttribute, numberOfPixels)
{
theAttribute = numberOfPixels + "px";
}
changeAttributeValue(myImage.style.left, 50);
changeAttributeValue(myImage.style.top, 80);
changeAttributeValue(myCanvas.width, 600);
The first two calls should move an image and the third should adjust the width of a canvas.
Like this however, just the value of the passed variables were passed, so nothing happens.
Does anybody know how to solve this problem?
You could do something like this.
function changeAttributeValue(obj, propName, numberOfPixels) {
obj[propName] = numberOfPixels + "px";
}
changeAttributeValue(myImage.style, "left", 50);
changeAttributeValue(myImage.style, "top", 80);
changeAttributeValue(myCanvas, "width", 600);
Well, here is my "solution".
It is inspired by Jared Farrish's comment. That is, this creates a "wrapper" getter/setter function for attribute access that can be used. I do not recommend it here (as it is more complicated than what is needed), but it could be used for something awesome elsewhere.
function mkAccessor(obj, attribute) {
return function (value) {
if (arguments.length > 0) {
obj[attribute] = value
}
return obj[attribute]
}
}
function changeAttribute (accessor, value) {
accessor(value)
}
changeAttribute(mkAccessor(myImage.style, "top"), 50)
changeAttribute(mkAccessor(myImage.style, "left"), 80)
var canvasWidthAccessor = mkAccessor(myCanvas, "width")
// somewhere later on
changeAttribute(canvasWidthAccessor, 600)
// or just
canvasWidthAccessor(600)
Theory is sound, but code is untested. YMMV. :-)
It's probably useful if I actually post and comment what I provided in comments.
To wit:
function setDim(attr, modifier, pix) {
if (!modifier) {
this[attr] = pix + 'px';
} else {
this[attr][modifier] = pix + 'px';
}
}
Now, how does this work? The this references the scope. If you simply call it directly:
setDim('style', 'width', 50);
That will simply call the window or global scope. Really, this is built for passing the scope using el.call() or el.apply(), more or less the same, just depends on how you want the arguments passed into the function.
How you really want to call it:
// Gives you the scope of the element #test1
var test = document.getElementById('test1');
Which then can be "called" like:
setDim.call(test, 'style', 'width', 50);
Note, if it's just an attribute (plain and simple), pass null:
setDim.call(test, 'width', null, 50);
I use setTimeout() in this fiddle for effect, but it's not needed:
http://jsfiddle.net/userdude/tCz6j/2/
Take a look through the jQuery core source and see how much .apply() is used. These two methods are really useful, if you "get" them.

Categories

Resources