Browser freezes when I execute this simple code to increment number - javascript

I am trying to create a div where the number starts from 1 and automatically increments by one - like a stopwatch. But when I run the code, it crashes or does not work.
<html>
<body>
<div></div>
<script>
var div = document.querySelector('div');
var i = 1;
while(i>0){
div.innerText=i;
i++;
}
</script>
</body>
</html>

You never let the JavaScript engine rest. As soon as it has incremented the variable, the loop starts over, and it never exits. This blocks everything else.
You need to pause between loops to let the browser do other things.
You can use the requestAnimationFrame function to do this.
const div = document.querySelector('div');
let i = 1;
const incrementCounter = () => {
div.innerText = i;
i++;
requestAnimationFrame(incrementCounter);
}
incrementCounter();
<div></div>

Since you mentioned the term "stopwatch" maybe you prefer something like this:
This increments i by 1 every second using setInterval()
The first parameter is our function, that gets executed on that interval.
The second parameter 1000 is the interval. In this case 1000 ms.
You can adjust it as needed.
You can stop the timer with:
clearInterval(incrementCounter);
const div = document.querySelector('div');
let i = 1;
const incrementCounter = setInterval(() => {
div.innerText = i;
i++;
},1000);
<div></div>
You can find more information here:
https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/setInterval

Related

How can I implement a countup that stops at all counters simultaneously?

