Complex recursive function call with multiple setTimeouts (clearing issue) - javascript

I've created a function that animates between tiles which I'd like to pause on hover.
Here's a basic overview of how it works: there are 3 tiles which are beside each other that cycle through their own dinstinct sets of tiles, on hover that particular tile should pause, and then when moused out it should resume.
In the code, there's a timer in which a class is added to the tile which animates it and appends the next tile to it. Then, inside this timer, another setTimeout is created which removes the animation class, removes the tile that was just shown and shows the tile that just got appended. Then, there's a final setTimeout which recursively calls the function again with all the same passed parameters (so it loops forever). Inside the function I have events binded which watch for mouse over and mouse out on the indivual tiles. They are meant to pause the timers on hover and start it again on mouse out.
The function works as intended except the mouseout binding calling the function recursively for some reason gets called twice for 2 or 3 different tiles (1-2 of them not being the tile I hovered over) and causes weird results. Anyone know this part:
.one("mouseout", function () {
animateTiles(content, tile);
});
gets called multipled times with different parameters on mouseout?
Here is a simplified version of the code:
var l_tAnimateTilesTimer = null,
l_tAnimateTilesFlipTimer = null,
l_tAnimateTilesRecursiveTimer = null;
function animateTiles(content, tile) {
l_tAnimateTilesTimer = setTimeout(function () {
appendNextTileContent(content, tile);
tile.addClass(l_sAnimationClass);
l_tAnimateTilesFlipTimer = setTimeout(function () {
tile.removeClass(l_sAnimationClass)
.find(".front").remove();
tile.find(".back").removeClass("back").addClass("front");
l_tAnimateTilesRecursiveTimer = setTimeout(function () {
animateTiles(content, tile);
}, 5000);
}, 300);
}, 2000);
tile.off().one("mouseover", function () {
if (l_tAnimateTilesTimer !== null) {
clearTimeout(l_tAnimateTilesTimer);
l_tAnimateTilesTimer = null;
}
if (l_tAnimateTilesFlipTimer !== null) {
clearTimeout(l_tAnimateTilesFlipTimer);
l_tAnimateTilesFlipTimer = null;
}
if (l_tAnimateTilesRecursiveTimer !== null) {
clearTimeout(l_tAnimateTilesRecursiveTimer);
l_tAnimateTilesRecursiveTimer = null;
}
}).one("mouseout", function () {
animateTiles(content, tile);
});
};
var tile = $("#tile_wrap .right");
var content = {
title = "tile 1",
id = "tile_1_id"
};
animateTiles(tile, content); //an example of calling it
Let me know if you need me to clarify anything. I would greatly appreciate any help, thanks.

Mouseover and mouseout will be triggered on the element or any of it's nested elements. Try to use mouseenter and mouseleave instead.

I figured out the answer if anyone's curious, I needed to move the setTimeout variable creations inside the function (so they are local to the specific function call). I also added the mouse events inside the l_tAnimateTilesTimer as well, I'm not sure if this is necessary though. This is the complete example:
function animateTiles(content, tile) {
var l_tAnimateTilesTimer = null,
l_tAnimateTilesFlipTimer = null,
l_tAnimateTilesRecursiveTimer = null;
l_tAnimateTilesTimer = setTimeout(function () {
tile.off().one("mouseover", function () {
if (l_tAnimateTilesTimer !== null) {
clearTimeout(l_tAnimateTilesTimer);
l_tAnimateTilesTimer = null;
}
if (l_tAnimateTilesFlipTimer !== null) {
clearTimeout(l_tAnimateTilesFlipTimer);
l_tAnimateTilesFlipTimer = null;
}
if (l_tAnimateTilesRecursiveTimer !== null) {
clearTimeout(l_tAnimateTilesRecursiveTimer);
l_tAnimateTilesRecursiveTimer = null;
}
}).one("mouseout", function () {
animateTiles(content, tile);
});
appendNextTileContent(content, tile);
tile.addClass(l_sAnimationClass);
l_tAnimateTilesFlipTimer = setTimeout(function () {
tile.removeClass(l_sAnimationClass)
.find(".front").remove();
tile.find(".back").removeClass("back").addClass("front");
l_tAnimateTilesRecursiveTimer = setTimeout(function () {
animateTiles(content, tile);
}, 5000);
}, 300);
}, 2000);
tile.off().one("mouseover", function () {
if (l_tAnimateTilesTimer !== null) {
clearTimeout(l_tAnimateTilesTimer);
l_tAnimateTilesTimer = null;
}
if (l_tAnimateTilesFlipTimer !== null) {
clearTimeout(l_tAnimateTilesFlipTimer);
l_tAnimateTilesFlipTimer = null;
}
if (l_tAnimateTilesRecursiveTimer !== null) {
clearTimeout(l_tAnimateTilesRecursiveTimer);
l_tAnimateTilesRecursiveTimer = null;
}
}).one("mouseout", function () {
animateTiles(content, tile);
});
};
var tile = $("#tile_wrap .right");
var content = {
title = "tile 1",
id = "tile_1_id"
};
animateTiles(tile, content); //an example of calling it

