create jquery infinite animation not using recursion? - javascript

How to use jquery to create infinite animation,
BUT NOT using recursion way?
The recursion solution I found: jQuery .animate() callback infinite loop
The problem of using recursion is:
while animating, the browser tab of currrent page will take MORE and MORE memory.

You have actually made a mistake in assuming that the code is "recursive". It is not recursive. Your comment that "the browser tab of current page will take MORE and MORE memory." is not because of any recursion in that code. If you have a memory leak per second (as per your comments), then the problem lays elsewhere.
The instances of the code actually run sequentially and not via nested stack calls (i.e. linear and not recursive).
Using the most basic example you linked to:
function start() {
$('#element').animate({}, 5000, 'linear', start);
}
start();
here is what actually happens:
A function called start is defined and then called once
Inside start, an animation operation is started, then start immediately exits!
The animate just adds information to a single animation queue for the element, then returns.
The animation queue is processed step by step by a separate process (single timer etc).
When the queued entry has met its expected end state, it calls the callback - The global start function stored in the queued entry.
That simple adds another entry on the animation queue and exits.
Basically the code looks recursive, but as the operations are async, via a queue, it is not recursive. I don't know if there is an official name for these, so I just call them chained callbacks.

UPDATED WITH TEST RESULTS
Conclusion... All methods burn up the same amount of memory and it eventually gets released, doesn't keep building forever so not really an issue.
Example 1
This is the code the OP originally had a problem with memory usage increasing in Chrome.
(function($){
$(function(){ //document.ready
function start() {
$('#animate').animate({'margin-left':'150px'}, 1000, function () {
$(this).animate({'margin-left':'50px'}, 1000, 'linear', start);
});
}
start();
});
})(jQuery);
Example 2
Current solution including a callback as requested by Bergi to avoid potential "drifting" in setinterval.
(function($){
$(function(){ //document.ready
});
(function customSwipe(element) {
element
.animate({"margin-left" : "150px"}, 1000)
.animate({"margin-left" : "50px"}, 1000, function(){
setTimeout(function(){
customSwipe(element);
}, 2000);
});
})($('#animate'));
})(jQuery);
Example 3
Original Answer I gave, using setInterval()
(function($){
$(function(){ //document.ready
setInterval(function(){
$("#animate").animate({'margin-left':'150px'},1000);
$("#animate").animate({'margin-left':'50px'},1000);
},2000);
});
})(jQuery);
Skeleton w/ Jquery
Empty page w/ only the #animate element
(function($){
$(function(){ //document.ready
});
})(jQuery);
DATA AFTER TAB OPEN FOR 10 MINUTES
CODE STARTING ENDED
Example 1 14.300k 19.996k
Example 2 14.300k 20.020k
Example 3 14.300k 20.344k
Skeleton w/ jQuery 14.300k 15.868k
Interesting that the code that did nothing still increased usage slightly. These values go up and down as memory is used and released. Another thing would be to use the "purge memory" button in task manager to see how much of that used memory is garbage waiting to be collected.
DATA AFTER 25 MINUTES
Example 1 14.300k 18.640k
Example 2 14.300k 18.724k
Example 3 14.300k 18.876k
Skeleton w/ jQuery 14.300k 15.868k
Conclusion... All methods burn up the same amount of memory and it eventually gets released, doesn't keep building forever so not really an issue.
So just using the most solid, sound code would be the best option, Example 2 would be my choice.
UPDATED USING CALLBACK
Have you tried using setTimeout with $.animate()?
(function($){
(function customSwipe(element) {
element
.animate({'margin-left':'150px'}, 1000)
.animate({'margin-left':'50px'}, 1000, function(){
setTimeout(function(){
customSwipe(element);
}, 2000);
});
})($('#animate'));
$(function(){ //document.ready
});
})(jQuery);
Remove the setTimeout() if you don't need it to delay between animations.
JSFIDDLE of the above code working...
You might also want to look into this for more intricate animations.
http://api.jquery.com/jquery.fx.interval/
Conclusion... All methods burn up the same amount of memory and it eventually gets released, doesn't keep building forever so not really an issue.
What you are seeing in the Chrome TM is every time it fires the animation, that much memory is requested and the OS "commits" that memory to chrome for the operation. Once the operation is completed, the memory is still committed. At some point Chromes garbage collector comes along and releases that memory and your usage stats will drop back down. So you will see the memory going up and down if you watch it long enough.
You can put --purge-memory-button at the end of your Chrome command line to have a Purge Memory button available in Chrome TM. This might help you to see how much memory is actually waiting to be released.
Hope this answer helps you and maybe some others.

