capture setTimeout timerID when setTimeout is used in an if loop - javascript

I'm using setTimeout to create a pause in a loop (on mouseenter) and need to capture the timer ID so it can be stopped on mouseleave.
I thought that would be a simple task… before I tried to code it.
I have a gallery of thumbnails and on hovering over a thumbnail I want to cycle though a series of images (in an endless loop); kind of like a mini-carousel. I achieve this with the common method of swapping out the thumbnail’s source file path; I capture and store the thumbnail’s file path, loop through an array of images then replace the original thumbnail on mouseleave. Nothing complicated.
I coded this up and everything worked fine except on mouseenter the function looped through the entire array of images no matter how briefly the mouse hovered. I eventually discovered that you can’t actually pause a function or stop it but by wrapping it in a setTimeout and using a boolean flag you can create that effect. This is the answer I found https://stackoverflow.com/a/19192399 by user1693593, it’s really simple and works really well.
var doLoop = false;
function loopy() {
if (doLoop === true) {
setTimeout(function () {
// code to loop through array of images
loopy();
}, 11);
}
};
I'm using jQuery's hover method to set the flag.
$(".thumbnails").hover(function() {
doLoop = true;
}, function() {
doLoop = false;
});
So I coded that up and it worked perfectly when you hover over the first thumbnail but when you move to a new thumbnail it all goes haywire. I figured out this was because the first timeout wasn’t being cancelled and subsequent timeouts were interfering with it (and each other). I can see from various Stack Overflow questions that this is a common problem. I discovered that setTimeout returns an ID that you need to capture if you want to cancel it with clearTimeout.
So I found an example on MDN setInterval whereby you store the timeout ID in a variable. It looked simple enough so I coded that up and it worked perfectly. I can see in the console all the timeouts being set (with unique IDs) and cancelled as the mouse moves over the thumbnails… but now I can’t get the loop to work.
I can’t understand why this is so complicated in JavaScript, surely something like this is a common requirement.
Can someone please take pity on a relative newbie and explain how I can code user1693593's example so that it works when mousing over multiple elements. I don’t mind if it uses setTimeout or setInterval.
Please don’t mark this as a duplicate – I’ll understand if someone does – but I’ve been all over Stack Overflow (plus MDN and W3Schools) and I can’t find an example that specifically answers this question, certainly not one that I can understand anyway.

