Pausing Recursive Function Call - javascript

I have a function of which I'm supposed to pause on mouseenter and pause on mouseleave but the problem is that the function is recursive. You pass in a parent and index and it will recursively loop through each inner div displaying and hiding. The function looks like this:
var delay = 1000;
function cycle(variable, j){
var jmax = jQuery(variable + " div").length;
jQuery(variable + " div:eq(" + j + ")")
.css('display', 'block')
.animate({opacity: 1}, 600)
.animate({opacity: 1}, delay)
.animate({opacity: 0}, 800, function(){
if(j+1 === jmax){
j=0;
}else{
j++;
}
jQuery(this).css('display', 'none').animate({opacity: 0}, 10);
cycle(variable, j);
});
}
I've tried setting a timeout and clearing it but it doesn't seem to do anything (it seems to ignore the timeout entirely), I've tried using stop() and calling the function again on mouseout but that seemed to repeat the function call (I was seeing duplicates) and it stopped mid animation which didn't work. I tried adding in a default variable at one point (var pause = false || true;) but I also couldn't get it to work as expected (though I feel the solution relies on that variable). I'm open to all suggestions but there are some rules of engagement:
Rules: There can't be any major changes in how this function works as many things rely on it, it's something I do not have control over. Assume the function call looks like this jQuery('#divList', 0) and holds a bunch of div elements as children.
The timeout function is the last solution I tried which looks like:
jQuery('#divList').on('mouseenter', function(){
setTimeout(cycle, 100000);
})
.on('mouseleave', function(){
window.clearTimeout();
});

Perhaps something like this? I simplified the animation just to make the example simpler, but you should be able to adapt it to your needs.
First, we have a function that's responsible for animating a set of elements. Every function call returns a new function that allows to toggle the animation (transition between pause and resume).
function startCycleAnimation(els) {
var index = 0,
$els = $(els),
$animatedEl;
animate($nextEl());
return pauseCycleAnimation;
function animate($el, startOpacity) {
$el.css('opacity', startOpacity || 1)
.animate({ opacity: 0 }, 800, function () {
animate($nextEl());
});
}
function $nextEl() {
index = index % $els.length;
return $animatedEl = $els.slice(index++, index);
}
function pauseCycleAnimation() {
$animatedEl.stop(true);
return resumeCycleAnimation;
}
function resumeCycleAnimation() {
animate($animatedEl, $animatedEl.css('opacity'));
return pauseCycleAnimation;
}
}
Then we can kick-start everything with something like:
$(function () {
var $animationContainer = $('#animation-container'),
toggleAnimation = startCycleAnimation($animationContainer.children('div'));
$animationContainer.mouseenter(pauseOrResume).mouseleave(pauseOrResume);
function pauseOrResume() {
toggleAnimation = toggleAnimation();
}
});
Example HTML
<body>
<div id="animation-container">
<div>Div 1</div>
<div>Div 2</div>
<div>Div 3</div>
</div>
</body>
If you want something more generic, it seems there's a plugin that overrides animate and allows to pause/resume animations in a generic way.

