How to update DOM in each loop iteration? - javascript

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>

Related

Javascript Iteration to display custom list with pause/stop jquery fadeIn and fadeOut

I have an array that is constantly being updated, and needs to display the items in the array 5 at a time. Sometimes there are more than 5 elements in the array, sometimes there are less. If there are more than 5 elements in the array, then I need to cycle them 5 at a time. For example, if there are 10 elements, I want to fade in 1-5, then fade out 1-5, then fade in 5-10. I have this working, and updating, however, if there are only 4 news articles available after the data update, it still fades in and out 1-4, over and over. I need to always fade in the first articles, and if there are less than the numberToShow, don't fade out, just update.
I have tried clearInterval, but that stops updating. I tried .stop().fadeOut(); but then the fade in keeps occurring. I tried .stop().fadeOut(); with .stop().fadeIn(); but the data never fades in. Should I pass the array in to display it, and cycle in there?
For testing, this is simulated with using the date. Every 8 seconds it should update the the data with an updated number. If there are 4 articles, fade in, and update the Date.now() number, but never fade out. If there are 10 articles, fade in and update each cycle.
var numberToShow = 5;
var newsArray = [];
var startRow = 0;
var endRow = 0;
function getData() {
// Simulate the data changing using date.
newsArray = [Date.now(), "News article 1", "News article 2", "News article 3", "News article 4",
"News article 5", "News article 6", "News article 7", "News article 8", "News article 9"];
showNews(numberToShow);
}
// Fade out the results for the next cycle
setInterval(function() {
$("span.text").fadeOut({
duration: 800
});
setTimeout(
function() {
getData();
},
(800)
);
}, 8000);
// Update the data
function updateData() {
getData();
setTimeout(updateData, 6000);
}
// Display the results
function showNews() {
if (endRow >= newsArray.length) {
startRow = 0;
}
endRow = startRow + numberToShow;
if (endRow >= newsArray.length) {
endRow = newsArray.length;
}
var results = "";
for (var k = startRow; k < endRow; k++) {
results += "<span class='text' style='display:none;'>" + newsArray[k] + "</span><br>";
}
startRow = startRow + numberToShow;
document.getElementById('showResults').innerHTML = results;
$("span.text").fadeIn({
duration: 800
});
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div id="showResults"></div>
While previous answer works fine - this one could be more like the case in your description...
Short description of the idea:
show list or the part of it.
if list is longer - repeat (go to step 1 in a couple of secs to show another part of the list)
when update comes - anytime - start again with new array
And working example (removed unneeded code and added button to help with tests):
var
numberToShow = 5,
newsArray = [],
startRow = 0,
endRow = 0,
$results = $("#showResults"),
timer;
function getData() {
// Simulate the data changing
newsArray = [Date.now()];
// add random number of items
var j = Math.floor(Math.random()*7)+1;
for(var i=0; i<j; i++){
newsArray.push('News article '+i);
}
// add one more item named "last"
newsArray.push('Last News article');
startCycle();
}
function startCycle() {
startRow = 0;
endRow = 0;
$results.fadeOut(800, function(){
renderList();
});
}
function renderList() {
if (endRow >= newsArray.length) {
startRow = 0;
}
endRow = startRow + numberToShow;
if (endRow > newsArray.length) {
endRow = newsArray.length;
}
var results = "";
for (var k = startRow; k < endRow; k++) {
results += "<span class='text'>" + newsArray[k] + "</span><br>";
}
startRow = startRow + numberToShow;
$results.html(results);
$results.fadeIn(800, function(){
nextCycle();
});
}
function nextCycle() {
// start cycling only if there is more results to be shown
if(newsArray.length > numberToShow){
timer = setTimeout(function(){
$results.fadeOut(800, function(){
renderList();
});
}, 4000);
}
}
// update on request
function updateData() {
clearTimeout(timer);
$results.stop();
getData();
}
// add button for tests
$results.before(
$('<button/>').text('Update now').click(function(){
updateData();
})
)
getData();
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div id="showResults"></div>
Ok - not sure what exactly you tried to do - but you will easily change my code into anything you need
...lets start with clear algorithm:
getData
fade out if list is visible ...then continue to step 3.
render next portion of items from array
fade in if list is not visible ...then go to step 5.
wait a while...
check if all items has been shown (if not - show next portion with step 2, if so - update data with step 1)
hope the the code will give you a chance to adopt it to your needs:
var
numberToShow = 5,
newsArray = [],
startRow = 0,
endRow = 0,
$results = $("#showResults"),
visible = false,
timer;
function fadeInIfNeeded(callback) {
var is_visible = visible;
visible = true;
if(is_visible){
callback();
}else{
$results.fadeIn(800, callback);
}
}
function fadeOutIfNeeded(callback) {
var is_visible = visible;
visible = false;
if(is_visible){
$results.fadeOut(800, callback);
}else{
callback();
}
}
function getData() {
// Simulate the data changing
newsArray = [Date.now()];
// add random number of items
var j = Math.floor(Math.random()*6)+2;
for(var i=1; i<j; i++){
newsArray.push('News article '+i);
}
// add one more item named "last"
newsArray.push('Last News article');
startCycle();
}
function startCycle() {
startRow = 0;
endRow = 0;
fadeOutIfNeeded(function(){
renderList();
});
}
function renderList() {
if (endRow >= newsArray.length) {
startRow = 0;
}
endRow = startRow + numberToShow;
if (endRow > newsArray.length) {
endRow = newsArray.length;
}
var results = "";
for (var k = startRow; k < endRow; k++) {
results += "<span class='text'>" + newsArray[k] + "</span><br>";
}
startRow = startRow + numberToShow;
$results.html(results);
fadeInIfNeeded(function(){
nextCycle();
});
}
function nextCycle() {
// every portion of data will be seen for 6 + 0.8 + 0.8 = 7.6 sec
timer = setTimeout(function(){
if(startRow >= newsArray.length){
// if all items has been shown - get new data (update)
getData();
}else{
// if there is more to show - fade out and render
fadeOutIfNeeded(function(){
renderList();
});
}
}, 6000);
}
getData();
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div id="showResults"></div>
Here is a modern view of how to implement your use case. Please note there
is not a single global variable and all DOM changes (side effects) occur in a single function.
You describe a constantly changing array. This code produces that array of integers
with one remarkable difference. After the data is set/retrieved, a Custom Event
is produced and fired. That event carries the changed/updated array.
function newData(data) { // Random size of array (between 1-15)
const previousLength = data.length;
// ~50% of the time the array grows, otherwise it shrinks
data.length = (Math.random() > .5)?
data.length + Math.ceil(Math.random() * (7 - 1) + 1) :
Math.ceil(Math.random() * (4 - 1) + 1);
for (let i = previousLength; i < data.length; i++) {
data[i] = Math.ceil(Math.random() * (15 - 1) + 1);
}
let dataEvent = new CustomEvent('gotData', { detail: data});
document.getElementById('showResults').dispatchEvent(dataEvent);
}
Your question describes what is the need for a custom iterator that provides no more than 5
array elements for each iteration. The following code provides a Generator function that follows
JavaScript's iterator protocol by returning a result with a .value property
containing an array of no more than 5 elements and a .done property containing a Boolean indicating if there
is no futher data. The yield statement returns data or the return statement results in
.done being set to true to indicate there is no further data. (see the MDN articles for details)
function* nextSet(data = [], numberToShow = 5) {
let current = [];
let currentStart = 0;
while (true) {
[current] = [data.slice(currentStart, currentStart + numberToShow)];
if (currentStart < data.length) {
yield current;
} else {
return;
}
currentStart += numberToShow;
}
}
With the data and iterator in place this code starts off the procession. Set up
an event listener for the custom event, then get some mock data (starting with an
empty array):
document.getElementById('showResults').addEventListener('gotData', doDOM);
newData([]);
All the DOM work is done in the event callback function below (doDOM()).
First create an iterator from the Generator Function.
Then start the interval timer so that we can repeatedly call .next() on the iterator.
Please note how dead-simple the animation actually is with
a bit of rethinking the approach to the entire problem. If result is undefined
then cancel the interval timer, mock more data and repeat the process with the updated array.
function doDOM(event) {
const data = event.detail;
const iterator = nextSet(data); // create iterator from Generator
let text = '';
let page = 0;
let interval = setInterval(()=>{
page++;
let result = iterator.next().value;
if(result) {
text = `Array size: ${data.length} (Page ${page}) -- ${JSON.stringify(result)}`;
// Dead simple animations...
$(event.target).fadeOut(1000, () => {
event.target.innerText = text;
$(event.target).fadeIn(1000);
});
} else {
event.target.innerText += " ----> getting more data..."
// all done, so kill this one
clearInterval(interval);
// Mock new data arrival
newData(data);
return;
}
}, 5000);
}
I do realize this seems to be a mile off from your question. But this answer addresses the whole
puzzle rather than just one bit.
/**
* A Generator function to produce number of data elements
*/
function* nextSet(data = [], numberToShow = 5) {
let current = [];
let currentStart = 0;
while (true) {
[current] = [data.slice(currentStart, currentStart + numberToShow)];
if (currentStart < data.length) {
yield current;
} else {
return;
}
currentStart += numberToShow;
}
}
/**
* CustomEvent Handler - fired when new data is received
*
* DOM manipulations
* All side effects are contained within one function
* #parm Event - contains detail with data
*/
function doDOM(event) {
const data = event.detail;
const iterator = nextSet(data); // create iterator from Generator
let text = '';
let page = 0;
let interval = setInterval(() => {
page++;
let result = iterator.next().value;
if (result) {
text = `Array size: ${data.length} (Page ${page}) -- ${JSON.stringify(result)}`;
// Dead simple animations...
$(event.target).fadeOut(1000, () => {
event.target.innerText = text;
$(event.target).fadeIn(1000);
});
} else {
event.target.innerText += " ----> That's it! Getting more data..."
// all done, so kill this one
clearInterval(interval);
// Mock new data arrival
newData(data);
return;
}
}, 5000);
}
// Array that is either growing or changing on each call
function newData(data) { // Random size of array (between 1-15)
const previousLength = data.length;
// ~50% of the time the array grows, otherwise it shrinks
data.length = (Math.random() > .5) ?
data.length + Math.ceil(Math.random() * (7 - 1) + 1) :
Math.ceil(Math.random() * (4 - 1) + 1);
for (let i = previousLength; i < data.length; i++) {
data[i] = Math.ceil(Math.random() * (15 - 1) + 1);
}
let dataEvent = new CustomEvent('gotData', {
detail: data
});
document.getElementById('showResults').dispatchEvent(dataEvent);
}
document.getElementById('showResults').addEventListener('gotData', doDOM);
newData([]);
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script defer src="index.js"></script>
</head>
<body>
<main id="showResults">Welcome! ----> waiting for data...</main>
</body>
</html>
Expand the code snippet to see this work.

How do I change my code to start Blinking at the same time, and still be infinite?

How do I change my code to start Blinking at the same time, and still be infinite?
function blink() {
if (!toggleSwitch) {
for (let i = 0; i < elements.length; i++) {
shapes.push(elements[i].className);
}
// Start off at the first element.
let i = 0;
let len = shapes.length;
// Do the next link
function doNext() {
let element = shapes[i];
i++;
eval(element).fillColor = eval(element).setColor;
document.getElementById(element).style.backgroundColor = (eval(element).fillColor === document.getElementById(element).style.backgroundColor) ? 'white' : eval(element).setColor;
if (i < len) {
// Don't do anything special
} else {
// Reset the counter
i = 0;
}
, myVar = setTimeout(doNext, 1000);
}
// And the code needs kicked off somewhere
doNext();
}
}
Also I can't toggle Classes because with a drag and drop I can change the background color any time during a running time so I have an object for each shape where I keep what color is the background. –
To have your page call your blink function after a certain pause first how about doing something like this?
<BODY onLoad="setTimeout('blink()', 5000);">

JavaScript While Loop issue

function draw() {
background(51);
balloon.show();
balloon.update();
score.time();
var state = 0;
if (frameCount % 100 == 0) {
blocks.push(new Blocks());
}
while (state != 1) {
for (var i = blocks.length-1; i >= 0; i--) {
blocks[i].show();
blocks[i].update();
if (blocks[i].offscreen()) {
blocks.splice(i, 1);
}
if (blocks[i].hits(balloon)) {
blocks = null
console.log("hitted");
state = 1;
}
}
}
}
So basically I just want the blocks array to stop drawing themselves on the screen once the "balloon" object hits one of them. I get no error after running it, and I get the text written on the console, so the collision is working as intended. Any ideas why the loop doesn't stop?

React Native staggered render

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).