I have implemented a countup which animates up to the data-target. How can I adjust the counter so that all counters stop at the same time?
Or is there a better way to implement this?
function animationEffect(){
const counters = document.querySelectorAll('.counter');
const speed = 2000;
counters.forEach((counter) => {
const updateCount = () => {
const target = + counter.getAttribute('data-target');
const count = + counter.innerText;
const inc = target / speed;
if(count < target) {
counter.innerText = Math.ceil(count + inc);
setTimeout(updateCount, 1);
} else {
counter.innerText = target;
}
}
updateCount();
});
}
<div class="counter" data-target="299" onmouseover="animationEffect()">0</div>
<div class="counter" data-target="1299" onmouseover="animationEffect()">0</div>
<div class="counter" data-target="99" onmouseover="animationEffect()">0</div>
Updated Answer
Looks like I misunderstood what you mean by at the same time, but using setTimeout like this is still a really bad practice. Here is my take on it:
const counters = Array.from(document.querySelectorAll(".counter"));
const counterValues = [];
const speed = 500;
const updateCount = (counter, target, count, index) => {
const inc = target / speed;
counterValues[index] = count + inc;
if (count < target) {
counter.innerText = Math.floor(counterValues[index]);
} else {
counter.innerText = target;
}
};
counters.forEach((counter, index) => {
counterValues.push(0)
const interval = setInterval(() => {
const target = +counter.getAttribute("data-target");
const count = counterValues[index];
if (target !== count) {
updateCount(counter, target, count, index)
} else {
clearInterval(interval);
}
}, 1)
});
<div class="counter" data-target="32">0</div>
<div class="counter" data-target="3000">0</div>
<div class="counter" data-target="10">0</div>
Please check my old answer for why setInterval is better for this problem.
Other than that here is what is going on in this snippet:
I defined an array called counterValues which will hold the count values in a float format. In your example when you store the ceiled number to be used in your calculation later again, you are not doing a correct calculation.
If I remember correct one of your counters must be increased by 0.145, while you are incrementing it by 1 every time. By the way flooring is the right method for this as it won't reach to the target until it really reaches to it. If the target is 10, but your counter is at 9.5 it will be written as 10 in your code although it is not there yet.
updateCount is almost the same function. It now uses floor. It updates the amount of counter by using the previous floating value and then while writing to the DOM, it uses the floored value.
For each counter, it adds an interval which will update the counter and cancel itself whenever the counter reaches to the target value.
I used a shared state and indexed calculation for simplicity.
Old Answer
If you paste this code to the top of your code and run, when you log window.activeTimers you will see that there are hundreds of timers defined. It is because every time updateCount is called you are setting a new timer to updateCount. Although your animationEffect function is like the main function of your program, if you invoke it every time I mouseover your counters, it will set new timers which means faster update on your counters every time. To sum up, you don't have control at all at the moment.
For periodic calls you should use setInterval. It takes a function and a delay parameter (there are also optional args. You can check the docs). It repeatedly calls a function or executes a code snippet, with a fixed time delay between each call (From Mozilla docs). It also returns an interval ID so that you can cancel it later (meaning we can control it).
So in your case, just to stop in a desired moment first thing you should do is to get rid of onmouseover calls to the animationEffect and add a button to stop execution of updateCounters.
<div class="counter" data-target="299">0</div>
<div class="counter" data-target="1299">0</div>
<div class="counter" data-target="99">0</div>
<button id="stop">Stop</button>
After assigning variables counters and speed we can define an array to hold the intervals' IDs to cancel them later.
const counters = document.querySelectorAll(".counter");
const speed = 2000;
const cancelButton = document.getElementById('stop')
const countIntervals = [];
cancelButton.addEventListener('click', () => {
countIntervals.forEach(interval => clearInterval(interval))
})
As you can see I defined an event listener to our button. When you click on it it will iterate the interval IDs in countIntervals and clear those intervals. For simplicity, I didn't implement features like pausing and resetting and tried not to change your code much. You can experiment later.
Now first thing you should do is to comment or delete setTimeout line in your if statement. Then we will push the returned interval ID to our array countIntervals:
counters.forEach((counter) => {
const updateCount = () => {
const target = +counter.getAttribute("data-target");
const count = +counter.innerText;
const inc = target / speed;
if (count < target) {
counter.innerText = Math.ceil(count + inc);
// setTimeout(updateCount, 1);
} else {
counter.innerText = target;
}
};
countIntervals.push(setInterval(updateCount, 10))
});
Now your counters will stop once you hit the Stop button. I ignored the speed feature, but If you understand setInterval you can implement it easily.
I don't know if it helps, but i got it working when i change this line:
const inc = target / speed;
to this:
const inc = 1/(speed / target);
AND get rid of the Math.ceil() because this const speed = 2000; which is actually a step creates floating values. Maybe try smaller speed value
EDIT
A little more tweaking and we have a smooth answer without floating values >:D
function animationEffect(){
if(animationEffect.called){return null}
animationEffect.called=true
//these 2 lines prevent this function from being called multiple times to prevent overlapping
//the code works without these lines though so remove them if u don't want them
const counters = document.querySelectorAll('.counter');
const incrementConstant = 2; //the higher the number the faster the count rate, the lower the number the slower the count rate
const speed = 2000;
counters.forEach((counter) => {
const updateCount = () => {
const target = + counter.getAttribute('data-target');
counter.storedValue=counter.storedValue||counter.innerText-0; //saving a custom value in the element(the floating value)
const count = + counter.storedValue; //accessing custom value(and rounding it)
const inc = incrementConstant/(speed / target); //the math thanks to #Tidus
if(count < target) {
counter.storedValue=count+inc
counter.innerText = Math.round(counter.storedValue);
setTimeout(updateCount, 1);
} else {
counter.innerText = target;
}
}
updateCount();
});
}
<div class="counter" data-target="299" onmouseover="animationEffect()">0</div>
<div class="counter" data-target="1299" onmouseover="animationEffect()">0</div>
<div class="counter" data-target="99" onmouseover="animationEffect()">0</div>

setInterval is not working with innerHTML?