You will need to put a flag that each cycle checks before it determines if it is going to run. Then you can just change that flag when the mouse events are triggered. If you need to pick up where you left off when you unpause, consider saving the last value of j
function cycle(variable, j){
if (window.paused) {
window.last_j = j;
return;
}
...
Then when you want to pause, just set window.paused = true . To resume, change it back to false and call cycle again:
cycle(variable, last_j);

Related

Repeat a javascript function with a delay in it

I have a javascript function that cycles through background images inside of a div. It works well, but it stops when it runs through all the images. How can I call this again when it's done?
$(document).ready(function(){
FlipBackgroundOver();
});
function FlipBackgroundOver()
{
$(".bkgrndimg").each(function(index) {
$(this).hide();
$(this).delay(5000 * index).fadeIn(5000).fadeOut();
});
}
If I add FlipBackgroundOver(); inside the function, it goes into an infinite loop because the delay doesn't stop the function execution.
Here is a fiddle with what I'm working with: http://jsfiddle.net/RyGKV/
On jq 1.6+, you should use promise() relevant callback:
function test() {
$("img").each(function(index) {
$(this).delay(3000 * index).fadeIn(3000).fadeOut();
}).promise().done(test);
}
-jsFiddle-
You can use setInterval to repeat execution with a dealy
$(document).ready(function(){
setInterval(FlipBackgroundOver, 10000); // delay should be based on the number of images => numer_of_images * delay_for_each
});
You can change the delay to value you want based on the number of images you have.
Create a loop in an infinite interval by your own. I used the helper function next for this.
var elements = $("img");
var index = 0;
function next() {
index = ++index == elements.length ? 0 : index;
elements.hide().eq(index).fadeIn(3000).fadeOut();
}
function start() {
// show first one directly
next();
// start infinite loop
setInterval(function() {
next();
}, 3000);
}
start();
Working example.

Clearing setInterval() Issue

I have a setInterval on a function X that runs every 500ms. In this function X, I call another function Y that essentially binds an event on some divs. However, I would like to unbind these events the next time the function X is called (to start "fresh"). My code doesn't seem to work:
setInterval(this.board.updateBoard, 500); //called from another constructor
This then initiates the functions below:
Board.prototype.updateBoard = function() {
//I attempt to unbind ALL my divs
var divs = this.$el.find("div");
for(var i = 0; i < divs.length; i++) {
$(divs[i]).unbind(); //Apparently this doesn't work?
}
//...some code here...
//find appropriate $div's (multiple of them), and then calls this.beginWalking() below on each of those
//loop here
this.beginWalking($div, direction + "0", direction + "1");
//end of loop
}
//alternate between classes to give appearance of walking
Board.prototype.beginWalking = function ($div, dir0, dir1) {
return setInterval(function () {
if ($div.hasClass(dir0)) {
$div.removeClass(dir0);
$div.addClass(dir1);
} else {
$div.removeClass(dir1);
$div.addClass(dir0);
}
}.bind(this), 80);
};
Basically, updateBoard is called every 500ms. Each time it's called, beginWalking is called to set another interval on a div. The purpose of this other interval, which functions correctly, is to add and remove a class every 80ms. I just can't seem to unbind everything before the next updateBoard is called.
Any suggestions appreciated!
use clearInterval()
edit: $(selector).toggleClass(dir0) might also be helpful
// In other file, use a global (no var) if you need to read it from another file:
updaterGlobal = setInterval(this.board.updateBoard, 500);
// store interval references for clearing:
var updaterLocals = [];
Board.prototype.updateBoard = function() {
//I attempt to unbind ALL my divs
var divs = this.$el.find("div");
// Stop existing div timers:
while(updaterLocals.length > 0){
clearInterval(updaterLocals[0]);
updaterLocals.shift(); // remove the first timer
}
//...some code here...
//loop here to call the below on several $div's
this.beginWalking($div, direction + "0", direction + "1");
//end of loop
}
//alternate between classes to give appearance of walking
Board.prototype.beginWalking = function ($div, dir0, dir1) {
var interval = setInterval(function () {
if ($div.hasClass(dir0)) {
$div.removeClass(dir0);
$div.addClass(dir1);
} else {
$div.removeClass(dir1);
$div.addClass(dir0);
}
}.bind(this), 80);
// Save the timer:
updaterLocals.push(interval);
return;
};

Can I detect if an arbitrary CSS transition has started

In my close function I want to do all my DOM clean-up stuff after css transitions have finished running. But there might not be any transitions running/might be multi-stage ones - (maintaining the stylesheets is out of my hands).
How would I go about writing a function something like the following
function close () {
myEl.removeClass('open');
if (animation is running/about to be run) {
// wait for transition to end, then recursively check to see if another
// one has started, wait for that ...
// then
cleanUpDOM();
} else {
cleanUpDOM();
}
}
My thoughts so far are to wrap the initial check in a timeout/requestAnimationFrame in order to give the animation a chance to start then checking to see if it's running. Unfortunately, without a transitionstart event I have no idea how to check if a transition has begun.
edit Answers recommending jquery are irrelevant as jquery animations are javascript animations, not CSS transitions
About transitionStart and transitionEnd events:
The transition can't starts from nowhere. Usually transition starts after some event, where you change the state of DOM element by changing styles by class or something else. So you know when transition starts because you start it in your code.
During the transition user I/O don't blocks, so transition is asynchronous and then transition will end you don't know right. So you needs transitionEnd event to do something then transition has finished in javascript.
About transitionEnd event:
Just look the jsfiddle
Here's my solution so far - a bit hacky and only works when which element might transition is known, and doesn't work with transition-property: all... but it's a promising start
function toCamelStyleProp (str) {
return str.replace(/(?:\-)([a-z])/gi, function ($0, $1) {
return $1.toUpperCase();
});
}
function toHyphenatedStyleProp (str) {
return str.replace(/([A-Z])/g, function (str,m1) {
return '-' + m1.toLowerCase();
}).replace(/^ms-/,'-ms-');
}
function getPrefixedStyleProp (prop) {
prop = toCamelStyleProp(prop);
prop = Modernizr.prefixed(prop);
return toHyphenatedStyleProp(prop);
}
function getStyleProperty (el, prop) {
return getComputedStyle(el,null).getPropertyValue(getPrefixedStyleProp(prop));
}
function doAfterTransition ($wrapper, cssClass, mode, $transitioningEl, callback) {
$transitioningEl = $transitioningEl || $wrapper;
var transitioningEl = $transitioningEl[0],
duration = +getStyleProperty(transitioningEl, 'transition-duration').replace(/[^\.\d]/g, ''),
transitioners = getStyleProperty(transitioningEl, 'transition-property').split(' '),
initialState = [],
changedState = [],
i,
callbackHasRun = false,
//makes sure callback doesn't get called twice by accident
singletonCallback = function () {
if (!callbackHasRun) {
callbackHasRun = true;
callback();
}
};
// if no transition defined just call the callback
if (duration === 0) {
$wrapper[mode + 'Class'](cssClass);
callback();
return;
}
for (i = transitioners.length - 1;i>=0;i--) {
initialState.unshift(getStyleProperty(transitioningEl, transitioners[i]));
}
$wrapper[mode + 'Class'](cssClass);
setTimeout(function () {
for (i = transitioners.length - 1;i>=0;i--) {
changedState.unshift(getStyleProperty(transitioningEl, transitioners[i]));
}
for (i = transitioners.length - 1;i>=0;i--) {
if (changedState[i] !== initialState[i]) {
$transitioningEl.transitionEnd(singletonCallback);
// failsafe in case the transitionEnd event doesn't fire
setTimeout(singletonCallback, duration * 1000);
return;
}
}
singletonCallback();
}, 20);
}
There is no way (that I know of) to detect if a transition is currently working in the background without knowing the element that is being transitioned.
However, if you can move away from transition to key frame animations, then you'd have the so needed event - animationStart and animationEnd and then it will be easy to figure out if there are running animations.
If you're planning to make css transition, you can check out jQuery Transit Plugin http://ricostacruz.com/jquery.transit/
Very powerfull and useful, you can get transform x value with. css('x') for example.
Have you tried the JQuery pseudo ":animated"?
if( $(elem).is(':animated') ) {...}
See More http://api.jquery.com/animated-selector/
Here is a function that waits for the page Html to become stable. i.e. when all animations are finished. In the example below it waits for the Html to be unchanging for 200 milliseconds and a maximum timeout of 2 seconds.
Call the function with ...
waitUntilHtmlStable(yourCallback, 200, 2000);
The function ...
waitUntilHtmlStable = function (callback, unchangedDuration, timeout, unchangedElapsed, html) {
var sleep = 50;
window.setTimeout(function () {
var newHtml = document.documentElement.innerHTML;
if (html != newHtml) unchangedElapsed = 0;
if (unchangedElapsed < unchangedDuration && timeout > 0)
waitUntilHtmlStable(callback, unchangedDuration, timeout - interval, unchangedElapsed + interval, newHtml);
else
callback();
}, sleep);
};
In my case I wanted to be sure new elements where present. If you want to track animation movement then change the document.documentElement.innerHTML to
JSON.stringify(Array.prototype.slice.call(document.documentElement.getElementsByTagName("*"), 0)
.map(function(e) {
var x = e;
var r = x.getBoundingClientRect();
while (r.width == 0 || r.height == 0) {
x = x.parentNode;
r = x.getBoundingClientRect();
}
return r;
}));
There is an unprefixed transitionstart event in IE10+. It is even cancelable.
https://msdn.microsoft.com/library/dn632683%28v=vs.85%29.aspx
On animation.css i found this.
You can also detect when an animation ends:
$('#yourElement').one('webkitAnimationEnd mozAnimationEnd MSAnimationEnd oanimationend animationend', doSomething);
read full doc here
you could use Jquery which would be much easier for example you could use .animate like this
(function(){
var box = $('div.box')
$('button').on('click', function(){
box.animate({ 'font-size' : '40px'})
.animate({'color': 'red'});
})
})();
or simply do a callback function

Fade in Each Thumbnail?

all of my thumbnails have the class .thumb
I'm trying to fade in each at a time like I've seen on some websites but it doesnt seem to be working. My code is below
$('.thumb').eachDelay(function(){
$(this).fadeIn('1000');
}, 100);
What is wrong here?
Here is a fiddle: http://jsfiddle.net/NPWGu/
I've tried a ton of the solutions and havent had any luck yet so hopefully this will help make it easier to understand how I have my code setup
Try this:
$(".thumb").each(function(i) {
$(this).delay(500*i).fadeIn(1000);
});
I'm not aware of any jQuery method called eachDelay(). As it is not quite clear from your question which exact scenario you want, here are four different different options:
1) If you want to fade them all in at once, you would use this:
$('.thumb').fadeIn('1000');
2) If you want to fade them all in at the same time after delay, you can use this:
$('.thumb').delay(1000).fadeIn('1000'); 
3) If you want to fade them all in one after another, you can use this:
function sequentialFade(selector) {
var items$ = $(selector);
var index = 0;
function next() {
if (index < items$.length) {
items$.eq(index++).fadeIn('1000', next);
}
}
next();
}
sequentialFade('.thumb');
4) If you want to fade them all in one after another with a delay between the finish of one and the start of the next, you can use this:
function sequentialFade(selector, delayTime) {
var items$ = $(selector);
var index = 0;
function next() {
if (index < items$.length) {
items$.eq(index++).delay(delayTime).fadeIn(1000, next);
}
}
next();
}
sequentialFade('.thumb', 1000);
Working demonstration of this last option here: http://jsfiddle.net/jfriend00/zg8S4/
Serial animation code should look like (you can change the 1000ms below as needed):
var cur = -1;
var thumbs = $(".thumb");
function doIt() {
if(cur == -1) cur = 0;
else if(cur < thumbs.length-1) cur++;
else return;
$(thumbs[cur]).fadeIn(1000, doIt);
}
doIt();