Related

Browser hangs for after computing intense JS function in a loop

My problem requires the below function to run in a loop on an average of 20 iterations
var test = function() {
// some logic which take about 0.5 sec to execute
// and deal with DOM manipulation
};
so if I iterate over it 20 items it will take an average of 10 sec and in the meantime browser hangs (not able to scroll down).
So I tried to change it to something like this
var test = function() {
setTimeout(function() {
// some logic which take about 0.5 sec to execute
// 0.5 sec as it deals with DOM manipulation
}, 0);
};
so that browser gets time to execute after each time Out but still the browser in hanging.
Why it is so? This is what I have always read in theory but not working practically.
This is how JavaScript works, whenever you block the main thread, your site will become unresponsive to events.
You should really try to make parsing your templates more efficient. 10s is an eternity when it comes to UX.
If that is not an option, you could look into moving some of the logic into a different thread and execute it asynchronously via web workers and only do the actual DOM manipulation in your main thread.

Understanding async using setTimeout

I have a UI where I need animations to run smoothly. Every so often, I need to do a semi-large data calculation that makes the animation skip until it is this calculation is completed.
I am trying to get around this by making the data calculation async with setTimeout. Something like setTimeout(calcData(), 0);
The whole code is something like this (simplified):
while (animating) {
performAnimation();
if (needCalc) {
setTimeout(calcData(), 0);
}
}
But I still get a skip in the animation. It runs smoothly when I do not need to do any data calculations. How can I do this effectively? Thanks!
You are seeing the skip because only one javascript thread is run at once. When something is done asynchronously the javascript engine puts it a queue to be ran later, then finds something else to execute. When something in the queue needs to be done the engine will pull it out and execute it, blocking all other operations until it completes.The engine then pulls something else out of its queue to execute.
So if you want to allow your render to run smoothly you must break up your calculation into multiple async calls, allowing the engine to schedule the render operation in between calculations. This is easy to accomplish if you are just iterating over a array, so you can do something like:
var now=Date.now;
if(window.performance&&performance.now){//use performace.now if we can
now=performance.now;
}
function calculate(){
var batchSize=10;//If you have a exceptionally long operation you may want to make this lower.
var i=0;
var next=function(){
var start=now();
while(now()-start<14){//14ms / frame
var end=Math.min(i+batchSize,data.length);
for(;i<end;i++){//do batches to reduce time overhead
do_calc(data[i]);
}
}
if(i<data.length) setTimeout(next,1)//defer to next tick
};
next();
}
calculate();
function render(){
do_render_stuff();
if(animating) {
requestAnimationFrame(render);//use requestAnimationFrame rather then setTimeout for rendering
}
}
render();
Better yet, if you can, you should use WebWorkers which work in a different thread, completely separate from the main js engine. However you are stuck with this if you need to do something you cant do in a WebWorker, such as manipulating the DOM tree.
Firstly, let's talk what's going on in your code:
while (animating) {
performAnimation();
if (needCalc) {
// it should be setTimeout(calcData, 0);
setTimeout(calcData(), 0);
}
}
In line setTimeout(calcData(), 0); really you don't defer calling of calcData function, you call it, because you use () operator after function name.
Secondly, lets think, what's going on when you really make defer calling for calcData in the code above: commonly JavaScript is running in one thread, so, if you have code like this:
setTimeout(doSomething, 0);
while (true) {};
doSomething will never be called, because interpreter of javascript executes while loop forever and it hasn't "free time" to execute other things (even UI) . setTimeout - just say to schedule execution of doSomething when interpreter will be free and it's time to execute this function.
So, when browser executes javascript function, all other stuff become freezing.
Solution:
If you have big data that you need to process, maybe it would be better to make calculations on backend and after send results to frontend.
Usually when you need to make some calculation and render results it's better to use requestAnimationFrame than while loop. Browser will execute function passed in requestAnimationFrame as soon as possible, but also you give browser a time to handle other events. You can see smooth redrawing using requestAnimationFrame for game (step-by-step tutorial here).
If you really want to process huge amount of data at frontend part and you want to make ui works smooth, you can try to use WebWorkers. WebWorkers look like threads in JavaScript, you need to communicate between main UI "thread" and WebWorker by passing messages from one to another and back and calculations on WebWorker don't affect UI thread.
Mostly, your problem boils down to your incorrect usage of setTimeout()
setTimeout(calcData(), 0);
The first argument to setTimeout is a REFERENCE to the function that you wish to call. In your code, you are not referencing the calcData function, you are invoking it because you've included () after the function name.
Second, the fact that you've put 0 for the delay does not mean you will have a 0 second delay before the function runs. JavaScript runs in a single threaded context. The setTimeout function is placed in a queue and executed when the JavaScript engine is available, but no sooner than a minimum of 10ms or the amount you specify (whichever is less).
Realistically, your line should be:
setTimeout(calcData(),10);

