Why in the following code I see the whole page at once ? Thanks !
HTML:
<div></div>
CSS:
div {
width: 100px;
height: 300px;
border: 1px solid black;
text-align: center;
}
Javascript:
$(function() {
for (var i=0; i<15; i++) {
sleep(100);
$("div").append("<span>--- " + i + " ---<br /></span>");
}
function sleep(milliseconds) {
var start = new Date().getTime();
for (var i = 0; i < 1e7; i++) {
if ((new Date().getTime() - start) > milliseconds){
break;
}
}
}
});
Because Javascript on web browsers is single-threaded (although that's changing; see below) and virtually no browser updates its UI while the (main) Javascript thread is busy. All your sleep function does is block everything for 100ms, it doesn't let the browser do anything else like update its UI (e.g., doesn't "yield").
There are a couple of ways to do what you're trying to do:
Use the new web workers stuff; but note that it's not widely-supported yet.
Make your loop a function that calls itself via setTimeout. That way, your code is yielding to the browser and letting it update its UI.
Here's a simple example of how you might apply #2 above to your code:
$(function() {
var i;
doOne();
function doOne() {
$("div").append("<span>--- " + i + " ---<br /></span>");
if (i++ < 15) {
setTimeout(doOne, 0); // <== Won't really be zero, browsers clamp it at ~10
}
}
});
If you have a lot of loop iterations (e.g., a couple of hundred instead of 15), it may well be worth doing a chunk of them on each iteration rather than yielding on each iteration; the yield takes a measureable time (typically ~10-15ms).
You need to hand over some processing time to the UI since javascript is single threaded, like this:
$(function() {
function appendDiv(i) {
$("div").append("<span>--- " + i + " ---<br /></span>");
if(i < 14)
setTimeout(function() { appendDiv(i+1); }, 100);
}
appendDiv(0);
});
You can see a demo here
You could also use an interval for what you want, like this:
$(function() {
var i = 0;
var interval = setInterval(function() {
$("div").append("<span>--- " + i++ + " ---<br /></span>");
if(i == 15) clearInterval(interval);
}, 100);
});
Related
Im having trouble getting javascript timers to work as intended when updating an old site to be more modern, it used to use iframes so refreshing the timers wasn't an issue, but now i want to use ajax to reload the div containing the timer, however it keeps counting at double speed, ive been reading about using clearTimeout which seems to be the solution im looking for, however i just can not get it to work at all, could anyone give me any advice on where to look to stop the times counting at double speed
<script type="text/javascript">
var var1 = 60; // time in seconds to count down
function timer1() {
if (var1 > 0) {
var1 = var1 - 1;
}
else {
var1 = "<font color=\"#90ee90\">GO</font>";
}
var countdown = document.all ? document.all["timer1"] : document.getElementById ? document.getElementById("timer1") : "";
if (countdown) {
countdown.innerHTML = var1 + " ";
setTimeout( 'timer1()', 1000 );
}
}
timer1();
</script>
I have already attempted var stop= setTimeout('<?=$id; ?>()',1000);
And then calling clearTimeout(stop);
However this seemed to completely ignore clearTimeout so i felt like I must have been doing it wrong
You probably want to use setInterval() in this case.
In case you want types: TS Playground link
#timer1 {
background-color: #000;
color: #fff;
font-family: monospace;
font-size: 2rem;
padding: 1rem;
}
<div id="timer1"></div>
<script type="module">
const tickDelay = 1000;
let seconds = 5;
let timerId = 0;
// To stop the timer, just call this function
function stopTimer () {
clearInterval(timerId);
console.log('Timer done');
}
function tick () {
const element = document.getElementById('timer1');
if (!element) return;
if (seconds > 0) {
element.textContent = String(seconds);
seconds -= 1;
}
else {
element.style.setProperty('color', '#90ee90');
element.textContent = 'GO';
stopTimer();
}
}
timerId = setInterval(tick, tickDelay);
</script>
I am working on a very basic animation where classes are removed from list items once they have been loaded and appended to the document. The issue I am having is with the animation itself. I want the animation to execute in a stepped manner like the image below...
What actually though is that the loop runs completely, console.log messages get output in a stepped manner, but the classes are all removed at the same time once the loop has completed. How can I change this behavior? Why would the console.log messages be stepped but the classList.remove functionality is not executed at the same time?
Here is my code...
function sleep(milliseconds) {
var start = new Date().getTime();
for (var i = 0; i < 1e7; i++) {
if ((new Date().getTime() - start) > milliseconds){
break;
}
}
}
/**/
function showListItems() {
var listItems = document.querySelector('.idList');
var n = 20;
var c = 0;
var itemArray = new Array();
for (var i = 0; i < listItems.children.length; i++) {
var item = listItems.children[i];
if (item.classList && item.classList.contains('idList__item--hide')) {
console.log('Item: ', item);
itemArray[c] = item;
c++;
}
}
console.log('Item Array: ', itemArray);
itemArray.forEach(function(el, index) {
sleep(n);
el.classList.remove('idList__item--hide');
console.log("EL[" + index + "]: ", el);
});
}
I realize this code may look over complex and perhaps it is, but I have tried about everything I can think of. I have tried using promises, for loops, now the forEach method.
Thank you.
The browser doesn't update until javascript finishes running. Your script doesn't relinquished control back to the browser while sleeping so the browser can't update. This is exactly what setTimeout is for.
Change
itemArray.forEach(function(el, index) {
sleep(n);
el.classList.remove('idList__item--hide');
console.log("EL[" + index + "]: ", el);
});
to
itemArray.forEach(function(el, index) {
const ms = n * (index + 1);
setTimeout(function() {
el.classList.remove('idList__item--hide');
console.log("EL[" + index + "]: ", el);
}, ms);
});
We're scheduling all the remove calls in advance which is why we're multiplying n by index + 1.
And in case you're interested, here is the code I used to test sleep vs setTimeout.
https://codepen.io/rockysims/pen/jeJggZ
This may be a bad way but it should solve your problem.
You can use setTimeout() in forEach, and use index to change time parameter, like it:
itemArray.forEach(function(el, index) {
setTimeout(function(){
el.classList.remove('idList__item--hide')
},500*(index+1))
});
I used Jquery each and setTimeout functions for chaining the animations.
$( "li" ).each(function( index ) {
var listItem = $( this );
setTimeout(function() {
listItem.animate({
opacity: 1
}, 500);
}, (index + 1) * 500);
});
ul {
padding : 20px;
}
ul li {
list-style : none;
height : 50px;
background-color : lightgreen;
margin : 20px;
opacity : 0;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<ul>
<li>Hello world 1</li>
<li>Hello world 2</li>
<li>Hello world 3</li>
<li>Hello world 4</li>
</ul>
I would like to fill an element with dots in random order. I have managed to write all the functionality, but I am not satisfied with the execution speed.
If I add all of the points using a while loop, the points just seem to appear all at the same time.
Therefore I add points one by one using a function that I call recursively with a timeout. This, on the other hand, appears too slow. Is there any chance to run a sequence of actions slower than in a loop but faster than setTimeout() can?
var dotCellSize;
var initialOffset;
var slotsHorizontally;
var slotsVertically;
var container;
var redDots;
var dots;
var newDotElement = $('<div class="dot">');
function randomInteger(min,max)
{
return Math.floor(Math.random() * (max - min + 1)) + min;
}
function addDots()
{
if (!dots.length)
return;
var dotIndex = randomInteger(0, dots.length - 1);
var dot = dots[dotIndex];
dots.splice(dotIndex, 1);
var column = dot % slotsHorizontally;
var row = Math.floor(dot/slotsHorizontally);
var position = {
left: initialOffset + column*dotCellSize,
top: initialOffset + row*dotCellSize
};
var dotElement = newDotElement.clone().css(position);
if (-1 != redDots.indexOf(dot))
dotElement.addClass('red');
dotElement.appendTo(container);
setTimeout(function() {
addDots();
}, 1);
}
function generateDots(dotContainer, cellSize, numberOfRedDots)
{
container = dotContainer;
dotCellSize = cellSize;
dots = [];
redDots = [];
container.find('div.dot').remove();
numberOfRedDots = typeof numberOfRedDots !== 'undefined' ? numberOfRedDots : 3;
initialOffset = Math.floor(dotCellSize/2);
slotsHorizontally = Math.ceil(container.width()/dotCellSize);
slotsVertically = Math.ceil(container.height()/dotCellSize);
var numberOfSlots = slotsHorizontally*slotsVertically;
while (dots.length < numberOfSlots)
dots.push(dots.length);
while (redDots.length < numberOfRedDots)
{
var newRedDot = randomInteger(0, numberOfSlots - 1);
if (-1 == redDots.indexOf(newRedDot))
redDots.push(newRedDot);
}
addDots();
}
generateDots($('.dot-container'), 18, 15);
.dot {
width: 4px;
height: 4px;
border-radius: 50%;
background-color: #C0E3EA;
position: absolute;
z-index: 1;
}
.dot.red {
background-color: #EF3D48;
}
.dot-container {
width: 420px;
height: 280px;
background-color: #333;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class="dot-container"></div>
Unfortunately, not really, this is because of how the browser engine decides to repaint the screen. Without the timeout, the browser engine recognizes it's going to do a bunch of updates (adding the dots to the DOM). Because repainting the screen is expensive, it waits to do as much as possible at one time, and, in your case, all of the dots show up at once. With the timeout added, each call to your function gets "deferred" for future execution.
This may or may not happen "right away" and is non-trivial to explain in detail so I would recommend watching this video https://www.youtube.com/watch?v=8aGhZQkoFbQ which explains the JS event loop or read some articles on browser reflow:
Minimizing browser reflow
What is Layout Thrashing?
Without changing much of what you've already done, one solution is to batch a few of the dots to be drawn together. I've added a for loop to your function which will make five dots get drawn together. Adjust this to 10, 20, or higher and you'll see the dots get painted even faster. I hope there is a number that you'll find suitable. I understand you may want to just speed up the drawing of every dot individually, but bear in mind that screens have refresh rates, so the faster you want the routine to finish the more they will appear in batches any way.
var dotCellSize;
var initialOffset;
var slotsHorizontally;
var slotsVertically;
var container;
var redDots;
var dots;
var newDotElement = $('<div class="dot">');
function randomInteger(min,max)
{
return Math.floor(Math.random() * (max - min + 1)) + min;
}
function addDots()
{
if (!dots.length)
return;
for (let i = 0; i < 5; i++) {
var dotIndex = randomInteger(0, dots.length - 1);
var dot = dots[dotIndex];
dots.splice(dotIndex, 1);
var column = dot % slotsHorizontally;
var row = Math.floor(dot/slotsHorizontally);
var position = {
left: initialOffset + column*dotCellSize,
top: initialOffset + row*dotCellSize
};
var dotElement = newDotElement.clone().css(position);
if (-1 != redDots.indexOf(dot))
dotElement.addClass('red');
dotElement.appendTo(container);
}
setTimeout(function() {
addDots();
}, 1);
}
function generateDots(dotContainer, cellSize, numberOfRedDots)
{
container = dotContainer;
dotCellSize = cellSize;
dots = [];
redDots = [];
container.find('div.dot').remove();
numberOfRedDots = typeof numberOfRedDots !== 'undefined' ? numberOfRedDots : 3;
initialOffset = Math.floor(dotCellSize/2);
slotsHorizontally = Math.ceil(container.width()/dotCellSize);
slotsVertically = Math.ceil(container.height()/dotCellSize);
var numberOfSlots = slotsHorizontally*slotsVertically;
while (dots.length < numberOfSlots)
dots.push(dots.length);
while (redDots.length < numberOfRedDots)
{
var newRedDot = randomInteger(0, numberOfSlots - 1);
if (-1 == redDots.indexOf(newRedDot))
redDots.push(newRedDot);
}
addDots();
}
generateDots($('.dot-container'), 18, 15);
.dot {
width: 4px;
height: 4px;
border-radius: 50%;
background-color: #C0E3EA;
position: absolute;
z-index: 1;
}
.dot.red {
background-color: #EF3D48;
}
.dot-container {
width: 420px;
height: 280px;
background-color: #333;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class="dot-container"></div>
Quickly profiling on my i7 3770k revealed that newDotElement.clone().css(position) took about .1 seconds. If you are running at 30 FPS, your frame time is .03 seconds. So you can see that Jquery clone is somewhat of a bottleneck.
However, your initial approach of drawing all the dots at once is sound, if you flag their styles to be "hidden". Then, when all the dots are added to the DOM, but are not visible, retrieve a list of their nodes (forgive the vanilla JS):
Array.from(document.getElementsByClassName("dot-container")[0].childNodes);
Now you can iterate over them and simply change their visibility style from "hidden" to "visible". As skyline3000 points out, the limit with setTimeout (or even requestAnimationFrame) is in the browser, and looping and setting one dot per iteration will take a little over 1 frame, which is actually a little slow. So you can write yourself a little abraction which per call will set a certain number of elements' visibility styles to "visible". By adjusting the quantity of dots you make visible per call, you will speed up or slow down the animation.
function showDots() {
var list = Array.from(document.getElementsByClassName("dot-container")[0].childNodes);
function draw(q) {
var e;
for (var i = 0; i < q; i++) {
if (list.length == 0) {
return;
}
e = list.shift();
e.style.visibility = "visible";
}
}
function callback() {
if (list.length == 0) {
return;
}
draw(4);
setTimeout(callback);
}
callback();
}
I would like to use a function that prints a string slow in sequence on a page once the page has loaded. Something like this:
"W, E, L, C, O, M, E , T, O, M, Y, P, A, G, E"
Any suggestions where i can find a guide?
Thanks in advance!
Try with SetInterval function .split the string with space the apply the array with setInterval with time delay
var a = "WELCOMETOMYPAGE";
window.onload = function() {
var arr = a.split('');
var i = 0;
var timer = setInterval(function() {
if (arr.length > i) {
document.body.innerHTML += '<span>' + a[i] + ',</span>';
}
i++;
}, 500);
}
span {
transition: all 0.5s ease;
}
You can probably achieve this with a clever use of setInterval
var welcome = "Welcome to my page";
var index = 1;
var interval = setInterval(function(){
if(index <= welcome.length) {
console.log(welcome.substr(0,index))
index++;
} else {
clearInterval(interval);
}
}, 500);
But of course instead of console.log update your page element. I'm using console.log so that you can quickly test this code.
So substitute:
console.log(welcome.substr(0,index))
With:
document.getElementById("element_id").textContent=welcome.substr(0,index)
Alternatively you could use a library like Greensock which is very good at sequencing stuff. In fact they even have a function that does exactly that:
https://greensock.com/
UPDATE:
Of course all of this has to be inside a window onload function to make sure it executes after the page has loaded:
window.onload = function() {
var welcome = "Welcome to my page";
var index = 1;
var interval = setInterval(function(){
if(index <= welcome.length) {
document.getElementById("element_id").textContent=welcome.substr(0,index)
index++;
} else {
clearInterval(interval);
}
}, 500);
}
SUMMARY:
Study
How to execute javascript code after the page has loaded
How to use setInterval in javascript
How to update dom elements using javascript (study jquery as well while you at it)
I'm trying to make a few things scroll down the screen in javascript, however, upon execution, it just says a little and displays everything at once. So it's not clearing with the $("#Menu").html('') function and the setTimeout(function {},500) is just setting a timeout for the entire page instead of the code segment.
var MenuData = [
{'Name':'pictures','x':'30'},
{'Name':'blog','x':'50'},
{'Name':'contact','x':'42'}
]
;
var PositionArray = new Array();
$(document).ready(function () {
for (var count = 0; count < 1000; count++) {
$("#Menu").html('');
if (PositionArray[count] != null) {
PositionArray[count]++;
} else {
PositionArray[count] = 0;
}
setTimeout(function () {
for (var i in MenuData) {
$("#Menu").append('<div style="position:relative; left:' + MenuData[i].x + 'px; top:' + PositionArray[i] + 'px; ">123</div>');
}
}, 500);
}
});
Here's the fiddle: http://jsfiddle.net/LbjUP/
Edit: There was a little bit of error in the code that doesn't apply to the question. Here's the new one: http://jsfiddle.net/LbjUP/1/, I just moved PositionArray[count] to the setTimeout function as PositionArray[i]
As stated in the comments, you are creating 1000 timeouts for 500 ms at the same time - after 500 ms all of them will be executed. What you want is to increase the timeout for every scheduled function:
setTimeout(function() {
// do something
}, count * 500);
However, creating 1000 timeouts at once is not a that good idea. It would be better to use setInterval or call setTimeout "recursively" until a count of 1000 is reached, so that you only have one active timeout at a time.
var count = 0;
function update() {
// do something
if (++count < 1000)
setTimeout(update, 500);
// else everything is done
}
update();
Also, if you intend to create timeouts in a loop, be sure to be familiar with closures and their behavior when accessing counter variables after the loop ran.
Try
function recurse ( cnt ) {
for (var i in MenuData) {
$("#Menu").append('<div style="position:relative; left:' + MenuData[i].x + 'px; top:' + PositionArray[i] + 'px; ">123</div>');
}
if (cnt < 1000){
setTimeout(function () { recurse(cnt + 1); }, 500);
}
}
$("#Menu").html('');
if (PositionArray[count] != null) {
PositionArray[count]++;
} else {
PositionArray[count] = 0;
}
recurse(0);
You can also use setInterval
let i = 0;
const interval = setInterval(() => {
console.log(i);
i++;
if (i >= 10) {
clearInterval(interval);
}
}, 1000);`