Related

Calling function with setTimeout in rapid fashion

I want the backgroundColor of a div to change for 250ms, and then change back. for this I use the following code as onclick on the div:
function keyAnimation(key) {
basicColor = key.style.backgroundColor;
key.style.backgroundColor = "red";
setTimeout(function () {
key.style.backgroundColor = basicColor;
}, 250);
But when I click the div multiple times quickly (within the 250ms) it remains red.
How can I code this so it will always go back to the basicColor after 250ms?
Add a flag that blocks additional keypresses:
var running = false;
function keyAnimation(key) {
if(running) return;
running = true;
const basicColor = key.style.backgroundColor;
key.style.backgroundColor = "red";
setTimeout(function () {
key.style.backgroundColor = basicColor;
running = false;
}, 250);
}

Update event listeners when page switches

I'm in a bit of a predicament, because I need to somehow update event listeners, when the page changes using ajax. I have a specific element that I need to update based on what page the ajax call injects. Here's my issue:
I have this slider control constructor:
UI.CONTROLS.SLIDER = function (select, action, actionWhenActive, actionWhenSet) {
'use strict';
var self = this;
this.action = action;
this.select = select;
this.area = select.find($('area-'));
this.fill = select.find($('fill-'));
this.value = 0;
this.active = false;
this.actionWhenActive = actionWhenActive;
this.actionWhenSet = actionWhenSet;
function eventlisteners(self) {
$(document).on('mousemove', function (event) {
self.move(event, self);
});
$(document).on('mouseup', function (event) {
self.drop(event, self);
});
self.area.on('mousedown', function (event) {
self.grab(event, self);
});
}
eventlisteners(self);
this.reselect = function (element) {
self.area = element.find($('area-'));
self.fill = element.find($('fill-'));
eventlisteners(self);
};
};
UI.CONTROLS.SLIDER.prototype = {
action: this.action,
width: function () {
'use strict';
var calcWidth = ((this.value * 100) + '%');
this.fill.width(calcWidth);
},
update: function (event, self) {
'use strict';
if (this.actionWhenActive === true) {
this.action();
}
var direction, percent, container, area;
direction = event.pageX - this.area.offset().left;
percent = Math.min(Math.max(direction / this.area.width(), 0), 1.0);
this.value = percent;
this.width();
},
move: function (event, self) {
'use strict';
if (this.active === true) {
this.update(event);
}
},
grab: function (event, self) {
'use strict';
this.active = true;
self.update(event);
event.preventDefault();
},
drop: function (event, self) {
'use strict';
if (this.active === true) {
this.active = false;
this.action();
}
},
setValue: function (value) {
'use strict';
if (this.active === false) {
this.value = value;
this.width();
if (this.actionWhenSet === true) {
this.action();
}
}
}
};
This can create new sliders based on the container (select) specified. In my website, I have an audio player. So using ajax you can navigate while this audio player plays. I have two states, viewing a track, and not viewing a track. When you're not viewing the track that is playing, a transport bar will pop down from the header containing the scrubber (slider control), this scrubber is also inside the track view (viewing the track) page.
This code checks if you're viewing the track that is playing. audioFromView gets updated on the ajax calls, it basically replaces it with what track you're viewing. It then compares it with audioCurrent which is the track currently playing, UI.PAGE.TYPE is what type of page you're viewing, in this instance a track:
var audioViewIsCurrent = function () {
'use strict';
if (audioCurrent.src === audioFromView.src && UI.PAGE.TYPE === 'track') {
return true;
} else {
return false;
}
};
So this code then updates the scrubber based on the above code's output (audioElementTrackView is the scrubber inside the track page, and audioElementTransport is the scrubber inside the transport panel):
var audioScrubber = {
element: {},
active: false,
action: function () {
'use strict';
audioScrubber.active = this.active;
var time = audioSource.duration * this.value;
if (this.active === false) {
audioCurrent.time(this.value * duration);
}
},
set: function () {
'use strict';
var container, slider;
if (audioElementTrackView.length === 1) {
container = audioElementTrackView.find(audioElementScrubber);
} else {
container = audioElementTransport.find(audioElementScrubber);
}
this.element = new UI.CONTROLS.SLIDER(container, this.action, true);
},
reselect: function () {
if (audioElementTrackView.length === 1) {
container = audioElementTrackView.find(audioElementScrubber);
} else {
container = audioElementTransport.find(audioElementScrubber);
}
this.element.reselect(container)
}
};
audioScrubber.reselect()
So this works fine with how I'm currently doing it, HOWEVER since I am adding new event listeners everytime I update my scrubber object (inside the ajax call) in order to keep the scrubber working I am also piling them up, making the old event listeners take up space and RAM eventually making the site slow down to a halt (if you navigate enough)
I tested this using console.log on mouseup, everytime I switched page, it would log it twice, and then thrice and so on.
How can I avoid this?
You can delegate events to the nearest static ancestor, so you don't need to rebind them everyime.
Read this article
https://davidwalsh.name/event-delegate

How to stop method execution defined in setTimeout

I am facing one problem in JS, please help me to resolve this.. I have two methods they are simultaneously executing one after other.. I have to stop them after second click on image.
Below is my JS code
I am calling fadeIn(img1, img2, img3) after first click on image then some animation will be goes on, once I click second time the whole animation(method execution) should be stopped.
var t1 = 0;
var t2 = 0;
function fadeIn(img1, img2, img3) {
$(imgIdForClick_1).addClass('animated pulse');
$('#cashBar1').addClass('animated pulse');
$('#cashBarDec1').addClass('animated pulse');
var t1 = setTimeout(function() {
fadeOut(img1, img2, img3);
if (clickCount_1 >= 2) {
alert("clicked 1");
return false;
}
}, 500);
//t1 = setTimeout(fadeOut, 500);
};
function fadeOut(img11, img22, img33) {
$(imgIdForClick_1).removeClass('animated pulse');
$('#cashBar1').removeClass('animated pulse');
$('#cashBarDec1').removeClass('animated pulse');
var t2 = setTimeout(function() {
fadeIn(img11, img22, img33);
if (clickCount_1 >= 2) {
alert("clicked 2");
return false;
}
}, 500);
//t2 = setTimeout(fadeIn, 500);
};
By below code I am calling this snippet in first click
imgId1 = $('#img1');
imgId2 = $('#cashBar1');
imgId3 = $('#cashBarDec1');
fadeIn(imgId1, imgId2, imgId3);
After second click
clickCount_1 varibale will be 2
clearTimeout(t1);
clearTimeout(t2);
See the DEMO. Click on the image to stop animation.
I have added a flag that would be checked upon the call of the function, and if the value is true then and then only the functions would be called. The flag would be set to false when clicked on image, so the function would be called no longer.
code:
var animate = true;
function fadeIn() {
$('#pulseDiv').find('div#advisersDiv').delay(400).addClass("pulse");
if(animate == true)
{
setTimeout(fadeOut,1000);
}
};
function fadeOut() {
$('#pulseDiv').find('div#advisersDiv').delay(400).removeClass("pulse");
if(animate == true)
{
setTimeout(fadeIn,1000);
}
};
fadeIn();
$('#image').click(function(){
animate = false;
alert('now the animation will stop');
});

After setTimeout() check if still mouse out

I have a piece of code that hides an element on mouseout.
The code looks like this:
var myMouseOutFunction = function (event) {
setTimeout(function () {
$(".classToHide").hide();
$(".classToShow").show();
}, 200);
};
This produces a result very close to what I want to do. However, I want to wait the time on the timeout (in this case 200 ms) then check to see if my mouse is still "out" of the element. If it is, I want to do .hide() and .show() on the desired elements.
I want to do this because if a user slightly mouses out then quickly mouses back in, I don't want the elements to flicker (meaning: hide then show real quick) when the user just wants to see the element.
Assign the timeout's return value to a variable, then use clearTimeout in the onmouseover event.
Detailing Kolink answer
DEMO: http://jsfiddle.net/EpMQ2/1/
var timer = null;
element.onmouseout = function () {
timer = setTimeout(function () {
$(".classToHide").hide();
$(".classToShow").show();
}, 200);
}
element.onmouseover = function () {
clearTimeout(timer);
}
You should use mouseenter and mouseleave of jquery. mouseenter and mouseleave will get called only once.and use a flag if to check if mouseenter again called.
var isMouseEnter ;
var mouseLeaveFunction = function (event) {
isMouseEnter = false;
setTimeout(function () {
if(isMouseEnter ){ return;}
$(".classToHide").hide();
$(".classToShow").show();
}, 200);
};
var mouseEnterFunction = function(){
isMouseEnter = true;
}
Use a boolean flag:
var mustWait = true;
var myMouseOutFunction = function (event) {
setTimeout(function () {
if(mustWait){
mustWait = false;
}
else{
$(".classToHide").hide();
$(".classToShow").show();
mustWait = true;
}
}, 200);
};

restart loop after it's paused by flag

I'm putting together some code to essentially replace the contents of a div when I mouseover a specific link. I then added the changer function to cycle through the content replacement automatically. I set flags for mouseover and mouseout and I can actually get the changer function to stop on mouseover but I can't quite figure out how to make it start up again on mouseout. Any advice is appreciated.
var pause=false;
$('.banner-nav a').mouseover(function () {
pause=true;
setFeature(this);
return false;
});
$('.banner-nav a').mouseout(function () {
pause=false;
});
changer(0, 5000);
function setFeature(f) {
var m = $(f).attr('rel');
$('.banner-nav a').not(f).removeClass('active');
$(f).addClass('active');
$('#featureContainer').html($(m).html());
}
function changer(index, interval) {
var buttons = $('.trigger'),
buttons_length = buttons.length;
var button = buttons.eq(index % buttons_length);
setFeature($(button));
setTimeout(function() {
if (!pause) {
changer(++index, interval);
}
}, interval)
}
The issue is that changer is responsible for its own delayed execution, but pausing it stops the scheduled execution. Another problem is that the next scheduled execution (if any) still happens after pausing.
Use setInterval instead of setTimeout. Instead of using a flag, clear the interval to pause and start it again to unpause.
(function() {
var index=0;
function changer() {
var buttons = $('.trigger'),
buttons_length = buttons.length;
var button = buttons.eq(index % buttons_length);
setFeature($(button));
++index;
}
var changerInterval,
period = 5000;
function startChanger() {
if (! changerInterval) {
changerInterval = setInterval(changer, interval);
}
}
function stopChanger() {
clearInterval(changerInterval);
changerInterval = 0;
}
$('.banner-nav a').mouseover(function () {
stopChanger();
setFeature(this);
return false;
});
$('.banner-nav a').mouseout(function () {
startChanger();
});
/* could implement other functions to e.g. change the period */
function setChangerPeriod() {
...
}
window.setChangerPeriod = setChangerPeriod;
...
})

Categories

Resources