In the code below, I have a Mass constructor and some methods. Originally, methods were inside the Mass constructor, but there are many methods I'm using. So, to keep things more organized I removed some of the methods outside of the Mass and add them using prototype.
But then I have an issue. I can't reference to Mass with this; it refers to window.
function Mass(elm) {
this.getElement = function(element) {
if (typeof element == "string") {
return document.getElementById(element);
}
return element;
};
this.elm = this.getElement(elm);
this.elm.style.position = "absolute";
this.elm.style.left = 0 + "px";
this.elm.style.top = 0 + "px";
this.updateVars = function () {
this.width = parseInt(this.elm.style.width, 10);
this.height = parseInt(this.elm.style.height, 10);
this.top = parseInt(this.elm.style.top, 10);
this.left = parseInt(this.elm.style.left, 10);
this.radius = this.width / 2;
this.originX = this.left + this.radius;
this.originY = this.top + this.radius;
this.worldOrgX = this.originX + parseInt(this.elm.parentNode.style.left, 10);
this.worldOrgY = this.originY + parseInt(this.elm.parentNode.style.top, 10);
};
}
Mass.prototype = {
// other methods here
rotation : {
// this = window
mass : this,
angle : 0,
handler : null,
rotate : function (angularVelocity, rotationSpeed) {
this.angle = (this.angle + angularVelocity) % 360;
// here I need to access Mass.elm and apply rotate
this.mass.elm.style.webkitTransform = "rotate(" + this.angle + "deg)";
},
start : function (angularVelocity, rotationSpeed) {
var rotation = this; // this = Mass.rotation
rotation.handler = setInterval(function () {
rotation.rotate(angularVelocity, rotationSpeed);
}, rotationSpeed);
},
},
}
var earth = new Mass("earth");
//earth.rotation.start(4.5, 25);
Fiddle
Old version of the code. This is working fine. What changes should I make for new one to work?
The prototype is not meant to make the code inside your constructor smaller. The prototype is meant to share common functionality. For example your getElement method is the same for all instances of Mass. Hence it would be ideal to put it on the prototype. Same for updateVars.
On the other hand this.rotation is an object and not a function. Every instance of Mass will have a different this.rotation property. Hence this.rotation cannot be shared and hence must not be put on the prototype.
I usually like to create "classes" in JavaScript using a simple defclass function:
function defclass(prototype) {
var constructor = prototype.constructor;
constructor.prototype = prototype;
return constructor;
}
The defclass function unwraps a prototype and returns its constructor. By the way, the constructor is just another method on the prototype.
So here's how I would rewrite your Mass class:
var Mass = defclass({
constructor: function (id) {
var element = this.element = this.getElement(id);
var style = element.style;
style.position = "absolute";
style.left = 0;
style.top = 0;
this.rotation = new Rotation(this);
},
getElement: function (id) {
if (typeof id !== "string") return id;
else return document.getElementById(id);
}
});
Next we create the Rotation class:
var Rotation = defclass({
constructor: function (mass) {
this.mass = mass;
this.angle = 0;
},
start: function (angularVelocity, delay) {
var rotation = this;
setInterval(function () {
rotation.rotate(angularVelocity);
}, delay);
},
rotate: function (angularVelocity) {
var angle = this.angle = (this.angle + angularVelocity) % 360;
var transform = "rotate(" + angle + "deg)";
var style = this.mass.element.style;
style.webkitTransform = transform;
style.mozTransform = transform;
style.msTransform = transform;
style.oTransform = transform;
style.transform = transform;
}
});
Finally we simply create an instance of Mass and make it rotate:
var earth = new Mass("earth");
earth.rotation.start(4.5, 25);
See the demo for yourself: http://jsfiddle.net/NL3SJ/
Related
here is the code for my project:
'_' is a selector method.
'update' is run on an interval of 0.1
'init' creates the
'Ball' is the object I'm trying to create
'ball.set()' is the method that is not working.
var width = window.innerWidth/3;
var height = window.innerHeight-60;
var balls = [];
var id;
function rand(min, max) {
return (Math.random() * (max - min)) + min;
}
function run(){
init();
}
function init(){
document.write("<div style='width:"+width+"px;height:"+height+"px;' id='container'></div>");
new Ball(width/2-12.5, height/2-12.5, rand(-2, 2), rand(-2, 2), 1);
id = setInterval(update, 0.1);
}
function _(elem){
return document.getElementById(elem);
}
function update(){
for(var b in balls){
b.set();
}
}
function Ball(x, y, vx, vy, ind){
this.x = x;
this.y = y;
this.vx = vx;
this.vy = vy;
this.ind = ind;
balls[this.ind] = this;
_("container").innerHTML+=("<div style='top:"+y+"px;left:"+x+"px;' class='ball' id='ball"+this.ind+"'></div>");
this.deleteBall = function () {
_('ball'+this.ind).outerHTML = "";
balls[this.ind] = null;
};
this.set = function(){
this.x += this.vx;
this.y += this.vy;
var elem = _("ball"+this.ind);
elem.style.left = this.x + 'px';
elem.style.top = this.y + 'px';
};
}
run();
I'm trying to make a Ball object for a JS game I'm making. The only problem is Google Chrome is giving me an error:
Uncaught TypeError: b.set is not a function
at update (ballgame.js:29)
update # ballgame.js:29
What am I doing wrong?
I don't know the complete requirement, but I found the cause for this issue.The problem is with the line
balls[this.ind] = this;
Assume for the first time the constructor is called with 'ind' value of 5. This will set balls[5]=this object. Now 'balls' array length becomes 6 and balls[0] through balls[4] will contain value as 'undefined'.
Now in 'update' method, when you iterate through balls array, as balls[0] through balls[4] are undefined, you get the error
Uncaught TypeError: b.set is not a function
Problem: I want to arrange divs in a circle and animate the radius. Unfortunately, I seem to have some sort of binding issue with my variables. Trying to figure this out since 3 days. What can I do to get out of this predicament? I mean how do I solve this in an "elegant fashion"?
What I Tried: I did a few console logs and everything implies that javascript has problems interpreting what I mean with "this".
I also found out that I need to bind the parameter inside requestAnimationFrame but I cant imagine binding every variable and function name so I must be doing something wrong here.
Here is the JSFiddle: https://jsfiddle.net/Lh23pzvb/4/
Code:
function explodeRocket(obj) {
this.elem = document.getElementsByTagName('div');
this.particle = [];
this.radius = obj.radius;
this.color = ['red', 'yellow', 'cyan', 'orchid']
this.interval = 2 * Math.PI / obj.particles;
this.init = function() {
this.create();
this.blast();
}
this.init();
this.create = function() {
for(var i = 0; i < obj.particles; i++) {
document.body.appendChild(document.createElement('div'));
this.elem[i].style.background = this.color[obj.colorIndex];
this.particle.push(new this.Properties(0, obj.x, obj.y));
this.updatePosition(i);
}
}
this.Properties = function(angle, x, y) {
this.angle = angle;
this.x = x;
this.y = y;
}
this.updatePosition = function(index) {
this.elem[index].style.left = this.particle[index].x + 'px';
this.elem[index].style.top = this.particle[index].y + 'px';
}
this.blast = function() {
for(var i = 0; i < 10 - 1; i++) {
if(this.radius < obj.radiusMax) {
this.radius += 0.2;
}
this.particle[i].x = Math.round(window.innerWidth) / 2 + obj.radius * Math.cos(this.particle[i].angle) ;
this.particle[i].y = Math.round(window.innerHeight) / 2 + obj.radius * Math.sin(this.particle[i].angle);
this.updatePosition(i);
this.particle[i].angle += this.interval;
}
requestAnimationFrame(this.blast);
}
}
new explodeRocket({
particles: 4, colorIndex: 0, radius: 0, radiusMax: 200, x: 0, y: 0
})
Bug 1:
You call the init() before the create() function initialised, so you cannot call it. You can fix it by putting the init() at the end of the explodeRocket function, so all your functions exist when calling init().
Bug 2:
.blast() runs the loop too many times. You only have 4 particles, but the loop runs 9 times. You can fix it by running the loop only particle.length times:
for(var i = 0; i < this.particle.length; i++) {
Bug 3:
The this bug. Your code loses this, when you call requestAnimationFrame. You can use the Function.call, which takes the this object as the first parameter. It's common to bind this to some other variable (like self) to overcome these issues:
var self = this;
requestAnimationFrame(function() {
self.blast.call(self);
});
Bug 4:
You're referring to obj.radius, which is always 0. You should use this.radius:
this.particle[i].x = Math.round(window.innerWidth) / 2 + this.radius * Math.cos(this.particle[i].angle);
this.particle[i].y = Math.round(window.innerHeight) / 2 + this.radius * Math.sin(this.particle[i].angle);
Bug 5:
You probably don't want to rotate full 1/4th of round always, so animate 1/40th of circle per frame:
this.particle[i].angle += this.interval * 0.1;
Bug 6:
You probably want the particles to be evenly spaced, so intialize the particle with proper radius:
this.particle.push(new this.Properties(this.interval * i, obj.x, obj.y));
Complete code:
function explodeRocket(obj) {
var self = this;
this.elem = document.getElementsByTagName('div');
this.particle = [];
this.radius = obj.radius;
this.color = ['red', 'yellow', 'cyan', 'orchid']
this.interval = 2 * Math.PI / obj.particles;
this.init = function() {
this.create();
this.blast();
}
this.create = function() {
for(var i = 0; i < obj.particles; i++) {
document.body.appendChild(document.createElement('div'));
this.elem[i].style.background = this.color[obj.colorIndex];
this.particle.push(new this.Properties(this.interval * i, obj.x, obj.y));
this.updatePosition(i);
}
}
this.Properties = function(angle, x, y) {
this.angle = angle;
this.x = x;
this.y = y;
}
this.updatePosition = function(index) {
this.elem[index].style.left = this.particle[index].x + 'px';
this.elem[index].style.top = this.particle[index].y + 'px';
}
this.blast = function() {
for(var i = 0; i < this.particle.length; i++) {
if(this.radius < obj.radiusMax) {
this.radius += 0.2;
}
this.particle[i].x = Math.round(window.innerWidth) / 2 + this.radius * Math.cos(this.particle[i].angle);
this.particle[i].y = Math.round(window.innerHeight) / 2 + this.radius * Math.sin(this.particle[i].angle);
this.updatePosition(i);
this.particle[i].angle += this.interval * 0.1;
}
requestAnimationFrame(function() {
self.blast.call(self);
});
}
this.init();
}
new explodeRocket({
particles: 4, colorIndex: 0, radius: 0, radiusMax: 200, x: 0, y: 0
})
That should do it!
Edit:
Working jsfiddle: https://jsfiddle.net/v3nt2kd0/
First of all, try moving your this.init(); at the end of the constructor.
After doing that there are some other errors thrown, inside your blast() method, which probably has something to do with the fact that the this.particles array only has 4 elements and you are trying to do 10 iterations or so.
As a tip, you could move most, if not all, of the methods on the explodeRocket.prototype so that instances share the same method instance, like so:
function ExplodeRocket(obj) {
//yada yada
}
ExplodeRocket.prototype.blast = function blast() {
//yada yada
}
Revisiting one of my old experiments. I'm using an interval to rotate an element. Now I want to change the interval to rAF. I tried to rewrite the Rotation.start method a few times, but I couldn't succeed. Since I'm new to this requestAnimationFrame thing, I don't know what else to try.
Relevant JS code:
function Rotation(mass) {
this.mass = mass;
this.angle = 0;
this.velocity = 1;
this.fps = 1000/25; // for interval
this.handler = null;
this.rotate = function () {
// when called from rAF "this" refers to window, method fails
this.angle = (this.angle + this.velocity) % 360;
var transform = "rotate(" + this.angle + "deg)";
this.mass.elm.style.webkitTransform = transform;
};
this.start = function () {
var rotation = this; // this = Rotation
rotation.handler = setInterval(function () {
// inside the interval "this" refers to window
// that's why we set rotation to "this" at the beginning
// so we can access the Rotation obj
rotation.rotate();
}, rotation.fps);
/* requestAnimationFrame(rotation.rotate); */
};
}
Fiddle (full code)
You can bind the value of this using the bind() method
function Rotation(mass) {
this.mass = mass;
this.angle = 0;
this.velocity = 1;
this.handler = null;
this.rotate = function () {
this.angle = (this.angle + this.velocity) % 360;
var transform = "rotate(" + this.angle + "deg)"
this.mass.elm.style.webkitTransform = transform;
//call bind on this.rotate so we can properly associate this
window.requestAnimationFrame(this.rotate.bind(this));
};
this.start = function () {
//then just start the animation by using this.rotate()
this.rotate();
};
}
Working JSFiddle: http://jsfiddle.net/gvr16mcL/
If I have an array in JavaScript that starts with var stars = [] and I create a star (code below). I found this code online and am working my way through it so that I can see how it works and to modify it.
Is the this.stars = stars; just creating another internal property for this particular class?
var stars = [];
for(var i=0; i<this.stars; i++) {
stars[i] = new Star(Math.random() * this.width,
Math.random() * this.height,
Math.random() * 3+1,
(Math.random() * (this.maxVelocity - this.minVelocity))
+ this.minVelocity);
}
this.stars = stars; // <-- creating internal property
Because I do not see it here in the definition of the class. So I am not certain if it is just created on the spot or if it could be declared in this definition.
Code Here:
function Starfield() {
this.fps = 30;
this.canvas = null;
this.width = 0;
this.width = 0;
this.minVelocity = 15;
this.maxVelocity = 30;
this.stars = 9000;
this.intervalId = 0;
}
// The main function - initialises the starfield.
Starfield.prototype.initialise = function(div) {
var self = this; //sets it self to current object
// Store the div
this.containerDiv = div;
self.width = window.innerWidth;
self.height = window.innerHeight;
window.onresize = function(event) {
self.width = window.innerWidth;
self.height = window.innerHeight;
self.canvas.width = self.width;
self.canvas.height = self.height;
self.draw();
}
// Create the canvas.
var canvas = document.createElement('canvas');
div.appendChild(canvas);
this.canvas = canvas;
this.canvas.width = this.width;
this.canvas.height = this.height;
};
Starfield.prototype.start = function() {
// Create the stars.
var stars = []; //creates an array that can be used for anything but in this case a star field
//this.stars is a property in the class that contains a number of the stars
for(var i=0; i<this.stars; i++) {
stars[i] = new Star(Math.random() * this.width,
Math.random() * this.height,
Math.random() * 3+1,
(Math.random() * (this.maxVelocity - this.minVelocity)) + this.minVelocity);
}
this.stars = stars;
var self = this;
// Start the timer.
this.intervalId = setInterval(function() {
self.update();
self.draw();
}, 1000 / this.fps);
};
Starfield.prototype.stop = function() {
clearInterval(this.intervalId);
};
Starfield.prototype.update = function() {
var dt = 1 / this.fps;
for(var i=0; i < this.stars.length; i++) {
var star = this.stars[i];
star.y += dt * star.velocity;
// If the star has moved from the bottom of the screen, spawn it at the top.
if (star.y > this.height) {
this.stars[i] = new Star(Math.random() * this.width,
0,
Math.random() * 3 + 1,
(Math.random() * (this.maxVelocity + 60 - this.minVelocity)) + this.minVelocity);
}
}
};
Starfield.prototype.draw = function() {
var ctx = this.canvas.getContext("2d");
// Draw the background.
ctx.fillStyle = '#000000';
ctx.fillRect(0, 0, this.width, this.height);
// Draw stars.
ctx.fillStyle = '#ffffff';
for(var i=0; i<this.stars.length;i++) {
var star = this.stars[i];
ctx.fillRect(star.x, star.y, star.size, star.size);
}
};
//This is the class for stars -- there are 4 properties that are in this particular class
function Star(x, y, size, velocity) {
this.x = x;
this.y = y;
this.size = size;
this.velocity = velocity;
}
Yes, properties can be added at any time in JavaScript. Take a look at this example:
function Person(firstName) {
this.firstName = firstName;
}
Person.prototype.getFullName = function() {
// Notice that I haven't defined this.lastName yet
return this.firstName + ' ' + this.lastName;
};
var bob = new Person('Bob');
bob.lastName = 'Saget';
console.log(bob.getFullName()); // 'Bob Saget'
Yes, Javascript objects are dynamic. They can have new properties added/deleted at any time unless they have been sealed and their properties can be modified at any time unless they have been frozen.
You probably won't see many sealed or frozen objects in the wild.
I wanted to encapsulate the position of a sprite within another object. So that instead of using tile.x and tile.y I would access via tile.position.x and tile.position.y.
Yet once I set the value of tile.position within the init-method all the instances of the tile-object change to the same value. Why is that?
As when I set tile.x everything works as expected, meaning each object gets the right value.
This is how I create the multiple instances:
In a for loop I am creating multiple instances of said object:
for (var y = 0; y < 10; ++y) {
for (var x = 0; x < 10; ++x) {
var tile = Object.create(tileProperty);
tile.init(x, y);
...
}
}
And this is the cloned object:
var tileProperty = {
// this works
x: null,
y: null,
// this will get changed for ALL instances
position: {
x: null,
y: null
},
init: function(x, y) {
this.name = x.toString() + y.toString();
this.x = x;
this.y = y;
this.position.x = x;
this.position.y = y;
this.canvas = document.createElement('canvas');
var that = this;
$(this.canvas).bind('click', function() {
console.log(that.position, that.x, that.y);
});
document.body.appendChild(this.canvas);
}
}
Use this:
var tileProperty = {
position: { // we will inherit from this
x: null,
y: null,
init: function(x, y) {
this.x = x;
this.y = y;
}
},
init: function(x, y) {
this.name = x.toString() + y.toString();
// create an own Position object for each instance
this.position = Object.create(this.position);
// and initialize it
this.position.init(x, y); // you might inline this invocation of course
…
},
…
}
You're having a reference to the same position object in all your objects.
What you should do is using the standard prototype solution :
function tileProperty() {
this.position = {
x: null,
y: null
};
}
tileProperty.prototype.init = function(x, y) {
this.name = x.toString() + y.toString();
this.x = x;
this.y = y;
this.position.x = x;
this.position.y = y;
this.canvas = document.createElement('canvas');
var that = this;
$(this.canvas).bind('click', function() {
console.log(that.position, that.x, that.y);
});
document.body.appendChild(this.canvas);
}
and then build your instance using
var tp = new tileProperty();