Canvas issues with updating

<canvas id="ctx" width="500" height="500" style="border:1px solid #000000;">
<script type="text/javascript">
window.onload = function() {
var ctx = document.getElementById("ctx").getContext("2d");
function createText(words) {
var LENGTH = words.length;
var LOOPS = LENGTH;
var reader = 0;
position = 10;
while( LOOPS > 0) {
letter = words.substr(reader,1);
setTimeout(ctx.fillText(letter,position,10),100);
position += 6;
reader += 1;
LOOPS -= 1;
}
}
createText("Hello, how are you?");
}
</script>
</canvas>
I want it to do kind of like a typing animation where it paused for a fraction of a second before each letter is printed, but instead it loads all at the same time. What am I doing wrong?
So there were a few things making this not work for you, for setTimeout your ctx.fillText was being called right away, as soon as the loop hit it. To stop that you need to wrap it in a function so it will be called during the timeout.
setTimeout(function(){
// your logic here.
}, 100);
However if you do that you will run into the common issue where you will only get the last letter due to the way variable scoping works in JavaScript. To fix that you need to wrap your function in a closure and pass the values to it.
// loop start
a++;
b++;
setTimeout(
(function (a,b) {
return function () {
// some logic that uses a and b
}
})(a, b), 100);
// loop end
The last thing that happens is your timeout is set to 100.. so it will all still happen at once. Meaning every timeout is going to fire after 100ms since the creation loop is so fast. In order to solve this you need to save the delay somewhere and increase that in the loop so they happen after one another. For example the first one will be delayed 100ms, and the next one 200ms, then 300, ect.
// loop start
a++;
b++;
// Increase the time delay each loop iteration
timeDelay += 100;
setTimeout(
(function (a,b) {
return function () {
// some logic that uses a and b
}
})(a, b), timeDelay);
// loop end
Full working code and demo
Live Demo
window.onload = function () {
var ctx = document.getElementById("ctx").getContext("2d");
function createText(words) {
var LENGTH = words.length;
var LOOPS = LENGTH;
var reader = 0;
var timeDelay = 100;
position = 10;
while (LOOPS > 0) {
letter = words.substr(reader, 1);
setTimeout((function (letter, position) {
return function () {
ctx.fillText(letter, position, 10);
}
})(letter, position), timeDelay);
position += 6;
reader += 1;
LOOPS -= 1;
timeDelay += 100;
}
}
createText("Hello, how are you?");
}

Categories