I have the following javascript:
var i = 0;
var intervalCounter = setInterval(function(){
[... CODE HERE ...]
if (i >= (arrayLength - 1)) {
clearInterval(intervalCounter);
}
else {
i++;
}
}, 200);
But, (you've guessed it), clearInterval(intervalCounter); doesn't clear the interval.
I could understand this, if the if condition were
if (i === (arrayLength - 1))
but how can the javascript engine repeatedly miss that i is greater than (arrayLength-1)?
The following code works correctly, displaying 0 through 9 on the console:
var a = Array (10),
i = 0,
intervalCounter = setInterval(function(){
console.log (i);
if (i >= (a.length - 1)) {
clearInterval(intervalCounter);
}
else {
i++;
}
}, 200);
Your problem must be with the value of i or arrayLength, try displaying those variables at each iteration
It turns out the answer lay elsewhere, further down the script - I'll include it for the sake of completeness.
From various tests, it looked like the error had something to do with speed of script execution. The script controls a fast animation which occurs on page load, so I changed the event listener from
window.addEventListener('load',activateAnimation,false);
to
window.addEventListener('DOMContentLoaded',activateAnimation,false);
That enabled the animation to start sooner - and for more of the animation to work before it failed - but it didn't fix the problem - the setInterval was still running and hitting blanks infinitely after a certain number of runs.
Then it occurred to me that because the animation is taking place within a live nodelist... maybe the next part of the script (which removes elements from that nodelist) was starting too soon, leaving the setInterval still trying to work on nodelist elements that were no longer present in the document.
My solution was to increase the setTimeOut to create a bigger delay before the next part of the script commenced, but also to add an additional clearInterval as the top line of the script running within the setInterval:
if ((i + 1) > elements.length) {clearInterval(enterHashtag);}
That way, not only would the setInterval clear itself if the counter reached the end of the nodelist's initial length, but it would also clear itself if the next part of the script had just removed from the nodelist the element the setInterval was about to go to work on.
Related
I am trying to slow down an animation of a picture shrinking so that you can actually see it shrink instead of just jump to a smaller size, but what instead happens is that my console log updates as it should-with intervals in-between each update, but the image on the screen waits until the for loop is done and then jumps to the end size instead of gradually getting smaller. Is there something wrong with my code or am I missing some behind the scenes stuff on how the page updates?
function sleep(milliseconds) {
const date = Date.now();
let currentDate = null;
do {
currentDate = Date.now();
} while (currentDate - date < milliseconds);
}
function makeSmaller(){
for (let i = 100; i>50; i--){
rockButtonImage.style.width = i+'%';
console.log(rockButtonImage.style.width);
sleep(50);
}
}
Modern browsers avoid unnecessary renderings of a page and wait while a JavaScript execution iteration is complete before doing a rendition.
The sleep() function in the provided code does not really cause a browser to sleep but rather to do a processor-intensive computation, forbidding the browser from rendering anything.
You can try using the setTimeout() function instead, e.g.:
var i = 100;
function makeSmaller(){
console.log(rockButtonImage.style.width);
rockButtonImage.style.width = i+'%';
i--;
if (i > 50) {
setTimeout(makeSmaller, 50);
}
}
You might also find it useful to rely on CSS animations or CSS transitions instead of JavaScript for such visual effects as JS is not nearly as efficient as they are.
See also:
More on setTimeout: https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/setTimeout
More about the CSS animations: https://developer.mozilla.org/en-US/docs/Web/CSS/animation
You have to dynamically get the DOM element getElementById and then change the width. Im not sure in which context the RockButton variable lives. Its all about the state.
Why does the div[id=box] not get updated until the for loop finishes? If I comment out the for loop, the div displays instantly.
document.getElementById('click').onclick = function() {
document.getElementById('box').style.display = 'block';
// loop after element update
for (var i = 0; i < 2000000000; ++i) {}
};
http://jsfiddle.net/472BU/
Simply, ALL browser processes (JS, repainting the page, even responding to user-clicks/key-presses and in most cases refreshes page-changes... even closing the tab) all happen in the same process thread.
Thankfully this isn't 100% true, 100% of the time, anymore.
Certain browser-vendors are working to move different parts of the web-platform to different threads, for a smoother experience, but typically, if you lock your JS up, you lock everything.
This simply means that the browser won't actually repaint until JS has finished running, and gives control back to the DOM.
The good news is that it means you can measure elements by unhiding them, grabbing their dimensions and hiding them again, at the end of the function. The width/height that they would take up is calculated on the spot, but a large portion of the page might have to be painted if you change an element, so if it's possible to change 30000 elements in a loop, then painting them all as it happens would be a very bad thing.
The cause is already explained by others. If you want the box to be painted instantly, the solution is simple. Put the loop in a timeout:
document.getElementById('click').onclick = function() {
document.getElementById('box').style.display = 'block';
// no delay anymore
setTimeout( function(){for (var i = 0; i < 2000000000; ++i) {}},10);
};
jsFiddle
Also check web workers
That amount of iterations running continuously will use up all of the browser's resources and it won't be able to worry with applying styles.
Your javascript is executed in the order it appears there, but behind the scenes there is a queue for rendering style changes. In any normal usage, you wouldn't notice this behavior, but since you're running an poor performant loop, it becomes evident.
Problem
It's because JavaScript is single-threaded and will only be able to run that loop.
Anything else will be on hold for as long as the loop lasts. As the DOM is wired into the JavaScript the DOM will be blocked as well (in general, except in browsers where DOM runs on a separate thread and will generate an event for the event queue instead which will be on hold until the current executing scope has finished).
Solution
To avoid this you need to split your functions into several asynchronous operations (not the same as multi-threaded) which will enable the browser to invoke some of the events queued up in the event queue (for example paint events).
You can do this by splitting up your function to perform iteration in segments using an inner mechanism to dispatch batches instead.
For example:
Live demo
function busyLoop(callback) {
var segCounter = 0, /// keep track of segment
totCounter = 0, /// keep track of total count
max = 2000000000, /// max count
segment = 1000000; /// segment size (smaller = better response)
/// invoke first batch
(function nextBatch() {
segCounter = 0; /// reset segment counter for each time
for(; segCounter < segment && totCounter <= max; segCounter++, totCounter++) {
///...work here...
}
if (totCounter < max) {
/// call setTimeout() which makes it async, +/- 11ms gives browser
/// chance to process other events such as paint events:
setTimeout(nextBatch, 11);
/// optional progress callback here
} else
callback();
})();
}
Then call it with a callback function:
busyLoop(doneFunction);
Notice that you can now interact with DOM as well as getting feedback.
Tip: The smaller segments the more responsive the DOM but the longer the total time as the delay in-between accumulates. Experiment to find a balance that suits your solution.
Hope this helps.
I want to make a javascript slideshow using a for loop
Javascript code
var Image_slide = new Array("img1.jpg", "img2.jpg", "img3.jpg");// image container
var Img_Lenght = Image_slide.length; // container length - 1
function slide(){
for (var i = 0; i < Img_Lenght; i++) {
Image_slide[i];
};
document.slideshow.src = Image_slide[i];
}
function auto(){
setInterval("slide()", 1000);
}
window.onload = auto;
html code
<img src="img1.jpg" name="slideshow">
i can't figure out what is the problem of this code it just run img3 continuously without looping img1 and it also skip img2 from the loop
The better option to solve this than to use a for loop is to simply skip the for loop all-together. Using a for loop is really too complicated and there's a far simpler solution.
Rather than using a for loop, simply assign the slides directly and keep track of positioning:
var Image_slide = new Array("img1.jpg", "img2.jpg", "img3.jpg");// image container
var Img_Length = Image_slide.length; // container length - 1
var Img_current = 0
function slide() {
if(Img_current >= Img_Length) {
Img_current = 0;
}
document.slideshow.src = Image_slide[Img_current];
Img_current++;
}
function auto(){
setInterval(slide, 1000);
}
window.onload = auto;
Interval should already run anyway. The loop inside the auto is redundant and simply messes it up. You only need to get one array element each time, not the whole loop. Looping through it every time will only return the last result.
You need to keep track of your position and reset the position to 0 once you reach the max length.
I'd also recommend at least 3 seconds for the interval instead of 1 second. One second I think is a bit too fast.
Here's an example of the correct solution on JSFiddle: http://jsfiddle.net/LUX9P/
NOW, that said, the question is actually asking how to make it work with a for loop. I've written up a potential solution to the problem (untested so I can't guarantee it will work), but I HIGHLY ADVISE NOT TO DO IT THIS WAY. It shouldn't be TOO bad overall, its just far more complicated and the solution above is so simple, this solution really isn't worth it.
var Image_slide = new Array("img1.jpg", "img2.jpg", "img3.jpg");// image container
var Img_Length = Image_slide.length; // container length - 1
function slide(){
delay = 0;
start = false;
for (var i = 0; i < Img_Length; i++) {
if(start && delay < 1000) {
delay += 1;
i--;
}
else {
document.slideshow.src = Image_slide[i];
delay = 0;
}
start = true;
}
}
function auto(){
setInterval("slide()", 1000);
}
window.onload = auto;
I cannot guarantee this will work, but essentially, the code I updated in slide() initializes a delay variable and a start variable. When the loop is run through once, it automatically activates start and always sets the first value in source.
Once start has been set, every consecutive time it will increment the delay variable until delay hits 1000, and it will decrement the i variable so that the for loop doesn't increment i over the cap (the length of the array). Basically, it sets i back by one so that the increment in for puts it back to where it should be, preventing it from moving on to the next variable until it finally processes the current entry.
This should, in theory, work. You may need to increase the delay significantly though; that 1000 should not actually equal one second; it'll likely go far faster than that. But I may be mistaken; it might run in one second, I haven't had a chance to try it out yet.
Clearly, the complexity of this is quite high, its just not worth it. My first option should be used instead.
I've tried everything that is supposed to invoke a reflow but that isn't happening. I'm calling my test function 10 times to draw some element on my screen and I move that element through each iteration. That loop is executed immediately and in the end I get one picture instead of seeing the movement of the element on the screen.
It's as if when all work is done, reflow and drawing on the screen is invoked. But i want to see each drawing.
All the things I've tried didn't give any results. The only thing that works is alert(), but i don't need an interaction with user.
I'm using an webkit 1.2.5 if that helps.
If I'm not understandable enough I will try to explain better.
This the code I'm forcing to reflow
var i = 0;
for(;i<500;i+=50){
fTestInfo(i);
console.log("Test loop!!! "+i);
}
The thing I nedd is to see a picture on my screen each time fTestInfo(i) is executed but instead, i only see the ending result.
fTestInfo depends on i it moves in left by the value of i.
I see you are using a for loop which typically means you misunderstand how timers work. The for loop is synchronously executed and you are probably setting all the timers at once.
Try this:
(function loop(i) {
if (i >= 500) {
return;
}
document.querySelector("div").style.left = i + "px";
setTimeout(function() {
loop(i + 1);
}, 16);
})(0);
demo http://jsfiddle.net/UCfmF/
I suppose you mean getting a value like .offsetWidth? This is not guaranteed to make a visible reflow on the screen, browsers may wait for some time (read: until javascript execution stops) before actually attempting to paint anything on the screen even if you are doing actions that trigger reflows.
This means that if you append 1000 elements to the document, it will not trigger 1000 reflows. Even if you fetch .offsetWidth in between each iteration. It will just be calculated for you but not necessarily painted.
You need to move the elements with a timer as the end of javascript execution is when browsers flush out any queued reflows.
See http://dev.opera.com/articles/view/efficient-javascript/?page=3#reflow
As stated earlier, the browser may cache several changes for you, and
reflow only once when those changes have all been made. However, note
that taking measurements of the element will force it to reflow, so
that the measurements will be correct. The changes may or may not not
be visibly repainted, but the reflow itself still has to happen behind
the scenes.
You need to give the browser the opportunity to enter its event loop between each iteration.
Use setTimeout to schedule each iteration of the drawing:
function scheduledTestInfo(i) {
setTimeout(function() {
fTestInfo(i);
}, i); // calls each function 50ms apart
}
var i = 0;
for ( ; i < 500 ; i += 50) {
scheduledTestInfo(i);
}
I have a long task in Javascript that should be performed before the web page content is displayed. During the execution of this task I would like to show an image whose opacity will grow up to 100% (when the task is done).
How this can be achieved ?
In your function , say it's 10 loops, every loop you need to increase the opacity, like this:
var curOpacity = 0;
function doWork(step) {
for(var j = 0; j <100000000; j++) { } //simulate work
console.log("Completed step: " + step);
$("#element").css('opacity', step * 0.1);
if(step < 10)
setTimeout(function() { doWork(step + 1); });
}
doWork(0);
You can see a working demo here
On the final step this would be setting the opacity to 1, completely faded in. The steps you just need to divide out, you need to increase opacity by 1/numOfSteps each step...a .fadeIn() won't work here because it'll just execute after your code is done...you need to manually set the opacity using .css() inside the loop.
The setTimeout() (without an argument it's instant) lets the UI update before the next step starts.
Note: this works cross-browser, just use opacity and it'll take care of setting filter: in IE for instance.
Decompose the task into multiple smaller tasks, each of which updates the progress bar appropriately. Then schedule these sub-tasks to run asynchronously using something like the jQuery Async extension. Without the async component, your tasks will hog the CPU until they are finished, and you won't see any interim status updates.
Depending on your requirements DOMContentLoaded Event might be called too late. Please notice the fact that that event is triggered when the whole DOM tree has been loaded. In some cases a web browser might start display web content before </html> is reached. You could just please some JavaScript right after <body> element (document.body is available there, while others DOM elements are not).
Any method you choose the rest of the code stays practically the same.
So now you have to:
Create an image and inject it into DOM tree:
var img = document.createElement("img");
img.setAttribute("src", "/path/to/the/image.png");
document.body.appendChild(img);
Make that image transparent:
img.style.opacity = "0";
Start your task
Every time the task is one percent closer to the end increase the value of the image's opacity style:
img.style.opacity = Math.min(1, (img.style.opacity * 1) + 0.01);
// Of course you can increase by any step, not only 0.01 (1%)
The problem may be the determination of when the task is one step closer to the end. If that task is for example a loop which repeats 10 times then there is easy way to determine it - every single iteration the task progress jumps 10%. But if your task has more complex structure then I think you'll have to define by your own places where task progress is 10, 20, 25, 50, 100%.
task(0);
//download something using AJAX
task(30);
while (i = 0..10) { ... task(+5) }
doSomething();
task(80);
doSthElse();
task(100);
Use web workers in modern browsers and partition the task with setTimeout() in old ones.