Is node.js setTimeout() working?

I'm new to Node.js. Is there something I need to do to get setTimeout() to work?
Here's a code snippet.
async code that sets appMsg.doneLoadTables = true when done
do {
console.log('waiting ... ' + appMsg.doneLoadTables);
setTimeout(function() { console.log('waiting ...'); }, 1000);
} while (!appMsg.doneLoadTables);
Symptoms:
(While the two calls to console.log are similar, only the first prints the value of appMsg.doneLoadTables.) Every result includes that value.
The spacing between calls to console.log is much closer than 1000 msec. (I suspect the spacing is as fast as the computer can process the loop shown here.)
While I would hope the async routines could continue to process during the delays I intended here, I've never seen this loop finish; it's as if the loop takes all processing resources and prevents the async routines from finishing their work and from setting the variable that'll end this loop.
I had this experience with Node 4.2.1; I continue to have this experience after installing Node 5.0.0.
I've seen that similar questions about setTimeout() have been asked here many times before. I hope my use of a IIFE inside setTimeout() makes this question distinct from all of those.
Thanks in advance for any help offered ...
JavaScript is single-threaded. setTimeout is not a form of sleep which pauses code at that line. It works by "scheduling" your callback for later, and execute it when the stack exhausts (the engine doing nothing) and is at least n milliseconds later, where n is the delay you placed in milliseconds.
Now your code doesn't work because it never exits the loop. The code doesn't get the chance to execute other code (the code you hope to run and change appMsg.doneLoadTables's value). All it does keep logging "waiting... [something]".
Essentially you are polling. What you could use instead is setInterval. When appMsg.doneLoadTables is true, you stop the polling by using clearInterval.
I am not 100% sure what is your goal ... however maybe this snippet takes you where you want to go (I opted for setTimeout instead of setInterval):
var appMsg = {doneLoadTables: false};
var stillLoading = function() {
if(false === appMsg.doneLoadTables) {
console.log('waiting ... ' + appMsg.doneLoadTables);
setTimeout(stillLoading, 50);
}
else {
console.log('Loading complete.');
process.exit();
}
}
stillLoading();
setTimeout(function(){
console.log('Setting appMsg.doneLoadTables = true');
appMsg.doneLoadTables = true;
}, 1000);
The script polls status every 50ms and marks "done" exactly after 1 second.
The output looks like this
waiting ... false
waiting ... false
waiting ... false
waiting ... false
...
Setting appMsg.doneLoadTables = true
Loading complete.
(While the two calls to console.log are similar, only the first prints the value of appMsg.doneLoadTables.) Every result includes that value.
That is the correct behavior since you never exit the while loop. You stay in the same event frame that keeps looping forever.
The spacing between calls to console.log is much closer than 1000 msec. (I suspect the spacing is as fast as the computer can process the loop shown here.)
That is the correct behavior again because you callbacks that you passed to setTimeout will never execute unless you exit the do-while loop, which you never do. So you just keep calling first console.log statement then you add a callback to event loop to execute in 1000 ms without ever giving it (the callback that you pass) the chance to execute.
While I would hope the async routines could continue to process during the delays I intended here, I've never seen this loop finish; it's as if the loop takes all processing resources and prevents the async routines from finishing their work and from setting the variable that'll end this loop.
The loop never finish because it doesn't have logic implemented that finishes it. "Async routines" can't continue because that would require exiting the current event frame (that runs infinite loop) and starting the next one that has you callback that you passed to setTimeout.
Hope my explanations will help you to understand how asynchronous JavaScript works.

Velocity.js: How to stop a queue but make the element go to its final state?

I would love to stop an animation queue and make the element go to the final state of that queue.
$el.velocity('finish', true) can only make $el go to the end of current animation in the queue but ignore all the remaining animations.
here's a demo maybe better explaining this.
In theory, running an animation queue with a total duration of 0 should play out all the effects instantly. This does not appear to work, but it does for a 1 ms duration.
ydaniv has pointed out that the Velocity API contains a mock property that, among other things, allows duration: 0 behaviour: mock: true. Velocity.js API for mock.
I have added three lines to your code (2 in stop function, 1 in start), that use mock to achieve a 0 ms duration:
$('.stop').click(function (){
// Stop and clear the animation queue.
$(".animate").velocity('finish', true);
// Enable mocking, and play out the animation instantly.
$.Velocity.mock = true;
$(".animate").velocity('callout.twirl');
})
// ...
function start(){
// Reset the instant mocking, and start the animation properly.
$.Velocity.mock = false;
$(".animate").velocity('callout.twirl')
}
Demo: Codepen (using mock).
Warning: the animated div can prevent user from being able to click stop. Note that you do still need to finish the animation queue as before.
My original solution (1 ms duration) is likely not as efficient but does not require resetting mock afterwards. Using mock will also prevent you from testing your UI with it, so I have included the original solution:
$('.stop').click(function (){
// Stop and clear the animation queue.
$(".animate").velocity('finish', true);
// Play out the animation, effectively immediately.
$(".animate").velocity('callout.twirl', { duration: 1 });
})
Demo: Codepen (using 1 ms duration).
I highly recommend you try to preserve the intended functionality of mock, by either using the more-crude duration approach, or by creating a construct that will manage the mock variable for you, based on your own semi-global setting (i.e. if you have enabled mocking non-locally, temporarily swap the values before and after your variant of finish).

Timing in javascript sequencer

I have developed a music sequencer in javascript; something like this: http://stepseq.michd.me/
I have implemented loop using setInterval function in following way:
var Sequencer = {
...
// every step sequencer ...
next: function(callback) {
// restart from begin if arrive to last sequecer step
if(Sequencer.current==Sequencer.steps.length)
Sequencer.current = 0;
// play all sounds in array step contains
if(Sequencer.steps[Sequencer.current].length>0) {
var set = Sequencer.steps[Sequencer.current];
for(var i=0;i<set.length;i++){
set[i].play();
}
}
callback(Sequencer.current);
Sequencer.current++;
},
loop: function(callback) {
Sequencer.interval = $interval(function(){
Sequencer.next(callback);
}, Sequencer.time);
}
}
...
Code below works but i think that there is a better way to implement loop; infact sometimes steps goes out of time. Sequencer.time (time passed to setInterval function) is a time in millisecs (this value is the conversion of a bpm value; for example it can be 125),
Someone can suggest me a better solution?
N.B.: this is a web application angularjs based (for this reason in code above a use $interval insteed of setInterval), but i think that this point is insignificant.
Javascript timer intervals are not guaranteed to run at exactly the time you request, due to the single threaded nature of JS. What you get is a callback that is queued up to run after the interval expires, whenever the engine is free to run it.
John resig has covered this off in some depth:
http://ejohn.org/blog/how-javascript-timers-work/
http://ejohn.org/blog/analyzing-timer-performance/
And from his conclusions, which is going to be important for your app:
If a timer is blocked from immediately executing it will be delayed
until the next possible point of execution (which will be longer than
the desired delay).
I don't really have a better solution for your problem, due to these fundamental issues with timers in JS, but this may at least explain what is happening.

Categories