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.
Related
Trying to maintain 60 fps animation. Currently, I'm getting a lot of spikes of slow frames that are happening at like 30~ fps and the choppiness is noticeable to my eye.
Significant Edits: Throwing out old obsolete code, adding in new code with explanation
fadeIn: function(ele){
raf = window.requestAnimationFrame(function() {
console.log(ele);
var opacity = 0;
function increase () {
opacity += 0.05;
if (opacity >= 1){
// complete
ele.style.opacity = 1;
return true;
}
ele.style.opacity = opacity;
requestAnimationFrame(increase);
}
increase();
});
},
fadeInElements: function(elements, properties, speed, delay){
var raf;
var ele;
for (i = 0; i < properties.length; i++){
ele = elements[properties[i]];
console.log('outside loop ' + ele);
instance.fadeIn(ele);
}
},
My new code is above. It is successfully:
Iterating through several elements (each as ele) and then calling fadeIn(ele)
So, all elements fade in.
However, I want a 50ms delay between each "fade in" (each triggering of fadeIn() on a new element
The good news is that it's not actually recursion — it's more like a timeout. You provide a function that draws a frame, and the browser calls it for you.
Here's an answer showing excerpts from a complete JSFiddle. It doesn't try to replicate your exact code, but instead tries to explain what you need to know so you can adapt your code accordingly. The code was written to be easy to understand, so I'm sure there are other ways to do it faster!
This answer works from the top level down, which means I describe the end of the JSFiddle source and work my way backwards. I personally think this makes it easier to understand than does starting with the details.
You need to start the animation somewhere. So the very last thing in the JSFiddle is
window.requestAnimationFrame(eachFrame); //start the animation
This will call a function called eachFrame() when it's time for the next frame, e.g,. on the next multiple of 60 times per second. It will only do it once, though.
You need eachFrame() to keep track of where we are in the animation.
var startTime = -1.0; // -1 = a flag for the first frame.
function eachFrame()
{
// Render this frame ------------------------
if(startTime<0) {
// very first frame (because of the -1.0): save the start time.
startTime = (new Date()).getTime();
render(0.0);
// the parameter to render() is the time within the
// animation.
} else {
// every frame after the first: subtract the saved startTime
// to determine the current time.
render( (new Date()).getTime() - startTime );
}
// Now we're done rendering one frame. ------
//Start the timer to call this function again
//when it's time for the next frame.
window.requestAnimationFrame(eachFrame);
}; //eachFrame
eachFrame() determines what the current time is with respect to the beginning of the animation. getTime() gives you the time in milliseconds.
The other thing eachFrame() does is to call window.requestAnimationFrame(eachFrame); again. This isn't recursion. Instead, eachFrame() will finish running, and then after that, the next time a frame comes around, the browser will call eachFrame() again.
The last function you need is something to actually draw the frame! That is render(current time). Assume that, e.g., head1 and head2 refer to two heading elements you want to animate, e.g., <h1> elements declared in your HTML. The clamp(x) function returns x but clamped below at 0 and above at 1.
function render(currTime)
{ // *** Put your rendering code here ***
// How opaque should head1 be? Its fade started at currTime=0.
var opacity1 = clamp(currTime/FADE_DURATION);
// over FADE_DURATION ms, opacity goes from 0 to 1
// How opaque should head2 be?
var opacity2 = clamp( (currTime-FADE_SPACING)/FADE_DURATION );
// fades in, but doesn't start doing it until
// FADE_SPACING ms have passed.
// Apply the changes
head1.style.opacity = opacity1;
head2.style.opacity = opacity2;
} //render
In render(), you figure out opacity based on the current time. You don't have to worry about delaying between frames, because requestAnimationFrame handles that for us. You stagger the transitions by offsetting the time. In this example, opacity1 depends on currTime and opacity2 depends on currTime minus a constant FADE_SPACING, so the opacity change for element 2 will start later than the opacity change for element 1 by FADE_SPACING ms.
The JSFiddle has all the details filled in. It animates the opacity of two <h1> elements, with a spacing between the beginning of the animation for each element. I hope this helps!
I am making a basic image slider (press the next/prev button and the picture changes). I am also trying to implement a very simple fade effect; to do this I have a for loop which changes the class of the image, cycling through classes that change the opacity. The code works fine and does what I want it to do, but the for loop executes so quickly that you don't notice the change in opacity. I have looked all over and everywhere people are mentioning setTimeout, but that only causes a delayed start of a function.. I just want to slow my for loop so you can visually notice each iteration.
function nextphoto(){
for(var x = 0; x < 5; x++){
photo.className = fade[x];
}
i++;
if(i>19){
i=0;
}
photo.src = image[i];
for(var y = 4; y >= 0; y--){
photo.className = fade[y];
}
}
You might be misunderstanding the concept of synchronous code execution. All of your code has to finish running before anything can be updated or "rendered" to the screen. So your for loop will run until it's done, then the screen will update, but it will of course only have the final view of the image to render.
You should either trigger a CSS transform, by dynamically adding a class to the element via javascript, or if you were desperate to do it all in code, had some reason not to use CSS, and do not want to write a custom per frame animation system, or use a library, then inside each iteration of the for loop, fire an asynchronous function that will update at a later and later time, outside of the current synchronous for loop code.
ie:
var aThing;
for(var i = 0; i < someNumber; i++){
setTimeout(function(){
aThing.someValue = aThing.someValue * .9;
}, 500 * i);
}
You should consider using JQuery's animate() function to animate css values like opacity. Even better you could be using CSS animations.
What you're doing is NOT a good way to achieve your goal. If you really want it that way you should use setInterval or setTimeout.
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'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'm using OpenX at work, and one of my boss requirements is a expandable banner. For that (and made a horrible simplification of the whole story) I made this script.
function retro(){
var acs = jQuery('#trial_center').height() - 5;
jQuery('#trial_center').css('height', acs + 'px');
}
jQuery(document).ready(function(){
jQuery("#trial_center").mouseover(function(){
setTimeout("jQuery('#trial_center').css('height', '500px')", 1000);
})
jQuery("#trial_center").mouseleave(function(){
var c = 89;
while (c > 0) {
setTimeout("retro()", 1000);
c--;
}
})
});
The problem I have is in the mouseleave event: the original idea was to made this loop several times (89 times), and each time, decrease the height of the banner until it get his original size. Why this? Because my boss want an "effect", and this effect must be in sync with the customer's flash.
The problem is that instead of decrease his size progressively, apparently the script made all the operations an "after" the sum of setTimeout calls, updated the page. So, the result is exactly as the banner shrinks one time from the expanded size to the original size.
I don't know what is wrong with this, or if exists other more intelligent solution.
Any help will be very appreciate.
Thanks in advance
Your loop setting the timeout is just setting 89 timers for one second later than the loop runs, and the loop will run in milliseconds — so they'll all fire about a second later. That doesn't sound like what you want to do.
Two options for you:
1. Use animate
jQuery's animate function seems like it does what you want. You can tell jQuery to animate the size change, and you tell it how long to take to do so:
jQuery('#trial_center').animate({
height: "500px" // Or whatever the desired ending height is
}, 1000);
That will animate changing the height of the container from whatever it is at the point that code runs to 500px, across the course of 1,000 milliseconds (one second). Obviously you can change the duration to whatever you like.
2. Set up the timer loop manually
If for whatever reason you don't want to use animate, you can do this manually (of course you can; jQuery can't do anything you can't do yourself, it just makes things easier). Here's how to set up a timer loop:
jQuery("#trial_center").mouseleave(function(){
var c = 89;
// Do the first one right now, which will schedule the next
iteration();
// Our function here lives on until all the iterations are
// complete
function iteration() {
// Do one
retro();
// Schedule this next unless we're done
if (--c > 0 {
setTimeout(iteration, 100); // 100ms = 1/10th second
}
}
});
That works because iteration is a closure over c (amongst other things). Don't worry about the term "closure" if it's unfamiliar, closures are not complicated.
Separately: You're using mouseover to set the height of the trial_center element a second later; you probably wanted mouseneter rather than mouseover. mouseover repeats as the mouse moves across it.
Off-topic:
It's best not to use strings with setTimeout; just pass it a function reference instead. For example, instead of
setTimeout("retro()", 1000);
you'd use
setTimeout(retro, 1000); // No quotes, and no ()
And for the other place you're using, instead of
setTimeout("jQuery('#trial_center').css('height', '500px')", 1000);
you'd use
setTimeout(function() {
jQuery('#trial_center').css('height', '500px');
}, 1000);