I am trying to create a small place on my website to show the seconds running. but my javascript function is not running. At the same time, it does not show any error. This is my javascript code:
function stoptime() {
let count = 0;
count = count+1;
stop = document.getElementById("time");
stop.iinnerHTML = count.value;
setInterval(stoptime, 1000);
I can use this code with document.write function, but it did not give the count ++ value, instead of that it showed many single value of count. so I tried to use it with innerHtml, but it not running.
please tell, what is the reason and the correct code?
HTML
<span id="time"></span>
JS
let count = 0;
function stoptime() {
count++
stop = document.getElementById("time");
stop.innerHTML = count;
}
setInterval(stoptime, 1000);
The count variable is initialized in the same function every time. Its value gets reset to 1 each time the function is called. Moving the count declaration outside the function should fix your issue.
Also you don't need count.value
let count = 0;
function stoptime() {
count = count+1;
stop = document.getElementById("time");
stop.innerHTML = count;
}
setInterval(stoptime, 1000);
<div id="time"></div>

Javascript : setInterval and ClearInterval on one click?

I'm trying to have the numbers 1-6 quickly flash on the screen when a button is clicked, then stop and display the random generated number. If I put clearInterval into the function it just displays the random Number and doesn't display the flashes up numbers before hand.
HTML
<div id='dice'>
<div id='number'>0</div>
</div>
<div id='button'>
<button onclick='roll()'>Roll Dice</button>
</div>
JAVASCRIPT
let rollButton = document.querySelector('button');
let diceNumber = document.getElementById ('number');
function roll(){
diceSides = [1,2,3,4,5,6];
var i = 0;
let shuffleDice = setTimeout(function(){
diceNumber.innerHTML = diceSides[i++];
if(diceNumber == diceSides.length){
i = 0;
}
}, 500);
let random = Math.floor(Math.random() * 6);
diceNumber.innerHTML = random;
}
Maybe this works for you
hint: you can use i as a counter variable OR use your approach (define an array with numbers and use index to find them and put them in number tag)
HTML:
<div id='dice'>
<div id='number'>0</div>
</div>
<div id='button'>
<button onclick='roll()'>Roll Dice</button>
</div>
JS:
let rollButton = document.querySelector('button');
let diceNumber = document.getElementById ('number');
function roll(){
diceSides = [1,2,3,4,5,6];
var i = 6;
let shuffleDice = setInterval(function(){
diceNumber.innerHTML = i;
if(i == 0){
clearInterval(shuffleDice);
let random = Math.floor(Math.random() * 6);
diceNumber.innerHTML = random;
}
i--;
}, 1000);
}
CodePen
clearInterval
setInterval
setTimeout
SetTimeout only executes once. Also your variable is changed by the interval!
After its over, you have to clear the interval with clearInterval.
let rollButton = document.querySelector('button');
let diceNumber = document.getElementById ('number');
function roll(){
diceSides = [1,2,3,4,5,6];
var i = 0;
var shuffleDice = setInterval(function(){
diceNumber.innerHTML = diceSides[i++];
//use i
if(i == diceSides.length){
i = 0;
//clear
clearInterval(shuffleDice);
// moved because the interval will change it
let random = Math.floor(Math.random() * 6);
diceNumber.innerHTML = String(random);
}
}, 500);
}
<div id = 'dice'>
<div id = 'number'>0</div>
</div>
<div id = 'button'>
<button onclick = 'roll()'>Roll Dice</button>
</div>
I suggest something like this:
var diceNumber = document.getElementById ('number');
const diceSides = [1,2,3,4,5,6];
function roll(){
let arr = [...diceSides,diceSides[Math.floor(Math.random() * diceSides.length)]];
cycle(diceNumber,arr,200);
}
function cycle(element,arr,delay) {
element.innerText=arr.shift();
if (arr.length > 0)
setTimeout(cycle,delay,element,arr,delay);
}
Given that you know precisely the list you want to iterate through, and thus the fixed number of iterations you want to execute, this seems cleaner and more concise than setInterval, which you would have to explicitly stop when you reach the end of the list. I like setInterval for things that will run an indeterminate period of time (for instance, until stopped by a user action).
What this says is: "Take this list of numbers (the last one being the random one). Show the first one and remove it from the list. Then wait a while and do it again, until you're out of numbers."
A couple other fine points here:
innerText rather than innerHTML, since you're just setting string content.
You want to use your random number as a key against your array of die faces, not directly - used directly, you get [0-5], not [1-6].
Use the length of your array, rather than hard-coding '6' - always avoid magic numbers when you can. By referring everything back to your constant array, changing the values on the faces of the die (or the number of them) becomes trivial; just change the array and everything else will still work.
Normally, there are stack-size concerns with recursive code. In this case, that wouldn't be a problem because of the small size of the array. But beyond that, the fact that the recursion is going through setTimeout means that each one is a seprate entry on the queue, and the prior isn't waiting for the next to complete before it can exit.

How to count up starting at any specified number in javascript?

Say I have a number in a div:
<div id="value">100</div>
And I have this javascript acting on it:
function animateValue(id){
var obj = document.getElementById(id);
var current = obj.innerHTML;
setInterval(function(){
current++;
},1000);
}
animateValue(value);
To my understanding, this script translates to:
• Grab the HTML element called (in this case) "value" and store it in "obj"
• Take the inner html from obj and store it in "current"
• Take the "current" variable and increment it every second
It was supposed to take the content of #value and increment it up by one every second, but instead it does nothing. How do I solve this? What have I done wrong? As you can tell, I am not experienced in javascript, so go easy on me.
You changed the current number but never updated the innerHTML of the obj, which is what is actually displayed. If you set obj.innerHTML = current; right after current++ it should do something for you.
And change animateValue(value); to animateValue('value'); (thanks, #nevermind)
I'm not sure if you wanted to update the contents of the DIV. Maybe this is what you wanted?
function animateValue(id){
var obj = document.getElementById(id);
var current = parseInt(obj.innerHTML);
setInterval(function(){
current++;
// Update the contents of the element
obj.innerHTML = current;
},1000);
}
animateValue('value');
http://jsfiddle.net/L1q5t9gz/
Use jQuery. It's easier... The other answers are more complex, this is an easy one.. No parse or something like that.... Simpler code, more effective.
So, It's more simple than you think: First... check the current value, then add and print
$(function(){
//Get Current HTML
var currentValue = $("#value").html();
//do this every 1000 ms (1 second)
setInterval(function(){
//add the value to the current
var newValue = currentValue++
// print the new value
$("#value").html(newValue);
},1000)
})
Here is the JSFiddle example solving your problem: http://jsfiddle.net/rszczypka/f5jfn5bo/
the html part:
<div id="value">100</div>
the javascript part:
function animateValue(id){
var obj = document.getElementById(id);
var current = parseInt(obj.innerHTML);
setInterval(function(){
obj.innerHTML = current++;
},1000);
}
animateValue('value');

Appending to empty element does not work

I want to display the characters of a string, stored in an array, one by one.
When I call threadsleep(a) using the following code: http://jsfiddle.net/thefiddler99/re3qpuoo/, it appears all at once.
The problem lies somewhere here I presume
for (var i = 0; i < str.length; i++) {
$('#hello').append(str[i])
alert("foo")
sleep(500)
};
The alert shows that everything is working properly except that the interval between each is not present.
I cannot figure out why.
Note: Sleep is a function I have defined that works for the set amount of time
JavaScript is single threaded. It is too busy running round and round your while loop to perform a repaint of the page.
Don't try to write a sleep function. The language isn't suited to it and it just chomps CPU. Rewrite your code to use setInterval or setTimeout instead.
var i = 0;
next();
function next() {
$('#hello').append(str[i]);
i++;
if (i < str.length) {
setTimeout(next, 500);
}
}
Just try with setTimeOut recursion call.
Demo
<script>
var text="This string will be display one by one.";
var delay=500;
var elem = $("#oneByOne");
var addTextByDelay = function(text,elem,delay){
if(!delay){
delay = 500;
}
if(text.length >0){
//append first character
elem.append(text[0]);
setTimeout(
function(){
//Slice text by 1 character and call function again
addTextByDelay(text.slice(1),elem,delay);
},delay
);
}
}
addTextByDelay(text,elem,delay);
</script>

Categories