Store the reference somewhere and cancel it.
var timer = null
function addOne (elem) {
elem.textContent = (+elem.textContent + .1).toFixed(1)
}
$(".foo")
.on("mouseenter", function () {
var elem = this
timer = window.setInterval(function () { addOne(elem) }, 100)
}).on("mouseleave", function () {
if (timer) window.clearInterval(timer)
})
div.foo {
width:100px;
line-height: 100px;
border: 1px solid black;
text-align: center;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div class="foo">1</div>
<div class="foo">1</div>
<div class="foo">1</div>
<div class="foo">1</div>
<div class="foo">1</div>

Just keep track of the element that’s currently doing the loop instead of a boolean true/false.
var loopEl;
function loopy() {
if (loopEl) {
setTimeout(function () {
// code to loop through array of images
// do something with loopEl
loopy();
}, 11);
}
};
$(".thumbnails").hover(function() {
loopEl = this;
}, function() {
loopEl = false;
});

Related

How can I trigger jquery event at random time intervals?

I created a header using jquery.flip.js, found at https://github.com/nnattawat/flip. The plugin allows several ways to trigger the flip, the two relevant ones are 'click' and 'hover'. I was hoping to have the div's flip at random intervals automatically. I did find a similar question on stackoverflow that Heretic Monkey suggested using a recursive approach (Trigger mouse click and random intervals)...
var clickHand = function() {
$("[id^='hand_'].handIcon").trigger('click');
setTimeout(clickHand, (Math.random() * 3000) + 32000);
}
clickHand();
EDIT: sorry for not being clear. In the jquery code, the following method (?) handles the flip on click, however what I would like to do is have the divs flip automatically (if possible) without a hover or click to trigger. I tried using a setTimeout inside attachEvents, but it seemed to cause an issue with the styling.
attachEvents: function() {
var self = this;
if (self.setting.trigger === "click") {
self.element.on($.fn.tap ? "tap.flip" : "click.flip", $.proxy(self.clickHandler, self));
} else if (self.setting.trigger === "hover") {
self.element.on('mouseenter.flip', $.proxy(self.hoverHandler, self));
self.element.on('mouseleave.flip', $.proxy(self.unflip, self));
}
},
I am pretty new to javascript and am having a difficult time with this and any help would be appreciated.
I created a simple codepen, demonstrating the 3D flip, https://codepen.io/coeyflyer/pen/eYZGymG.
Thank you for any suggestions,
C

force DOM redraw with javascript on demand

The title of the question expresses what I think is the ultimate question behind my particular case.
My case:
Inside a click handler, I want to make an image visible (a 'loading' animation) right before a busy function starts. Then I want to make it invisible again after the function has completed.
Instead of what I expected I realize that the image never becomes visible. I guess that this is due to the browser waiting for the handler to end, before it can do any redrawing (I am sure there are good performance reasons for that).
The code (also in this fiddle: http://jsfiddle.net/JLmh4/2/)
html:
<img id="kitty" src="http://placekitten.com/50/50" style="display:none">
<div>click to see the cat </div>
js:
$(document).ready(function(){
$('#enlace').click(function(){
var kitty = $('#kitty');
kitty.css('display','block');
// see: http://unixpapa.com/js/sleep.html
function sleepStupidly(usec)
{
var endtime= new Date().getTime() + usec;
while (new Date().getTime() < endtime)
;
}
// simulates bussy proccess, calling some function...
sleepStupidly(4000);
// when this triggers the img style do refresh!
// but not before
alert('now you do see it');
kitty.css('display','none');
});
});
I have added the alert call right after the sleepStupidly function to show that in that moment of rest, the browser does redraw, but not before. I innocently expected it to redraw right after setting the 'display' to 'block';
For the record, I have also tried appending html tags, or swapping css classes, instead of the image showing and hiding in this code. Same result.
After all my research I think that what I would need is the ability to force the browser to redraw and stop every other thing until then.
Is it possible? Is it possible in a crossbrowser way? Some plugin I wasn't able to find maybe...?
I thought that maybe something like 'jquery css callback' (as in this question: In JQuery, Is it possible to get callback function after setting new css rule?) would do the trick ... but that doesn't exist.
I have also tried to separte the showing, function call and hiding in different handlers for the same event ... but nothing. Also adding a setTimeout to delay the execution of the function (as recommended here: Force DOM refresh in JavaScript).
Thanks and I hope it also helps others.
javier
EDIT (after setting my preferred answer):
Just to further explain why I selected the window.setTimeout strategy.
In my real use case I have realized that in order to give the browser time enough to redraw the page, I had to give it about 1000 milliseconds (much more than the 50 for the fiddle example). This I believe is due to a deeper DOM tree (in fact, unnecessarily deep).
The setTimeout let approach lets you do that.
Use JQuery show and hide callbacks (or other way to display something like fadeIn/fadeOut).
http://jsfiddle.net/JLmh4/3/
$(document).ready(function () {
$('#enlace').click(function () {
var kitty = $('#kitty');
// see: http://unixpapa.com/js/sleep.html
function sleepStupidly(usec) {
var endtime = new Date().getTime() + usec;
while (new Date().getTime() < endtime);
}
kitty.show(function () {
// simulates bussy proccess, calling some function...
sleepStupidly(4000);
// when this triggers the img style do refresh!
// but not before
alert('now you do see it');
kitty.hide();
});
});
});
Use window.setTimeout() with some short unnoticeable delay to run slow function:
$(document).ready(function() {
$('#enlace').click(function() {
showImage();
window.setTimeout(function() {
sleepStupidly(4000);
alert('now you do see it');
hideImage();
}, 50);
});
});
Live demo
To force redraw, you can use offsetHeight or getComputedStyle().
var foo = window.getComputedStyle(el, null);
or
var bar = el.offsetHeight;
"el" being a DOM element
I do not know if this works in your case (as I have not tested it), but when manipulating CSS with JavaScript/jQuery it is sometimes necessary to force redrawing of a specific element to make changes take effect.
This is done by simply requesting a CSS property.
In your case, I would try putting a kitty.position().left; before the function call prior to messing with setTimeout.
What worked for me is setting the following:
$(element).css('display','none');
After that you can do whatever you want, and eventually you want to do:
$(element).css('display','block');

Flickering mousenter mouseleave with delay, button list

I have made a list(<p>) with buttons. When I move my mouse over them it's a 1,2 sec delay before my textbox are marked with yellow to show where I can write. When I move my mouse away they turn normal(white).
My problem is when I quickly hover my mouse over the buttons back and forth a lot of the textboxes gets marked.
I had hoped the 1,2 sec delay would have worked then but it doesn't. But it works if I move my mouse slowly in and out of the button.
Here is a fiddle to it: http://jsfiddle.net/Pota/Fj6E6/
Here is my JavaScript code
$(function () {
$("p.pRespRoleId").mouseenter(function () {
var timeOut = 1200;
$this = $(this);
$this.data("delay", setTimeout(function () {
mouseInRespRoleId();
}, timeOut)
);
})
.mouseleave(function () {
$this = $(this);
if ($this.next(mouseOutRespRoleId()).is(":visible")) {
clearTimeout($this.data("delay"));
mouseOutRespRoleId();
}
else {
$this.next("p.pRespRoleId").show();
}
});
});
and
function mouseInRespRole()
{
var txtInRespRole = document.getElementById("<%=txtRespRoleName.ClientID %>");
txtInRespRole.style.background = "#FFFF00";
if (document.getElementById('txtRespRoleName').value == '')
{
document.getElementById('txtRespRoleName').innerHTML = txtInRespRole;
return false;
}
}
function mouseOutRespRole()
{
var txtOutRespRole = document.getElementById("<%=txtRespRoleName.ClientID %>");
txtOutRespRole.style.background = "white";
if (document.getElementById('txtRespRoleName').value == '')
{
document.getElementById('txtRespRoleName').innerHTML = txtOutRespRole;
return true;
}
}
Your jsFiddle is surely confusing to me (I am not sure what you are trying to achieve - there is a tangible possibility that you are overcomplicating things). I hope I got your requirement right...
Anyway, I believe your logic was right, but there were some flaws in the implementation. So, here is a modified (and partially corrected) version of your jsFiddle, which does what (I believe) you were trying to achieve.
Your use of '$this.next(mouseOutRespRoleId()).is(":visible")' was sure the most confusing, so I removed it completely. (In case it was fulfilling some other, not obvious purpose, you'll have to provide a more detailed description.)
The main problem was that $this.next(mouseOutRespRoleId()).is(":visible") was never evaluating to true, thus never clearing the timer that called mouseInRespRoleId().
EDIT:
I updated my jsFiddle illustration so that it takes care of IE9's strange behaviour (a.k.a. bug (?)). It should work without flickering now.
Short explanation of the problem:
Aparantly, in IE9 the mouse-events generation has some "timing issues", so that when entering (mouseOver) and leaving (mouseOut) a component multiple times rapidly, sometimes the mouse-events order gets messed up. E.g.:
The following event sequence (i.e. actual events):
mouseOver -> mouseOut -> mouseOver
May produce the following (obviously wrong) javascript-event sequence (i.e. events triggered by JS-engine in IE9):
mouseOver -> mouseOver(!) -> mouseOut(!)
So, I added an extra clearTimeout($this.data("delay")) in the "mouseentered" handler-function, in order to clear any pending scheduled executions of "mouseInRespRoleId".
It does not work perfectly on IE9 (and probably previous versions of IE - not tested), but it is as good as it can get (afaik).
(NOTE: It still works as intended on other (non-buggy) browsers.)

setTimeout speeds up with multiple tabs

I’m having a setTimeout problem similar to this one. But that solution doesn't help me since I can’t use php in my file.
My site has a slider with a list of images that move every 8 seconds.However, when I have opened a few tabs in the browser and then switch back again, it goes nuts.
The slider proceeds to move the images one after the other immediately without the 8 second timedelay.
I'm only seeing it in Chrome and the latest Firefox.
**EDIT: I checked with console.log() and the setTimeout returns the same number before and after the clearTimeout. Not sure why. Maybe that also has something to do with it? **
EDIT 2: I added a fiddle: http://jsfiddle.net/Rembrand/qHGAq/8/
The code looks something like:
spotlight: {
i: 0,
timeOutSpotlight: null,
init: function()
{
$('#spotlight .controls a').click(function(e) {
// do stuff here to count and move images
// Don't follow the link
e.preventDefault();
// Clear timeout
clearTimeout(spotlight.timeOutSpotlight);
// Some stuff here to calculate next item
// Call next spotlight in 8 seconds
spotlight.timeOutSpotlight = setTimeout(function () {
spotlight.animate(spotlight.i);
}, 8000);
});
// Select first item
$('#spotlight .controls a.next:first').trigger('click');
},
animate: function(i)
{
$('#spotlight .controls li:eq(' + (spotlight.i) + ') a.next').trigger('click');
}
}
From the jQuery documentation:
Because of the nature of requestAnimationFrame(), you should never
queue animations using a setInterval or setTimeout loop. In order to
preserve CPU resources, browsers that support requestAnimationFrame
will not update animations when the window/tab is not displayed. If
you continue to queue animations via setInterval or setTimeout while
animation is paused, all of the queued animations will begin playing
when the window/tab regains focus. To avoid this potential problem,
use the callback of your last animation in the loop, or append a
function to the elements .queue() to set the timeout to start the next
animation.
I finally found my answer and it’s not at all what I was expecting.
It seems the culprit is jQuery’s .animate(), which I use to move the images in the slider.
I calculate and move my images positions with this:
$('.spotlight-inner')
.animate(
{ left: scrollToVal },
{duration: 'slow'}
)
;
Now the problem seems to be that in some browsers, after you switch to a new tab and back, jQuery’s .animate() saves up the animations and fires them all at once. So I added a filter to prevent queueing. That solutions comes from CSS-Tricks.com :
$('.spotlight-inner')
.filter(':not(:animated)')
.animate(
{ left: scrollToVal },
{duration: 'slow'}
)
;
The first slide you see when you go back can act a little jumpy but it’s better than the superspeed carousel from before.
Fiddle with the full code here
There is an easier way using the jquery animate queue property:
$(this).animate({
left: '+=100'
}, {duration:500, queue:false});
I don't know if this will help you, but it helped me with my slideshow. What I did was everytime I called an animation that was supposed to happen at a set interval because of the setTimeout, I called clearQueue() which would get rid of any other animations that had been set to happen. then i'd call the animation. That way when you come back to that tab, you don't have all these animations queued up and it goes crazy. at max you'll only have one set up.
So something like this:
spotlight.timeOutSpotlight = setTimeout(function () {
spotlight.clearQueue(); // get rid of other instances of the animation
spotlight.animate(spotlight.i);
}, 8000);
It may not work in all cases (depending on timing), but I hope that helps somebody!
You must also think you use clearTimeout.
As you call setTimeout function it returns an ID you can save this ID in a variable like
timeoutID = setTimeout(function () {
spotlight.animate(spotlight.i);
}, 8000);
and before setting a new timeout you can call the function like
clearTimeout(timeoutID)
My suspicion is that the browser queues input events like 'click' but only fires them when the tab where the event occurs actually has focus.
Perhaps you should try calling your click callbacks directly instead of using trigger('click').
Something like this:
spotlight: {
i: 0,
timeOutSpotlight: null,
clickFunc: function(element) {
// do stuff here to count and move images
// Clear timeout
clearTimeout(spotlight.timeOutSpotlight);
// Some stuff here to calculate next item
// Call next spotlight in 8 seconds
spotlight.timeOutSpotlight = setTimeout(function () {
spotlight.animate(spotlight.i);
}, 8000);
},
init: function()
{
$('#spotlight .controls a').click(function (e) {
// Don't follow the link
e.preventDefault();
spotlight.clickFunc(this);
});
// Select first item
spotlight.clickFunc($('#spotlight .controls a.next:first'));
},
animate: function(i)
{
var element = $('#spotlight .controls li:eq('+spotlight.i+') a.next');
spotlight.clickFunc(element);
}
}
What version of jQuery are you running? Apparently this problem was 'fixed' for version 1.6.3 - they reverted the change that caused this to happen. Discussions here and here.
Though this issue will likely have to be addressed in the future, it seems as though we're off the hook for now.

Can't see problem in JS

I want that when mouse is over an image, an event should be triggered ONCE, and it should be triggered again only after mouse is out of that image and back again, and also at least 2 seconds passed.
If I leave my mouse over the image,it gets called like every milisecond,and by the logic of my function once you hover on the variable 'canhover' becomes 0 until you move mouse out
This code seems to have a bug and I cant see it. I need a new pair of eyes, but the algorithm is kinda logical
Working code :
<script type="text/javascript">
var timeok = 1;
function redotimeok() {
timeok = 1;
}
//
function onmenter()
{
if (timeok == 1)
{
enter();
timeok = 0;
}
}
//
function onmleave()
{
setTimeout(redotimeok, 2000);
leave();
}
//
$('#cashrefresh').hover(onmenter,onmleave);
function enter(){
$("#showname").load('./includes/do_name.inc.php');
$("#cashrefresh").attr("src","images/reficonani.gif");
}
function leave(){
$("#cashrefresh").attr("src","images/reficon.png");
}
</script>
I don't know if this will solve your entire problem (since we don't have a detailed description of what it is), but instead of:
$('#cashrefresh').hover(onmenter(),onmleave());
try:
$('#cashrefresh').hover(onmenter,onmleave);
And the same thing here:
setTimeout(redotimeok, 2000); // just the function name
Also, I don't see where you ever set timeok to zero. Do you mean to set timeok = 0 in onmenter()?
There are two methods in jquery for your problem:
.mouseenter() and .mouseleave()
Check out the demos there.
EDIT:
I thought hover was for mouseover and mouseout, sorry for confusion.
I checked your code again. And it seems that you're changing the image when mouse gets over the image, which forces browser to load the new image and the old image disappears for a very little while till the new one appears and i think this must be triggering both handlers continuosly and you're getting this behaviour.
Try not to change the source of the image, comment out that line and instead console.log("some message") there and see if the message is repeated as much as .load() was fired before.
Hope this helps.
Try changing onmleave function as follows:
function onmleave()
{
setTimeout(redotimeok, 2000);
leave();
}

Categories