I am trying to create a loading bar during a very intensive period of JavaScript where some pretty heavy 3d arrays are built and filled. This loading bar needs to remain empty until the user clicks a button.
The freezing occurs whether or not I'm using -webkit-transition (This app can be chrome exclusive, cross browser is not necessary in my case).
Seeking simplicity I've built my bar like this...
<div id="loader">
<div id="thumb">
</div>
</div>
... and then sought to increment that bar at various stages of my main for loop:
for(i = 0; i < 5 ; i++){
document.getElementById('thumb').style.width = i*25 + '%';
//More Code
}
Problem is that everything freezes until the JavaScript finishes.
I found a similar question on Stack Overflow, Using CSS animation while javascript computes, and in the comments found and considered and/or tried the following:
Web Workers
Don't think it'll work since my script is filling an array with objects and constructors containing functions which according to this site isn't going to work
jQuery
Not an option, I can't use external libraries in my app - in any case, importing a whole library just for a loading bar seems kind of like overkill...
Keyframes
This was promising and I tried it, but in the end it freezes also, so no joy
timeOut()s
Thought about this, but since the point of the loading bar is to reduce frustration, increasing the waiting time seems counter-productive
I'd be happy to have any incrementation of the bar at this stage, even if it's not smooth! I'm pretty sure this is a problem that has struck more than just me - maybe someone has an interesting solution?
P.S.: I'm posting this as a new question rather than adding to the referenced question since I'm specifically seeking help with JavaScript (not jQuery) and would prefer if I could get it using a transition (!=animation) on the width.
Some people already mentioned that you should use timeouts. That's the appropriate approach, bc it'll give the browser time to "breathe" and render your progress bar mid-task.
You have to split your code up to work asynchronously. Say you currently have something like this:
function doAllTheWork() {
for(var i = 0; i < reallyBigNumberOfIterations; i++) {
processorIntensiveTask(i);
}
}
Then you need to turn it into something like this:
var i = 0;
function doSomeWork() {
var startTime = Date.now();
while(i < reallyBigNumberOfIterations && (Date.now() - startTime) < 30) {
processorIntensiveTask(i);
i++;
}
if(i < reallyBigNumberOfIterations) {
// Here you update the progress bar
incrementBar(i / reallyBigNumberOfIterations);
// Schedule a timeout to continue working on the heavy task
setTimeout(doSomeWork, 50);
}
else {
taskFinished();
}
}
function incrementBar(fraction) {
console.log(Math.round(fraction * 100) + ' percent done');
}
function taskFinished() { console.log('Done!'); }
doSomeWork();
Note the expression (Date.now() - startTime) < 30. That means the loop will get as much done as it can in the span of 30 milliseconds. You can make this number bigger, but anything over 100ms (essentially 10 frames-per-second) is going to start feeling sluggish from the user's point of view.
It may be true that the overall task is going to take somewhat longer using this approach as opposed to the synchronous version. However, from the user's experience, having an indication that something is happening is better than waiting indefinitely while nothing seems to be happening – even if the latter wait time is shorter.
Have you tried going even simpler and making a function, let's say:
Pseudo:
Function increment_bar(amount = 10)
{
document.getElementById('thumb').style.width = i*amount + '%';
}
Then from wherever you are doing your processing work just calling that function every x seconds or whenever you hit a certain point in processing (let's say 10-20% completion?)
Pseudo:
{
Doing_work_here;
increment_bar(25);
LOOP
}
Related
I'm attempting to make a page that will animate the a href links through a table of colors nice and smoothly. I currently have 2 problems with the code that I'm using experiencing a whole mix of problems relating to bad code (please note that JavaScript and jQuery I'm pretty damn weak at). I'm hoping that some Guru can spend 2mins and let me know what the problem or supply a better solution. (thanks in advance.).
Problems Encountered:
Uncaught RangeError: Maximum call stack size exceeded
Firefox is not smooth while Chrome is (Firefox just changes color).
Some hues are too dark
Performance seems an issue, maybe this is because of the Maximum stack size error
Libraries:
jQuery.v1.10.2.min.js
jQuery.color-2.1.0.js
Code:
jQuery(document).ready(function() {
spectrum();
function spectrum(){
var hue = 'rgb(' + (Math.floor(Math.random() * 256)) + ',' + (Math.floor(Math.random() * 256)) + ',' + (Math.floor(Math.random() * 256)) + ')';
jQuery('body a').animate( { color: hue }, 2000);
spectrum();
}
});
What I need:
Basically all I need is a script that will animate all links on the page from one color to another every 2seconds or more... smoothly. Ideally, I'll like to be able to select 6 or more colors that I know that work but due to my limited knowledge in JavaScript I don't know where to begin.
JSFiddle of the Code in Action
I've made a jsfiddle to hopefully save anyone time or just check what the current output looks like: http://jsfiddle.net/ebZ3x/
Yeah, you're recursively calling indefinitely which will quickly run you out of stack space. What you want instead is for the browser to regularly call your color changing function. We'll use window.setInterval() to accomplish that.
Then we'll also create an array of the six colors you want and we'll just randomly index into it. To add more colors just add them to the array.
jQuery(document).ready(function() {
function spectrum(){
var colors = [
'rgb(256,0,0)', //red
'rgb(0,256,0)', //green
'rgb(0,0,256)', //blue
'rgb(256,256,0)', //orange
'rgb(256,0,256)', //magenta
'rgb(0,256,256)']; //cyan
var hue = colors[(Math.floor(Math.random() * colors.length))];
jQuery('body a').animate( { color: hue }, 2000);
}
var intervalId = window.setInterval(spectrum, 2000);
});
I've been working on your question since you posted it yesterday, and I thought I would give it a shot so I might learn a little about setTimeout. And boy, have I learned - about the complexity of such a "simple" command. It's probably the most difficult I've encountered in javascript.
So I present my "answer" just as something to be viewed, with JoeClacks' answer obviously superior.
This FIDDLE shows the initiation of the timer (runmytimer) after loading the DOM. It changes the background color of two divs randomly. I've let it run for over an hour and it seems not to crash.
I added the "extra" stuff to make sure other things on the page don't interfere with the timer. So when you type into an input box, the timer continues. When you click the "save" button (I just moved the input text to another div) the timer continues.
Here is the relevant JS
var randomcolors = ['#FF00FF','#00FFFF','#FFFF00','#0000FF','#00FF00','#FF0000','#000000','#C0C0C0','#C0C1C1','#CFCHCH','#CCFFCC'];
var timer;
//var timer_is_on = 0;
runmytimer();
$('#saveme').click(function(){
var moveme = $('#getme').val();
$('.movemehere').html(moveme);
});
function runmytimer()
{
t = setTimeout( function(){ runmytimer() }, 1000 );
random = Math.floor(Math.random() * (11 - 0 + 1)) + 0;
$('.putmehere1').css('background-color', randomcolors[random]);
$('.putmehere2').css('background-color', randomcolors[random+1]);
}
For other noobs such as myself who are reading this, I've learned a few things that aren't clearly stated in any documentation (I went to 30-40 sites, that weren't really that helpful).
If you try to put a timer in a loop - its behavior is NOT intuitive. I'm used to BASIC loops where when you do something to STOP the loop - oddly - it STOPS! :-). Not so with javascript. As an experiment I did a loop with a setTimeout in it, and put the i's of the loop in a div. What chaos! The loop printed out all the i's before the first setTimeout was done! I read that the loop is actually creating a different timer for each loop of the setTimeout. Disaster!
You can stop a timer with clearTimeout(nameoftimer).
I'm guessing if you have a routine that stops a timer, it could be restarted at the bottom of the routine with setTimeout(nameoftimer).
I still haven't figured out why a variable assignment such as var timer = setTimeout ( alert('hello'), 1000 ); would not only assign the variable to the code, but also run the code. Not intuitive.
After going to all the sites and trying their code, I went to W3Schools (not held in high esteem by many) and found code that actually worked! I derived my fiddle from that.
Anyway, thanks for the question. I learned a lot!
I have some javascript/jquery code (for an internal website) that does a lot of client side processing on a large table. It runs a little slowly, but that's OK.
The problem is that it freezes the browser while running through the loops that it needs to do. This has two effects that are undesirable:
The processing spinner (an animated gif, but I also tried spin.js and that has the same issue) freezes.
If there are enough rows in the table, the loops take a long time and the browser reports an unresponsive script
Is there a way I can have some sort of a "breath" statement in the code, such that every (say) 100 iterations, it pauses to let the spinner spin and the browser know that the script is still working on it? Something like (pseudo code) :
for (i=0;i<20000;i++)
{
fnProcessRow();
if (i % 100 == 0) breath();
}
One way to break up your javascript is to divide your processing into chunks and use setTimeout() to perform the next "chunk"
Let me know if you need some code to show you what I mean.
There is no set method for doing this as it depends very specifically on your code. Something like this would work:
fnProcessRows(0, 10000);
function fnProcessRows(start, end) {
/* do row processing */
if (end < totalRows) {
setTimeout(function () {
fnProcessRows(start + 10000, end + 10000);
}, 1000);
}
});
This will process 10000 rows with a one second break in between. This can have potentially weird side effects like displaying the "processed" rows with "unprocessed" rows, which may be undesirable. If it's not, then this should be a pretty simple solution to help out.
You can of course reduce 10000 to something else if it's still unresponsive; it should be unresponsive for only very short periods of time that the user would have a hard time noticing.
See this question.
If you do not want to go the setTimeout/setInterval route, try adding:
if (i % 100) { $("selector for your animated image").width(); }
On some (most?) browsers that will force it to re-layout and render.
I've got a masterpage with some nice UI (jQuery) features. One of these options is interefering with my embedded YouTube (or other alike-) objects. On each, in this case, setInterval event the embedded video stops displaying new frames (for like a second).
More detail:
I've got a "polaroid" gallery (in the header) with only 5 100x100 images in it (test: preloading has no effect on performance) and my gallery will show or hide them (fade-in / fade-out) after a period of time. (test: non-animated display:hide or display:block has no effect on performance).
After some testing and I've come to the conclusion that it isn't the "animated" showing or hiding of the pictures, but it's the interval itself (- since altering to display:hide or block had the same result). Perhaps it is my "gallery" "function" on itself ...
function poladroid() {
if (!galleryHasFocus) {
if (galleryMax >= 0) {
galleryCurrent++;
if (galleryCurrent > galleryMax) {
galleryCurrent = 0;
showPictures = !showPictures;
}
if (showPictures) {
$('#pic-' + galleryCurrent.toString()).show("slow");
}
else {
$('#pic-' + galleryCurrent.toString()).hide("slow");
}
}
}
if (!intervalSet) {
window.setInterval("poladroid()", 3000);
intervalSet = true;
}
}
It's not like my function is doing really awkward stuff is it? So, I was thinking I needed a more "loose" interval function.. but is there an option for it?
Ow.. almost forgot to mention it: FireFox and Chrome perform pretty good; using IE (what else) has the most performance problems.
There is no substitute for setInterval/setTimeout - these are the only ways of timing events in browser based ECMAScript.
Its not easy to grasp the real issue here, but I'm guessing the whatever you are triggering using setInterval is heavier than it has to be (as it is jQuery) - you should try to make this more efficient.
If you want an easy to use slideshow, take a look at http://github.com/oyvindkinsey/JsSlideshow - it has a demo here
By the way, do not use a literal as the first argument to setTimeout/setInterval, use a function reference
setInterval(poladroid, 3000);
I'd like to run some calculations in a browser window, but I don't want it to slow the client computer down for user interaction, especially for single core machines. Is there some way to adjust the nice level of my executing JavaScript so that it will execute as fast as possible without detracting from the responsiveness of the machine?
I can't think of anything else than delaying execution of your calculations. In example, dividing all work into small pieces and then running them sequentially with some delay (using setTimeout or setInterval) between each task.
Create open loops... an example
This is a close loop
for( i = 0 ; i < 10000 ; i++)
{
doSomeMath(i);
}
This is an open loop
i = 0;
iMax = 10000
var MyWorkingThread =
setInterval(function()
{
if( i < iMax)
{
doSomeMath(i);
i++;
}
else
{
clearInterval(MyWorkingThread);
}
},1);
You can make a more, or a less work inside the open loop, but this is the general idea. I have made this many times for similar issues and I left the browser work very smooth.
For speed attempt to use a single multidimensional array to contain your data and then at the end of your function use a single join to convert that array to a string for output and output that data using the innerHTML method. That is the fastest possible method of containing and serving data in JavaScript. Definitely do not use DOM methods or elements to output your data as that is about 4 times as slow.
Output your data as few times as possible. This is going to be determined upon which event you use to execute your function. I recommend not using the onload event as this will slow the initial load time of your page. I would recommend using the onclick function associated with a button, because then the user is aware that they caused the execution that is slowing down your page.
I did some testing, and the browser needs quite some time between bursts of work to be reasonably responsive:
function work(cnt) {
// do some heavy work
for (var i=0;i<100000000;i++) ;
// start next work
if (cnt > 0) {
window.setTimeout(function(){work(cnt-1);},200);
}
}
run the calculation on the server with an ajax request
open a new window or frame and run the code there
run the code in a loop broken into intervals
be ready to use web-worker processes (html5 asynchronous script)
I'm calling a javascript function that sets the opacity of an iframe an unknown amount of times in rapid succession. Basically this tweens the alpha from 0 to 100.
here is the code
function setAlpha(value)
{
iframe.style.opacity = value * .01;
iframe.style.filter = 'alpha(opacity =' + val + ')';
}
My problem is that for the first time it is working in ie (7) and not in firefox (3.02). in Firefox I get a delay and then the contentdocument appears with an opacity of 100. If I stick an alert in it works, so I'm guessing it is a race condition (although I thought javascript was single threaded) and that the setAlpha function is being called before the last function has finished executing.
Any help would be greatly appreciated. I've read the 'avoiding a javascript race condition post' but I think this qualifies as something different (plus I can't figure out how to apply that example to this one).
The issue is that most browsers don't repaint until there is a pause in the javascript execution.
This can be solved by using setTimeout, as others have suggested. However, I recommend using something like jQuery, or any of the javascript libraries to do animations. Running setTimeout 100 times is a bad idea because the length of the animation will vary based on the browser and speed of the user's computer. The correct way to do animations, is to specify how long they should last and check the system time to determine how far the animation should progress.
function fadeIn(elem,animation_length) {
var start = (new Date()).getTime();
var step = function() {
window.setTimeout(function() {
var pct = ((new Date()).getTime() - start)/animation_length;
elem.style.opacity = Math.min(pct,1);
if (pct < 1)
step();
},20);
};
step();
}
[edit:] The code above is only to illustrate how to do animations based on the system clock instead of simple intervals. Please use a library to do animations. The code above will not work on IE, because IE uses "filter:opacity(xx)" instead of "opacity". Libraries will take care of this for you and also provide nice features such as completion events, and the ability to cancel the animation.
Javascript doesn't run across multiple threads so you're safe from race conditions (ignoring upcoming Worker thread support in Safari and Firefox :D ).
Simple question, how are you calling setAlpha multiple times, firefox, safari and opera all coalesce style sheet updates -- eg. they won't repaint or even recalc style info while js is running unless they have to. So they will only paint if JS has completed.
So if you're doing
while(...) setAlpha(...)
they won't update, you'll probably need to use setTimeout to trigger multiple distinct calls to update the style.
An alternative would be to use a library such as jQuery, mootools,etc that i vaguely recall provide a simplified mechanism to do these types of animations and transitions. As an added bonus i believe at least a few libraries will also use webkit transition and animation css rules when available (eg. Safari, and i think the latest firefox builds)
[edit: caveat: i haen't actually used any of these libraries, i only read about what they're supposed to do. My sites render the same in lynx as any other browser because i couldn't design my way out of a paper bag :D ]
Are you using setTimeout or a tight loop? If you're using just a loop to call the function, then switch to using setTimout.
example:
function setAlpha(value)
{
iframe.style.opacity = value * .01;
iframe.style.filter = 'alpha(opacity =' + val + ')';
if(value < 100 ) {
setTimeout(function () {setAlpha(value+1)},20);
}
}
setAlpha(0);
Because you see, it's not just javascript that's single threaded. It's the whole damn browser. If your javascript goes into a tightloop, you hang the whole browser. So the browser pauses waiting for javascript to finish, and doesn't even have a chance to update the screen, while your code is rapidly changing some dom values.
Some browsers are smart enough to delay changes to the DOM until the call stack is empty.
This is a generally a smart thing to do. For example, if you call a function that changes an element to yellow, and immediately call a function that changes the same element back to it's original state, the browser shouldn't waste time making the change, since it should happen so quickly as to be imperceptible to a user.
The setTimeout(func, 0) trick is commonly used to force Javascript to delay execution of func until the call stack is empty.
In code:
function setAlpha(opacity){
some_element.style.opacity = opacity;
}
/**
* This WON'T work, because the browsers won't bother reflecting the
* changes to the element's opacity until the call stack is empty,
* which can't happen until fadeOut() returns (at the earliest)
**/
function fadeOut(){
for (var i=0; i<10; i++){
setAlpha(0.1*i);
}
}
/**
* This works, because the call stack will be empty between calls
* to setAlpha()
**/
function fadeOut2(){
var opacity = 1;
setTimeout(function setAlphaStep(){
setAlpha(opacity);
if (opacity > 0){
setTimeout(setAlphaStep, 10);
}
opacity -= 0.1;
}, 0);
}
All this boils down to being a wonderful excuse to use one of many javascript libraries that handle this tricky stuff for you.
Edit: and here's a good article on the tricky Javascript call stack