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

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.

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.

call function of 'undefined' object

I got a JS question, I can't seem to be able to call a method. I'm using this dragger script: http://skidding.github.io/dragdealer/
To use it I gotta define it like this:
new Dragdealer('just-a-slider', {
animationCallback: function(x, y) {
$('#just-a-slider .value').text(Math.round(x * 100));
}
});
The documentation says:
setValue(x, y, snap=false) Set the value of a Dragdealer instance
programatically. The 3rd parameter allows to snap the handle directly
to the desired value, without any sliding transition.
I tried:
var dragdealer_instance = new Dragdealer('just-a-slider', {
animationCallback: function(x, y) {
$('#just-a-slider .value').text(Math.round(x * 100));
}
});
dragdealer_instance.setValue(10, 1);
but it says that the dragdealer_instance is undefined.. how can I call that function?

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.

Why object does not see his method?

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

Javascript Delegate issue

So far I haven't been able to grok any Delegate function tutorials out there for Javascript. Even less so the ones with parameters.
Hopefully my current problem will provide a good example. (XXXX() is a function I am passing to Fee())
The way I have it now, my "delegate", (XXXX()) executes as soon as Fee() is called, not when I want it to be called (somewhere inside of Fee())
line of code that calls function:
Fee(prod, item, "usage", i, y, XXXX(prod, y));
Function:
function Fee(prod, item, name, i, y, func) {
var open = false;
var prefix = "#" + prod + name;
$(prefix + y).click(function () {
var feeBoxName = prefix + "FeeBox" + y;
if (open == false) {
FeeOpen(prefix, y);
func; //delegate is called here
AddFeeButton(feeBoxName, "addFeeBtn2");
open = true;
} else {
FeeClosed(prefix, y);
open = false;
}
});
}
Delegate function:
function XXXX(prod, y) {
var deducedFeeBoxName = "#" + prod + "usageFeeBox" + y;
alert(deducedFeeBoxName); //at present, this is called immediately when Fee is executed
UsageFeeBoxHeader(deducedFeeBoxName);
UsageFeeBoxData(deducedFeeBoxName, prod, y);
}
Comments?
You want:
Fee(prod, item, "usage", i, y, function() { XXXX(prod, y); });
It's usually not called a "delegate" in Javascript. I believe that's a term mostly used in the Microsoft part of the world. We just call it a lambda or anonymous function.
You also have a bug in the main (Fee) function. Change this line:
func(); //delegate is called here
If your function is called XXX, then if you use this syntax: XXX(), you are invoking your function. To pass it in as a delegate, you simply pass in XXX:
Fee(prod, item, "usage", i, y, XXX);
The Fee method will be responsible for invoking it and determining which parameters are passed into the method.

Categories

Resources