With this code how would I make the render staggered? I want it to render each element one by one rather than just rendering all at once. I've tried setTimeout but don't know how to implement it or whether its even the right way to do it.
renderSelected() {
var temp=[];
for (var i = 0; i < 11; i++) {
temp.push(this.selected(i))
}
return temp;
}
selected(number) {
return (<View key={number} style={styles.normal} >
<Text>{number}</Text>
</View>);
}
Update based on the answer but it still doesn't work. The code in the answer was too different since this is React Native.
renderLater(i) {
TimerMixin.setTimeout(() => {
this.selected(i);
}, 100);
}
renderSelected() {
var temp=[];
for (var i = 0; i < 11; i++) {
temp.push(this.renderLater(i))
}
return temp;
}
selected(number) {
return (<View key={number} style={styles.normal} >
<Text>{number}</Text>
</View>);
}
Based on the code, the problem is that you return temp which actually contains nothing, since renderLater returns nothing.
A solution is to create an element with a state, and depending on the state your render one or more elements. This is similar to the timer element on the reactjs page, where the state is updated every second triggering a new rendering. Instead of changing the ticker every second, you can increase a counter and in renderSelected() display all the elements up to that counter. There is no renderLater, just a this.setState() called regularly triggering a new rendering with a different number of elements.
var MyClass = React.createClass({
getInitialState: function() {
return {counter: 0};
},
tick: function() {
if (this.state.counter >= 10) return;
this.setState({counter: this.state.counter + 1});
},
componentDidMount: function() {
this.interval = setInterval(() => {this.tick();}, 50);
},
componentWillUnmount: function() {
clearInterval(this.interval);
},
render: function() {
var temp=[];
for (var i = 0; i <= 10 && i <= this.state.counter; i++) {
temp.push(this.selected(i))
}
return temp;
},
selected: function(number) {
return (<View key={number} style={styles.normal} >
<Text>{number}</Text>
</View>);
}
});
ReactDOM.render(<MyClass />, mountNode);
Live demo
You can also instead create all the elements separately from the start with each an empty render() function in the beginning and have them display something when enough time is elapsed. For that, you can still use the x.setState() function for each separate element to trigger a new rendering. This avoids erasing and redrawing already drawn elements at each tick.
Old answer:
You can do a global queue for delayed stuff.
var queue = [];
/* executes all element that have their delay elapsed */
function readQueue() {
var now = (new Date()).getTime();
for (var i = 0; i < queue.length; i++) {
if (queue[i][0] <= now) {
queue[i][1]();
} else {
if(i != 0) queue = queue.slice(i);
return;
}
}
queue = [];
}
/* Delay is in milliseconds, callback is the function to render the element */
function addQueue(delay, callback) {
var absoluteTime = (new Date()).getTime() + delay;
for (var i = 0; i < queue.length; i++) {
if (absoluteTime < queue[i][0]) {
queue.splice(i, 0, [absoluteTime, callback]);
return;
}
}
queue.push_back([absoluteTime, callback]);
}
var queueTimer = setInterval(readQueue, 10); //0.01s granularity
With that queue, if you want to render something some elements every 50ms later, then you can just do:
function renderElementLater(time, index) {
/* render */
addQueue(time, function(){ReactDOM.render(selected(index), mountNode);}):
}
for (var i = 0; i <= 10; i++) {
renderElementLater(50*i, i);
}
You can change the granularity (when the queue is read and checked) to something even less than every 10ms for finer control.
Although, I don't see what's the problem with the setTimeout. You could just do:
function renderElementLater(time, index) {
/* render */
setTimeout(function(){ReactDOM.render(selected(index), mountNode);}, time):
}
for (var i = 0; i <= 10; i++) {
renderElementLater(50*i, i);
}
Maybe your problem came that if you want to use the value of a variable that changes in a callback created in a loop, then it needs to be enclosed by another function (like I did by creating the new function renderElementLater instead of directly putting function code there).
Related
I have the following class:
// Population class
class Population {
constructor(size, target, mutationRate, minFitness) {
this.target = target;
this.size = size;
this.individuals = [];
// contains phrases (strings)
this.matePool = [];
this.mutationRate = mutationRate;
this.bestFitness = 0;
this.bestPhrase = "";
this.totalGenerations = 0;
// Stopping criterion
this.minFitness = minFitness;
}
clearMatePool () {
this.matePool = [];
}
addIndividual(newIndividual) {
this.individuals.push(newIndividual);
}
evaluate() {
let fitness = 0;
for (let i = 0; i < this.size; i++) {
fitness = this.individuals[i].fitnessFunct(this.target);
// Update best fitness and best phrase
if (fitness > this.bestFitness) {
this.bestFitness = fitness;
this.bestPhrase = this.individuals[i].getGenotype();
}
}
// Stopping criterion
if (this.bestFitness < this.minFitness) {
return true;
}
else {
return false;
}
}
buildMatePool() {
for (let i = 0; i < this.size; i++) {
let n = Math.round(this.individuals[i].getFitness() * 100);
for (let j = 0; j < n; j++) {
this.matePool.push(this.individuals[i].phrase);
}
}
}
reproduce() {
// Create new generation
for (let i = 0; i < this.size; i++) {
// Pick 2 parents
let a, b, child, midpoint;
while (true) {
// Index of parentA
a = getRandomIntInclusive(0, this.matePool.length - 1);
// Index of parentB
b = getRandomIntInclusive(0, this.matePool.length - 1);
// Be sure you have picked two unique parents (phrases)
if (this.matePool[a] === this.matePool[b]) {
continue;
}
else {
break;
}
}
// Crossover
child = this.crossover(a, b);
// Mutation
this.mutation(child);
// The new child is part of the new population
this.individuals[i] = child;
}
this.totalGenerations += 1;
}
crossover(a, b) {
let child = new Individual(this.target.length);
child.setGenotype("");
let midpoint = getRandomIntInclusive(0, this.target.length-1);
for (let i = 0; i < this.target.length; i++) {
if (i < midpoint) {
child.phrase = child.phrase + this.matePool[a].charAt(i);
}
else {
child.phrase = child.phrase + this.matePool[b].charAt(i);
}
}
return child;
}
mutation(individual) {
for (let i = 0; i < this.target.length; i++) {
// The block inside the conditional statement would be executed 1% of the time.
if(Math.random() < this.mutationRate) {
// replace char with a new random character
individual.phrase = individual.phrase.substr(0, i) + String.fromCharCode(getRandomIntInclusive(32, 128)) + individual.phrase.substr(i + 1);
}
}
}
}
I have the following DOM elements:
// Shows the current generation
var totalGenerationsHTML = $('#total-generations');
// Shows the best phrase so far
var bestPhraseHTML = $('#best-phrase');
// Shows the best fitness so far
var bestFitnessHTML = $('#best-fitness');
// Shows the result of the reproduction process (child)
var processingHTML = $('#processing');
And the following code section:
var condition = population.evaluate();
while (condition) {
// Processing
population.buildMatePool();
population.reproduce();
population.clearMatePool();
condition = population.evaluate();
}
I need to update the value of the DOM elements in each iteration. I've tried different loop implementations using setInterval() and setTimeout() but the output was out of sync. Any help will be useful.
P.S. I am new to JavaScript. Please excuse any mistakes.
while (condition) {}
Is so called blocking code, since that loop runs without giving the browser a chance to actually render the result, you wont see anything and probably the UI freezes.
You need to use either setTimeout, setInterval or as recommended for animation requestAnimationFrame
but the output was out of sync
Changing the DOM and so visible things on a page is an asynchronous Process and while Js is running, the browser does not repaint. A while loop is running JS.
For more detailed information have a look here
#philipp was right. requestAnimationFrame() is the solution.
Basically requestAnimationFrame() is:
Synchronized with browser repaint (setTimeout and setInterval are not)
Optimized for animation
Runs as fast as the screen will allow
Battery saver
The following HTML/JS/CSS snippet demonstrates the use of requestAnimationFrame().
var currentPos = -200;
var element = document.getElementById("heading");
// Animation loop
function moveRight() {
currentPos += 5;
heading.style.left = currentPos + "px";
window.requestAnimationFrame(moveRight);
if(currentPos >= 800) {
currentPos = -200;
}
}
moveRight();
h1 {
position: relative;
border: 5px solid black;
text-align: center;
width: 200px;
}
div {
width: 800px;
height: 400px;
background-color: lime;
margin: 0 auto;
overflow: hidden;
}
<!DOCTYPE html>
<html>
<head>
<title>Animation Loops</title>
</head>
<body>
<div>
<p>requestAnimationFrame()</p>
<ol>
<li>Synchronized with browser repaint</li>
<li>Optimized for animation</li>
<li>Runs as fast as the screen will allow</li>
<li>Battery saver</li>
</ol>
<p>
Traditionally to create an animation in JavaScript, we relied
on setTimeout() called recursively or setInterval() to
repeatedly execute some code to make changes to an element
frame by frame, such as once every 50 milliseconds. What we
specify as the delay (ie: 50 milliseconds) inside these
functions are often times not honoured due to changes in user
system resources at the time, leading to inconsistent delay
intervals between animation frames.
</p>
<p>
<strong>DO NOT</strong> use setInterval() or setTimeOut() for
animations, use requestAnimationFrame() instead.
</p>
<h1 id="heading">Animation Loops</h1>
</div>
</body>
</html>
I am trying to animate through CSS properties by adding "start" and "end" classNames through javascript. I have defined a bunch of these classes in CSS (for instance starting with opacity: 0 and ending with opacity: 1
I am trying to loop through all of the elements, set a start className on an element, then an end className on the element to trigger the transition. The problem is if I do that how I normally would, by the time the function finishes there would be no actual className change.
Using setTimeout with even no delay is one way to get around this, as was mentioned here but that method does not work in this instance because of my looping. I am using a closure.
Here is my JS code:
var animation = (function () {
var a = {};
a.divIndex = -1;
a.imgIndex = startHolders.length;
a.animIndex = -1;
a.divs = startHolders;
a.run = function () {
if (++a.divIndex === a.divs.length) a.divIndex = 0;
if (++a.animIndex === animations.length) a.animIndex = 0;
if (++a.imgIndex === imageElements.length) a.imgIndex = 0;
imageElements[a.imgIndex].className = animations[a.animIndex]['start'];
setTimeout(function() {
imageElements[a.imgIndex].className = animations[a.animIndex]['end'];
}, 0);
startHolders[a.divIndex].appendChild(imageElements[a.imgIndex]);
};
setInterval(a.run, 1000);
return a;
})();
What I really wanted was to be able to set all those indexes to 0 and use this instead of a (just some placeholder object) but I couldn't do this with the setTimeout because of how it changes the value of this. Another problem here is that if I put the if(++a.index) at the bottom of the code, by the time setTimeout runs the value ofa.index` has changed (I believe) Does anybody know of a workaround here or just a better way?
If you would like access to this inside of the function passed to setTimeout you can use bind.
For example:
var obj = {}
obj.divIndex = "do you wanna build a snowman?";
obj.run = function () {
console.log(this.divIndex);
}
setTimeout(obj.run.bind(obj), 1000);
I'm thinking you might want to do something like this?:
var a = {};
a.divIndex = -1;
a.imgIndex = startHolders.length;
a.animIndex = -1;
a.divs = startHolders;
a.run = function () {
if (++this.divIndex === this.divs.length) this.divIndex = 0;
if (++this.animIndex === animations.length) this.animIndex = 0;
if (++this.imgIndex === imageElements.length) this.imgIndex = 0;
imageElements[this.imgIndex].className = animations[this.animIndex]['start'];
setTimeout(function() {
imageElements[this.imgIndex].className = animations[this.animIndex]['end'];
}.bind(this), 0);
startHolders[this.divIndex].appendChild(imageElements[this.imgIndex]);
};
setInterval(a.run.bind(a), 1000);
I did some digging around on SO and could not find exactly what I am trying to achieve.
In simplistic terms I have a function like
function(){
for(i=0;i<10;i++){
setInterval(function(){ alert(i);), 1000)
}
}
What I would expect is 10 setIntervals that would alert 1 to 10 every 1 second, what happens is it would alert 10 always since 'i' is 10 at the end of for loop. How do I pass 'i' to setInterval anonymous function so that I can preserve the value of i in setInterval?
Above was a simplistic version of my actual problem. I am actually trying to do this
var timers = [];
function(obj){
//Clear any intervals
for(i=0;i<timer.length;i++){
clearInterval(timers[i]);
}
// Empty timers Array
timers = [];
for(i in obj){
//My object from the dom. This guy is what I am trying to preserve
my_obj = document.getElementById(i);
if(obj[i] === "Something"){
timers.push(setInterval(function(){
my_obj.replace_class(["Something", "Otherthing"],"Something");
}, 1000)
}
}
}
my_obj in the above code always refers to id = last 'i' in obj.
Do I make sense?
This should do the trick ;)
for(i = 1; i < 11; i++){
(function(local_i){
setInterval(function(){ console.log(local_i); }, 1000 * local_i)
})(i);
}
You must capture the variable in a closure. In your case this is
function capture(x) {
setInterval(function () {
console.log(x);
}, 1000);
}
for (var i = 0; i < 10; i++) {
capture(i);
}
or
function capture(my_obj) {
var id = setInterval(function() {
my_obj.replace_class(["Something", "Otherthing"],"Something");
}, 1000);
return id;
}
for (i in obj) {
//My object from the dom. This guy is what I am trying to preserve
my_obj = document.getElementById(i);
if (obj[i] === "Something") {
timers.push(capture(my_obj));
}
}
I want to use setInterval to animate a couple things. First I'd like to be able to specify a series of page elements, and have them set their background color, which will gradually fade out. Once the color returns to normal the timer is no longer necessary.
So I've got
function setFadeColor(nodes) {
var x = 256;
var itvlH = setInterval(function () {
for (i in nodes) {
nodes[i].style.background-color = "rgb(0,"+(--x)+",0);";
}
if (x <= 0) {
// would like to call
clearInterval(itvlH);
// but itvlH isn't in scope...?
}
},50);
}
Further complicating the situation is I'd want to be able to have multiple instances of this going on. I'm thinking maybe I'll push the live interval handlers into an array and clean them up as they "go dead" but how will I know when they do? Only inside the interval closure do I actually know when it has finished.
What would help is if there was a way to get the handle to the interval from within the closure.
Or I could do something like this?
function intRun() {
for (i in nodes) {
nodes[i].style.background-color = "rgb(0,"+(--x)+",0);";
}
if (x <= 0) {
// now I can access an array containing all handles to intervals
// but how do I know which one is ME?
clearInterval(itvlH);
}
}
var handlers = [];
function setFadeColor(nodes) {
var x = 256;
handlers.push(setInterval(intRun,50);
}
Your first example will work fine and dandy ^_^
function setFadeColor(nodes) {
var x = 256;
var itvlH = setInterval(function () {
for (i in nodes) {
nodes[i].style.background-color = "rgb(0,"+(--x)+",0);";
}
if (x <= 0) {
clearInterval(itvlH);
// itvlH IS in scope!
}
},50);
}
Did you test it at all?
I've used code like your first block, and it works fine. Also this jsFiddle works as well.
I think you could use a little trick to store the handler. Make an object first. Then set the handler as a property, and later access the object's property. Like so:
function setFadeColor(nodes) {
var x = 256;
var obj = {};
// store the handler as a property of the object which will be captured in the closure scope
obj.itvlH = setInterval(function () {
for (i in nodes) {
nodes[i].style.background-color = "rgb(0,"+(--x)+",0);";
}
if (x <= 0) {
// would like to call
clearInterval(obj.itvlH);
// but itvlH isn't in scope...?
}
},50);
}
You can write helper function like so:
function createDisposableTimerInterval(closure, delay) {
var cancelToken = {};
var handler = setInterval(function() {
if (cancelToken.cancelled) {
clearInterval(handler);
} else {
closure(cancelToken);
}
}, delay);
return handler;
}
// Example:
var i = 0;
createDisposableTimerInterval(function(token) {
if (i < 10) {
console.log(i++);
} else {
// Don't need that timer anymore
token.cancelled = true;
}
}, 2000);
I am trying to create a page which needs to preform lots of loops. Using a while/for loops cause the page to hang until the loop completes and it is possible in this case that the loop could be running for hours. I have also tried using setTimeout, but that hits a recursion limit. How do I prevent the page from reaching a recursion limit?
var looper = {
characters: 'abcdefghijklmnopqrstuvwxyz',
current: [0],
target: '',
max: 25,
setHash: function(hash) {
this.target = hash;
this.max = this.characters.length;
},
getString: function() {
string = '';
for (letter in this.current) {
string += this.characters[this.current[letter]];
}
return string;
},
hash: function() {
return Sha1.hash(this.getString());
},
increment: function() {
this.current[0] += 1;
if (this.current[0] > this.max) {
if (this.current.length == 1) {
this.current = [0, 0];
} else {
this.current[1] += 1;
this.current[0] = 0;
}
}
if (this.current[1] > this.max) {
if (this.current.length == 2) {
this.current[2] == 0;
} else {
this.current[3] += 1;
this.current[2] = 0;
}
}
},
loop: function() {
if (this.hash() == this.target) {
alert(this.getString());
} else {
this.increment();
setTimeout(this.loop(), 1);
}
}
}
setInterval is the usual way, but you could also try web workers, which would be a more straightforward refactoring of your code than setInterval but would only work on HTML5 browsers.
http://dev.w3.org/html5/workers/
Your setTimeout is not doing what you think it's doing. Here's what it's doing:
It encounters this statement:
setTimeout(this.loop(), 1);
It evaluates the first argument to setTimeout, this.loop(). It calls loop right there; it does not wait for a millisecond as you likely expected.
It calls setTimeout like this:
setTimeout(undefined, 1);
In theory, anyway. In reality, the second step never completes; it recurses indefinitely. What you need to do is pass a reference to the function rather than the returned value of the function:
setTimeout(this.loop, 1);
However, then this will be window on the next loop, not looper. Bind it, instead:
setTimeout(this.loop.bind(this), 1);
setInterval might work. It calls a function every certain amount of milliseconds.
For Example
myInterval = setInterval(myFunction,5000);
That will call your function (myFunction) every 5 seconds.
why not have a loop checker using setInterval?
var loopWorking = false;
function looper(){
loopWorking = true;
//Do stuff
loopWorking = false;
}
function checkLooper()
{
if(loopWorking == false)
looper();
}
setInterval(checkLooper, 100); //every 100ms or lower. Can reduce down to 1ms
If you want to avoid recursion then don't call this.loop() from inside of this.loop(). Instead use window.setInterval() to call the loop repeatedly.
I had to hand-code continuation passing style in google-code prettify.
Basically I turned
for (var i = 0, n = arr.length; i < n; ++i) {
processItem(i);
}
done();
into
var i = 0, n = arr.length;
function work() {
var t0 = +new Date;
while (i < n) {
processItem(i);
++i;
if (new Date - t0 > 100) {
setTimeout(work, 250);
return;
}
}
done();
}
work();
which doesn't hit any recursion limit since there are no recursive function calls.