Javascript, local copying of a variable's value... struggling to achieve it

I have what i thought was a simple javascript / jquery function (fade out of one div, fade into another... loop until it reaches a maximum and then start back from the begining. The problem i have though is that to fadein the next div i need to increment the global counter. Doing this increments double increments it because i'm assuming the local variable i've created maintains the same reference to the global variable.
The code sample below should explain a little easier. Can anyone spot what i'm doing wrong?
var current_index = 1;
$(document).ready(function() {
$(function() {
setInterval("selectNextStep()", 3000);
});
});
function selectNextStep() {
$("#step_"+current_index).fadeOut('slow', function() {
var next = current_index;
next = next + 1;
$("#step_"+next).fadeIn('slow', function() {
if (current_index == 4) current_index = 1;
else current_index ++;
});
});
}
I think you're ending up with race conditions due to the interval trying to fade things in and the callbacks trying to fade things out. For this setup it makes more sense to let the fade callbacks start the next round.
Also using a 0-based index makes the math easier.
var current_index = 0; // zero indexes makes math easier
$(document).ready(function () {
$(function () {
// use timeout instead of interval, the fading callbacks will
// keep the process going
setTimeout(selectNextStep, 3000);
});
});
function selectNextStep() {
// +1 to adapt index to element id
$("#step_" + (current_index + 1)).fadeOut('slow', function () {
var next = current_index + 1;
// keeps index in range of 0-3
next = next % 4; // assuming you have 4 elements?
current_index = (current_index + 1) % 4;
// callback will start the next iteration
$("#step_" + (next + 1)).fadeIn('slow', function () {
setTimeout(selectNextStep, 3000);
});
});
}
demo: http://jsbin.com/exufu
I do not see any double increment the way your code is..
the problem is that the next variable goes beyond the 4 value that seems to be the limit, and trying to fadein an element that does not exist. so the code that resets the currentIndex never executes..
try adding if (next > 4 ) next = 1; after increasing the next variable
Example at http://jsfiddle.net/5zeUF/
isn't $(function() {}); the same as $(document).ready(function(){}), so you are initializing selectNextStep twice (hence the double increment)?
Try this. Simplifies things a little. Increments (and resets if needed) the current_index before the next fadeIn().
Example: http://jsfiddle.net/r7BFR/
var current_index = 1;
function selectNextStep() {
$("#step_" + current_index).fadeOut('slow', function() {
current_index++;
if (current_index > 4) current_index = 1;
$("#step_" + current_index).fadeIn('slow');
});
}
$(document).ready(function() {
setInterval(selectNextStep, 3000);
});
EDIT: Added example, and fixed my misspelling (camelCase) of current_index.
Here's an alternate way of doing the increment:
current_index = (current_index % 4) + 1;
Try this, slightly different approach but does what you need it to do I believe, also you can add more steps without modifying the script and doesn't pollute the global namespace (window)
[HTML]
​<div class="step defaultStep">One</div>
<div class="step">Two</div>
<div class="step">Three</div>
<div class="step">Four</div>
<div class="step">Five</div>
[CSS]
.step { display: none; }
.defaultStep { display: block; }​
[JS]
$( function() {
var steps = $( ".step" );
var interval = setInterval( function( ) {
var current = $( ".step" ).filter( ":visible" ), next;
if( current.next( ).length !== 0 ) {
next = current.next( );
} else {
next = steps.eq(0);
}
current.fadeOut( "slow", function( ) {
next.fadeIn( "slow" );
} );
}, 3000);
} );
Maybe you also want to have a look at the cycle plugin for jquery. There you can actually achieve such nice transitions. I think with a little work this would ease up everything.
http://jquery.malsup.com/cycle/
Regarding your code snippet. I think you can enhance it a little in this way:
$(document).ready(function() {
var current_index = 0;
window.setInterval(function() {
$("#step_"+ current_index).fadeOut('slow', function() {
$("#step_" + (current_index + 1)).fadeIn('slow', function() {
current_index = (current_index + 1) % 4;
});
});
}, 3000);
});
This should do the exact same work. As the interval function closes over the current_index variable it should be valid inside the function. Sorry, if you're not a fan of all these closures but I rather preferr passing the function I want to execute directly to the setInterval function, than defining it anywhere else.
P.S. Be aware that the changes I introduced imply that your #step_ IDs start at 0.

Categories

Resources