I have already tried my luck and searched a lot but could not find a solution to my issue.
The function in question is supposed to draw a set of lines using jcanvas and pause the drawing according to prerecorded times.
Instead it just draws the entire lines at once.
Here is the jQuery code in question:
$("#start").click(function(){
$("canvas").css("display","block");
var obj = { strokeStyle: "#000", strokeWidth: 6, rounded: true};
for (i=0;i<counter;i++)
{
obj['x'+(i+1)] = arrX[i];
obj['y'+(i+1)] = arrY[i] - 12;
setTimeout(function() {
var interval = setInterval(function() {
$("canvas").drawLine(obj);
}, 0);
}, timeDiffs[i]);
}
});
Because the loop finishes before your setTimeout() callback is run, your callback will always reference the final state of the object (that is, its state after the entire loop has run).
To fix this, you can wrap your setTimeout() callback in a closure. By wrapping the callback in a closure function, I am capturing the state of the obj variable. However, in this case, because objects are passes by reference in JavaScript, I have cloned the object (using $.extend) to ensure that its current state (properties and all) is preserved.
setTimeout((function(obj) {
return function() {
var interval = setInterval(function() {
$("canvas").drawLine(obj);
}, 0);
};
}($.extend({}, obj))), timeDiffs[i]);
FWIW, there is an extensive examination of this issue on StackOverflow.
just a simplification of Caleb531's point
$("#start").click(function(){
$("canvas").css("display","block");
var obj = { strokeStyle: "#000", strokeWidth: 6, rounded: true};
for (i=0;i<counter;i++)
{
(function(){
obj['x'+(i+1)] = arrX[i];
obj['y'+(i+1)] = arrY[i] - 12;
var incremental = $.extend({}, obj);
setTimeout(function() {
var interval = setInterval(function() {
$("canvas").drawLine(incremental);
}, 0);
}, timeDiffs[i]);
})();
}
});
Related
I'm trying to get the following script working, but when it falls into the continueAnimation function it isn't updating the cPreloaderTimeout variable and it runs in a 'Uncaught RangeError: Maximum call stack size exceeded'.
var loadingIcon = {
cSpeed : 8,
cWidth : 64,
cHeight : 32,
cTotalFrames : 7,
cFrameWidth : 64,
cImageSrc : 'sprites.png',
cImageTimeout : false,
cIndex : 0,
cXpos : 0,
cPreloaderTimeout : false,
SECONDS_BETWEEN_FRAMES : 0,
startAnimation : function() {
document.getElementById('loaderImage').style.backgroundImage='url('+ this.cImageSrc+')';
document.getElementById('loaderImage').style.width=this.cWidth+'px';
document.getElementById('loaderImage').style.height=this.cHeight+'px';
//FPS = Math.round(100/(maxSpeed+2-speed));
FPS = Math.round(100/this.cSpeed);
SECONDS_BETWEEN_FRAMES = 1 / FPS;
this.cPreloaderTimeout = setTimeout( this.continueAnimation(), SECONDS_BETWEEN_FRAMES/1000);
},
continueAnimation : function() {
this.cXpos += this.cFrameWidth;
//increase the index so we know which frame of our animation we are currently on
this.cIndex += 1;
//if our cIndex is higher than our total number of frames, we're at the end and should restart
if (this.cIndex >= this.cTotalFrames) {
this.cXpos =0;
this.cIndex=0;
}
if(document.getElementById('loaderImage'))
document.getElementById('loaderImage').style.backgroundPosition=(-this.cXpos)+'px 0';
this.cPreloaderTimeout = setTimeout(this.continueAnimation(), SECONDS_BETWEEN_FRAMES*1000);
},
stopAnimation : function(){//stops animation
clearTimeout( this.cPreloaderTimeout );
this.cPreloaderTimeout = false;
}
}
jQuery( document ).ready(function() {
jQuery( document ).on("click", "#test", function(){
var loader = loadingIcon;
loader.startAnimation();
setTimeout( loader.stopAnimation(), 3000);
});
});
It was a plain javascript script at first, but I'm trying to make an object from it so it can be re-used and multiple times at the same time. The problem is now that the cPreloaderTimeout variable isn't set correctly when startAnimation or continueAnimation is triggerd.
You have a couple issues.
First, your cPreloaderTimeout isn't going to be set like you think it is, as you're not creating an object with a prototype, so the scope of this inside that function is going to be the function itself, not the object.
Second, setTimeout takes a function, but you're calling the function when you try and use it, so the value sent to setTimeout will be the results of the function, not the function itself.
Consider instead the format:
function LoadIcon() {
this.cSpeed = 8;
// All your other properties
}
LoadIcon.prototype.startAnimation = function() {
// your startAnimation function, concluding with
this.preloaderTimeout = setTimeout(this.continueAnimation.bind(this), SECONDS_BETWEEN_FRAMES/1000);
}
// the rest of the methods built the same way
//then, your calling code would look like:
var loadIcon = new LoadIcon();
loadIcon.startAnimation();
EDIT
I updated the setTimeout call as I'd forgotten about binding to this for correct scoping when the callback fires.
Using a library called Velocity JS for animating: http://julian.com/research/velocity/
I am using it as follows:
var velocity = new Velocity(element, {
translateX: 250,
complete: function() {
return true;
}
}, 5);
What I am trying to do is check if complete has returned true on successful completion of the animation so that I can toggle the animation.
What is the best method for checking if it is true
if(velocity.complete() === true)
This returns undefined.
Any help is appreciated thanks.
If you need to store the fact that the animation has completed, in order to read that information later based on user interaction (e.g., clicking a button), simply set a variable in an outer scope:
var animationComplete = false;
var velocity = new Velocity(element, {
translateX: 250,
complete: function() {
animationComplete = true;
}
}, 5);
And read it when the user interaction happens:
$("#someButton").on("click", function() {
if(animationComplete) {
// do something only if the animation has completed
}
});
It does not matter where you store that value (as a local or global variable, as a property on an object, etc.), as long as it's visible to the functions that need to read and set it.
Note that the return value of the complete callback is never used. The complete callback function object is passed into the Velocity constructor, and it is called later from inside of velocity.js's own code somewhere. Velocity.js probably does not expose (or even store) this return value anywhere.
You already pass the complete function, but returning true is not so helpful in this case. Start the second animation when the complete function is called:
var velocity = new Velocity(element, {
translateX: 250,
complete: function() {
// start another animation
}
}, 5);
If you need to toggle animation on click or another event you have to store animation boolean outside somewhere, even in the dom element or using jQuery data method:
var $velocity = $("#velocity");
$velocity.data("animating", false);
$velocity.data("direction", "left");
$velocity.on("click", function () {
if ($velocity.data("animating")) {
return;
}
$velocity.data("animating", true);
if ($velocity.data("direction") === "left") {
var ml = -30;
$velocity.data("direction", "right");
} else {
var ml = 30;
$velocity.data("direction", "left");
}
$velocity.velocity({marginLeft:ml},{
duration:500,
complete: function () {
$velocity.data("animating", false);
}
});
});
JSFIDDLE
I made an image slider "class" and originally instantiated it as:
var foo = new Slider(document.getElementById("featuredSlider"), 900);
I tried removing var foo = and it continues to work which was surprising to me. What makes it continue working? Is it a bad idea to not reference it globally?
On a side note, "featuredSlider" is the id of a <div> tag. It contains some number of <a> tags and each contain <img> tags.
function Slider(inElement, inStep) {
if (!inElement) return;
this.element = inElement;
this.start = 0;
this.end = 0;
var self = this;
var limit = inElement.getElementsByTagName("a").length*inStep;
setInterval(function() {
self.start = self.end;
self.end = (self.end+inStep)%limit;
self.animate(this.start < this.end ? 1 : -1);
}, 3000);
}
Slider.prototype.animate = function(inZeno) {
this.start += ((this.end-this.start)>>2)+inZeno;
this.element.style.left = this.start+"px";
if (this.start !== this.end) {
var self = this;
setTimeout(function() {
self.animate(self.start < self.end ? 1 : -1);
}, 33);
}
}
//new Slider(document.getElementById("featuredSlider"), 900);
var foo = new Slider(document.getElementById("featuredSlider"), 900);
I tried removing var foo = and it continues to work which was surprising to me. What makes it continue working?
Because references to it are kept by the timer system, because it's set up callbacks to functions (closures) that reference it via setInterval and setTimeout. So references to the necessary parts of it are kept around as long as those timers are still held.
The function it's giving to setInterval here:
setInterval(function() {
self.start = self.end;
self.end = (self.end+inStep)%limit;
self.animate(this.start < this.end ? 1 : -1);
}, 3000);
is a closure over the call to Slider. So all of the things in scope within the closure are kept around as long as the closure (function) is around. Since that timer is never cancelled, the timer system keeps those references alive. (If "closure" is unfamiliar or only slightly familiar, don't worry: Closures are not complicated.)
Is it a bad idea to not reference it globally?
No, that's fine. If it keeps working and you don't need the reference, there's no need to store a reference to it. This was quite a common pattern back when a lot of people used script.aculo.us, which used new for about half of its effects.
Writing some code, and when creating an instance of a class, something strange happens with an integer variable I have:
function Mat(x, y, spawner) {
this.x = x;
this.y = y;
this.val = 1;
this._spawner = spawner;
this.newborn = true;
this.bornTime = 0;
this.spawnTimer = setInterval("this.bornTime++; console.log(this.bornTime);", 1000);
}
Pretty cut and clear code; every second after an instance of the variable is created, it should increment the bornTime variable by 1 and log it.
Mat.prototype.update = function() {
if (this.bornTime >= 5) {
this.bornTime = null;
clearInterval(this.spawnTimer);
this.newborn = false;
console.log("Grown!");
}
}
This additional code would cause this instance to be "grown" after 5 seconds, however when I check the console, it reads that bornTime is not a number(NaN).
Why is this, and is there a solution that I am not seeing?
this inside the setTimeout code is not the same as outside (more info on MDN), so your code is actually calculating undefined++, which is NaN.
You have to create another variable, and pass a function to setTimeout instead of letting it eval a string (by the way, passing a function is supposed to be faster, and looks better):
var that = this;
this.spawnTimer = setInterval(function(){
that.bornTime++;
console.log(that.bornTime);
}, 1000);
I know this is 5 years old question but its 2018 and heres an Es6 syntax solution to avoid extra step of binding key word this.
this.spawnTimer = setInterval(() => {
this.bornTime++;
console.log(this.bornTime);
}, 1000);
I'm having a hard time understanding why this.$map and this.markers are undefined. Here's the code I'm working with and I have added comments where I expect these variables to supply a value:
(function($) {
'use strict';
var A = {
/**
* Initialize the A object
*/
init: function() {
this.$map = this.renderMap();
this.markers = this.getMarkers();
this.renderMarkers();
},
renderMap: function() {
var url = 'http://a.tiles.mapbox.com/v3/delewis.map-i3eukewg.jsonp';
// Get metadata about the map from MapBox
wax.tilejson(url, function(tilejson) {
var map = new L.Map('map', {zoomControl: false});
var ma = new L.LatLng(42.2625, -71.8028);
map.setView(ma, 8);
// Add MapBox Streets as a base layer
map.addLayer(new wax.leaf.connector(tilejson));
return function() {
return map;
};
});
},
renderMarkers: function() {
var geojsonLayer = new L.GeoJSON(null, {
pointToLayer: function (latlng){
return new L.CircleMarker(latlng, {
radius: 8,
fillColor: "#ff7800",
color: "#000",
weight: 1,
opacity: 1,
fillOpacity: 0.8
});
}
});
geojsonLayer.addGeoJSON(this.markers); // this.markers is undefined
this.$map.addLayer(geojsonLayer); // this.$map is undefined
},
getMarkers: function() {
$.getJSON("/geojson/", function (data) {
return data;
});
}
};
/**
* A interactions
*/
$(document).ready(function() {
A.init()
});
})(jQuery.noConflict());
I have spent much of the day searching and I think I'm missing something fundamental here, but I don't get it.
Neither the renderMap, nor getMarkers methods return any value, consequently their return value is undefined.
It looks like you are trying to initialize these fields from an ajax request, which is not necessarily a good idea.
What you probably ought to do is something like:
getMarkers: function(callback){
var result = this;
$.getJSON("url", function(jsonData){
result.markers = jsonData;
if(callback) callback()
});
},
which will lazily initialize the fields of the object as they become available.
NB: AJAX is asynchronous you cannot rely on this callback setting the member quickly, or indeed ever (it could fail).
This suggests you need to think a bit more about your design, and try to use callbacks more.
e.g. modify the getMarkers and renderMap functions as above to take a callback that is called after the data is stored then change init to:
init: function(){
var res = this;
var post_init_callback = function(){
if(res.$map != undefined && res.markers!=undefined){
//this will only run after both ajax calls are complete
res.renderMarkers();
}
};
this.getMarkers(post_init_callback);
this.renderMap(post_init_callback);
},
The problem here is that you call return inside another function. What you're essentially doing is defining getMarkers (the simplest example) as this:
getMarkers: function() {
$.getJSON('/geojson/', some_random_func);
}
At which point it becomes obious that getMarkers doesn't actually return anything (ergo undefined). The same goes for your renderMap function. Also in this case your "some_random_func" is defined as function(data) { return data; } but what does it return it to? Truth is that the some_random_func is called by jQuery itself, and AFAIK jQuery doesn't care at all for the return-value of it's success-function.