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;
};
Related
I have a click event that has a Jquery animation in it.
How can i guarantee that the animation has finished when multiple click events are being fired.
$(this._ScrollBarTrack).click(function(e) {
if(e.target === this && _self._horizontalClickScrollingFlag === false){
_self._horizontalClickScrollingFlag = true;
if(_self._isVertical){
} else{ //horizontal
if(e.offsetX > (this.firstChild.offsetWidth + this.firstChild.offsetLeft)){ // Moving Towards Right
var scrollableAmountToMove = _self._arrayOfCellSizes[_self._scrollBarCurrentStep + 1]; // additional amount to move
var scrollableCurrentPosition = -($(_self._bodyScrollable).position().left);
var scrollBarCurrentPosition = $(_self._ScrollBarTrackPiece).position().left;
var scrollBarAmountToMove = _self.getScrollBarTrackPiecePositionBasedOnScrollablePosition(scrollableAmountToMove);
$(".event-scroll-horizontally").animate({left:-(scrollableCurrentPosition+ scrollableAmountToMove)});
$(_self._ScrollBarTrackPiece).animate({left: (scrollBarCurrentPosition + scrollBarAmountToMove)});
_self._scrollBarCurrentStep += 1;
} else{
var scrollableAmountToMove = _self._arrayOfCellSizes[_self._scrollBarCurrentStep - 1]; // additional amount to move
var scrollableCurrentPosition = -($(_self._bodyScrollable).position().left);
var scrollBarCurrentPosition = $(_self._ScrollBarTrackPiece).position().left;
var scrollBarAmountToMove = _self.getScrollBarTrackPiecePositionBasedOnScrollablePosition(scrollableAmountToMove);
$(".event-scroll-horizontally").animate({left:-(scrollableCurrentPosition - scrollableAmountToMove)});
$(_self._ScrollBarTrackPiece).animate({left: (scrollBarCurrentPosition - scrollBarAmountToMove)});
_self._scrollBarCurrentStep -= 1;
}
}
_self._horizontalClickScrollingFlag = false;
}
});
jQuery has a hidden (I'm not sure why it's not in the docs someplace) variable $.timers that you can test against.
I made this function a long time ago to handle situations like this. Mind you, this will test to make sure there are NO animations currently being executed.
function animationsTest (callback) {
var testAnimationInterval = setInterval(function () {
if ($.timers.length === 0) { // any page animations finished
clearInterval(testAnimationInterval);
callback(); // callback function
}
}, 25);
};
Useage: jsFiddle DEMO
animationsTest(function () {
/* your code here will run when no animations are occuring */
});
If you want to test against one individually you could do a class/data route.
$('#thing').addClass('animating').animate({ left: '+=100px' }, function () {
// your callback when the animation is finished
$(this).removeClass('animating');
});
You could declare a global boolean called isAnimating and set it to true right when you begin the animation. Then add a done or complete function to the animation that sets it back to false. Then set your click event to only begin the animation if isAnimating is false.
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);
Please take a look at this function:
var msgdiv, i=0;
msgdiv=$("#message");
messages=["Welcome!","Добро пожаловать!"];
function fadeMessages(messages, div){
while(i<messages.length){
div.fadeOut(1000).html('').append(messages[i]).fadeIn(1000);
i=i+1;
}
}
fadeMessages(messages,msgdiv);
What I want to do is, to show one by one elements of array. But, function above shows only last element of array messages.length time. Where I did wrong?
Live example: http://jsfiddle.net/QQy6X/
The while executes much much faster than the fadeOut/fadeIn calls, so you only see the last result. You need to make each animation wait for the previous ones to finish.
I like to solve these problems recursively. Note, it does alter the messages array, but it's not too hard to convert this to use a counter instead (like your original version). Here you go:
var $msgdiv = $('#message'),
messages = ['Xoş gəlmişsiniz!', 'Welcome!', 'Добро пожаловать!'];
function showNext() {
var msg = messages.shift();
if (msg) {
$msgdiv.fadeOut(1000, function () {
$(this).text(msg).fadeIn(1000, showNext);
});
}
}
showNext();
Demo: http://jsfiddle.net/mattball/Exj95/
Here's a version that leaves messages intact:
var $msgdiv = $('#message'),
messages = ['Xoş gəlmişsiniz!', 'Welcome!', 'Добро пожаловать!'],
i = 0;
function showNext() {
if (i < messages.length) {
var msg = messages[i];
$msgdiv.fadeOut(1000, function () {
i++;
$(this).text(msg).fadeIn(1000, showNext);
});
}
}
showNext();
Demo: http://jsfiddle.net/mattball/wALfP/
Your while loop finishes executing before the div has had a chance to fade out. Use an if statement and recursion:
var msgdiv = $("#message");
var i = 0;
var messages = ["Welcome!", "Добро пожаловать!"];
(function fadeMessages() {
if (i in messages) {
msgdiv.fadeOut(1000, function() {
$(this).html('').append(messages[i++]).fadeIn(1000, fadeMessages);
});
}
})();
http://jsfiddle.net/QQy6X/6/
Your while loop finishes very quickly; instead you should wait for the animation to finish before starting the next one. This is easy to do by adding a callback function to fadeIn. I would use this:
+function(){
var $msgdiv = $("#message");
var i = -1;
var messages = ["Xoş gəlmişsiniz!","Welcome!","Добро пожаловать!"];
+function fadeNext(){
$msgdiv.fadeOut(1000, function(){
$msgdiv.text(messages[i = (i + 1) % messages.length]);
$msgdiv.fadeIn(1000, fadeNext);
});
}();
}();
http://jsfiddle.net/Paulpro/QQy6X/7/
In jQuery, you can't intertwine animations and non-animations in the way you are doing and expect them to run in the right order. The animations will go into the animation queue and get sequenced one after another there, but the non-animations will all run immediately. Thus, things won't happen in the right order.
To do something like you want to do, you can use code like this.
messages=["Welcome!","Добро пожаловать!"];
function fadeMessages(msgs, div) {
var i = 0;
function next() {
if (i < msgs.length) {
div.fadeOut(1000, function() {
div.html(msgs[i++]).fadeIn(1000, next);
});
}
}
next();
}
fadeMesssages(messages, div);
This uses the completion functions of both fadeIn() and fadeOut() to carry out the next steps. Here's how it works:
It fades out the div.
In the completion function of the fadeOut, it sets the next message and then starts the fadeIn.
It advances the message counter.
In the completion function from the fadeIn, it calls the function to start the next iteration.
If you want a delay before the fadeOut (to make sure a given message displays for a certain amount of time), you can add that with this .delay(2000) added in the right place:
messages=["Welcome!","Добро пожаловать!"];
function fadeMessages(msgs, div) {
var i = 0;
function next() {
if (i < msgs.length) {
div.delay(2000).fadeOut(1000, function() {
div.html(msgs[i++]).fadeIn(1000, next);
});
}
}
next();
}
fadeMesssages(messages, div);
If you want a delay before the next iteration starts, you can do that like this with a setTimeout:
messages=["Welcome!","Добро пожаловать!"];
function fadeMessages(msgs, div) {
var i = 0;
function next() {
if (i < msgs.length) {
div.fadeOut(1000, function() {
div.html(msgs[i++]).fadeIn(1000, function() {
setTimeout(next, 2000);
});
});
}
}
next();
}
fadeMesssages(messages, div);
I have this function which acts as a loading box using setInterval to change the background images which creates a flashing effect.
function loading() {
clearInterval(start);
var i = 0;
function boxes() {
in_loading = ".in_loading:eq(" + i + ")";
$(".in_loading").css("background", "url(images/load_bar_green.png) no-repeat");
$(in_loading).css("background", "url(images/load_bar_blue.png) no-repeat");
if(i == 3) {
i = 0;
} else {
i++;
}
}
var start = setInterval(function() {
boxes();
}, 350);
}
But even with clearInterval if I click on it more than once the flashing goes out of order. I tried removing the boxes, hiding them but I can't seem to get the 'buffer' cleared? Any ideas?
The reason why it keeps flashing is because every time loading gets called it creates a new variable start, so clearInterval is actually doing nothing. You also shouldn't have the boxes function within loading because it is doing the same thing, creating a new boxes function every time loading is called. This will add a lot of lag the longer the script executes.
var i = 0;
var start;
function loading() {
clearInterval(start);
start = setInterval(function() {
boxes();
}, 350);
}
function boxes() {
var in_loading = ".in_loading:eq(" + i + ")";
$(".in_loading").css("background", "url(images/load_bar_green.png) no-repeat");
$(in_loading).css("background", "url(images/load_bar_blue.png) no-repeat");
if(i == 3) {
i = 0;
} else {
i++;
}
}
Function declarations get "hoisted" to the top of their scope, this is what is messing the execution order. Check this: http://javascriptweblog.wordpress.com/2010/07/06/function-declarations-vs-function-expressions/
The reason is every time you call loading it creates a new Interval or new var start. So if you click it twice, then you have two things manipulating the same data. So you need to have the var start outside of the function and the clearInterval inside. So every time you call loading it clears the interval and creates a new one.
var i = 0;
var start;
function loading() {
clearInterval(start);
start = setInterval(boxes, 350);
}
function boxes() {
in_loading = ".in_loading:eq(" + i + ")";
$(".in_loading").css("background", "url(images/load_bar_green.png) no-repeat");
$(in_loading).css("background", "url(images/load_bar_blue.png) no-repeat");
if(i == 3) {
i = 0;
} else {
i++;
}
}
maybe you should take a look at this Jquery Plugin , it seems to manage intervals very well .
Jquery Timers Plugin
I try to make a page to go to the startpage after eg. 10sec of inactivity (user not clicking anywhere). I use jQuery for the rest but the set/clear in my test function are pure javascript.
In my frustation I ended up with something like this function that I hoped I could call on any click on the page. The timer starts fine, but is not reset on a click. If the function is called 5 times within the first 10 seconds, then 5 alerts will apear... no clearTimeout...
function endAndStartTimer() {
window.clearTimeout(timer);
var timer;
//var millisecBeforeRedirect = 10000;
timer = window.setTimeout(function(){alert('Hello!');},10000);
}
Any one got some lines of code that will do the trick?
- on any click stop, reset and start the timer.
- When timer hits eg. 10sec do something.
You need to declare timer outside the function. Otherwise, you get a brand new variable on each function invocation.
var timer;
function endAndStartTimer() {
window.clearTimeout(timer);
//var millisecBeforeRedirect = 10000;
timer = window.setTimeout(function(){alert('Hello!');},10000);
}
The problem is that the timer variable is local, and its value is lost after each function call.
You need to persist it, you can put it outside the function, or if you don't want to expose the variable as global, you can store it in a closure, e.g.:
var endAndStartTimer = (function () {
var timer; // variable persisted here
return function () {
window.clearTimeout(timer);
//var millisecBeforeRedirect = 10000;
timer = window.setTimeout(function(){alert('Hello!');},10000);
};
})();
That's because timer is a local variable to your function.
Try creating it outside of the function.
A way to use this in react:
class Timeout extends Component {
constructor(props){
super(props)
this.state = {
timeout: null
}
}
userTimeout(){
const { timeout } = this.state;
clearTimeout(timeout);
this.setState({
timeout: setTimeout(() => {this.callAPI()}, 250)
})
}
}
Helpful if you'd like to only call an API after the user has stopped typing for instance. The userTimeout function could be bound via onKeyUp to an input.
Not sure if this violates some good practice coding rule but I usually come out with this one:
if(typeof __t == 'undefined')
__t = 0;
clearTimeout(__t);
__t = setTimeout(callback, 1000);
This prevent the need to declare the timer out of the function.
EDIT: this also don't declare a new variable at each invocation, but always recycle the same.
Hope this helps.
Practical example Using Jquery for a dropdown menu !
On mouse over on #IconLoggedinUxExternal shows div#ExternalMenuLogin and set time out to hide the div#ExternalMenuLogin
On mouse over on div#ExternalMenuLogin it cancels the timeout.
On mouse out on div#ExternalMenuLogin it sets the timeout.
The point here is always to invoke clearTimeout before set the timeout, as so, avoiding double calls
var ExternalMenuLoginTO;
$('#IconLoggedinUxExternal').on('mouseover mouseenter', function () {
clearTimeout( ExternalMenuLoginTO )
$("#ExternalMenuLogin").show()
});
$('#IconLoggedinUxExternal').on('mouseleave mouseout', function () {
clearTimeout( ExternalMenuLoginTO )
ExternalMenuLoginTO = setTimeout(
function () {
$("#ExternalMenuLogin").hide()
}
,1000
);
$("#ExternalMenuLogin").show()
});
$('#ExternalMenuLogin').on('mouseover mouseenter', function () {
clearTimeout( ExternalMenuLoginTO )
});
$('#ExternalMenuLogin').on('mouseleave mouseout', function () {
clearTimeout( ExternalMenuLoginTO )
ExternalMenuLoginTO = setTimeout(
function () {
$("#ExternalMenuLogin").hide()
}
,500
);
});
This works well. It's a manager I've made to handle hold events. Has events for hold, and for when you let go.
function onUserHold(element, func, hold, clearfunc) {
//var holdTime = 0;
var holdTimeout;
element.addEventListener('mousedown', function(e) {
holdTimeout = setTimeout(function() {
func();
clearTimeout(holdTimeout);
holdTime = 0;
}, hold);
//alert('UU');
});
element.addEventListener('mouseup', clearTime);
element.addEventListener('mouseout', clearTime);
function clearTime() {
clearTimeout(holdTimeout);
holdTime = 0;
if(clearfunc) {
clearfunc();
}
}
}
The element parameter is the one which you hold. The func parameter fires when it holds for a number of milliseconds specified by the parameter hold. The clearfunc param is optional and if it is given, it will get fired if the user lets go or leaves the element. You can also do some work-arounds to get the features you want. Enjoy! :)
<!DOCTYPE html>
<html>
<body>
<h2>EJEMPLO CONOMETRO CANCELABLE</h2>
<button onclick="inicioStart()">INICIO</button>
<input type="text" id="demostracion">
<button onclick="finStop()">FIN</button>
<script>
let cuenta = 0;
let temporalTiempo;
let statusTime = false;
function cronometro() {
document.getElementById("demostracion").value = cuenta;
cuenta++;
temporalTiempo = setTimeout(cronometro, 500);
}
function inicioStart() {
if (!Boolean(statusTime)) {
statusTime = true;
cronometro();
}
}
function finStop() {
clearTimeout(temporalTiempo);
statusTime = false;
}
</script>
</body>
</html>