js/jQuery - exit function by mouse position - javascript

I have a recursive function for a kind of image slider.
function nextCol(col) {
$('.menubox_col').fadeOut();
$('.menubox_col').eq(col).fadeIn(function(){
col++;
if (col > 3) col = 0;
setTimeout(function(){ nextCol(col) }, 1000);
});
}
<div id="menubox">
<div class="menubox_col">content</div>
<div class="menubox_col">content</div>
<div class="menubox_col">content</div>
<div class="menubox_col">content</div>
</div>
This works fine, but I found no way to stop the recursive function when the mouse cursor enters the #menubox div.

While you could use clearTimeout and then restart the animation again, you could simply set a flag, which means you don't need to stop and start timers... This will stop the animation when the mouse is over the menubox, and continue it when it leaves. I also took the liberty of making some small code changes - I find the result much simpler:
$(function(){
var col = 0, hover = false;
function nextCol() {
if(hover){return;} // if their mouse is over, do nothing
col = (col+1) % 4; // make this a one-liner. the 4 probably shouldn't be hard-coded though, it could be $('.menubox_col').length
$('.menubox_col').fadeOut().eq(col).fadeIn();
}
setInterval(nextCol, 1000);
$('#menubox').hover(function(){ hover=true; }, function(){ hover=false; });
});

You could clear the timeout using clearTimeout:
var timeoutHandle = null;
function nextCol(col) {
$('.menubox_col').fadeOut();
$('.menubox_col').eq(col).fadeIn(function() {
col++;
if (col > 3) { col = 0; }
timeoutHandle = setTimeout(function() {
nextCol(col);
}, 1000);
});
}
$('#menubox div').mouseenter(function() {
window.clearTimeout(timeoutHandle);
});

Related

JQuery transition animation

This program randomly selects two employees from a json-object Employees array, winnerPos is already defined.
For better user experience I programmed these functions to change pictures one by one. The animation stops when the randomly selected person is shown on the screen.
The slideThrough function will be triggered when the start button is pressed.
function slideThrough() {
counter = 0;
start = true;
clearInterval(picInterval);
picInterval = setInterval(function () {
changePicture();
}, 500);
}
function changePicture() {
if (start) {
if (counter > winnerPos) {
setWinner();
start = false;
killInterval();
} else {
var employee = Employees[counter];
winnerPic.fadeOut(200, function () {
this.src = 'img/' + employee.image;
winnerName.html(employee.name);
$(this).fadeIn(300);
});
counter++;
}
}
}
The problem is the animation doesn't work smoothly. At first it works, but not perfect. The second time the transition happens in an irregular way, i.e. different speed and fadeIn/fadeOut differs from picture to picture.
Could anyone help me to fine-tune the transition?
I would avoid using setInterval() and add a function to the call to .fadeIn() that starts the animation of the next picture.
It would look like this:
function changePicture(pos) {
pos = pos || 0;
if (pos <= winnerPos) {
var employee = Employees[pos];
winnerPic.fadeOut(200, function() {
this.src = 'img/' + employee.image;
winnerName.html(employee.name);
$(this).fadeIn(300, function() {
changePicture(pos + 1);
});
});
} else {
setWinner();
}
}
To start the animation, you call changePicture() without any arguments, like this.
changePicture();
jsfiddle

Pausing Recursive Function Call

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);

OnMouseOver start loop, OnMouseOut kill loop

I created this little function that loops through images when you go mouse over:
function THUMB_ROLL(NEXT)
{
LENGTH = ALL_IMAGES.length;
if(!LENGTH) { return false; }
if(!NEXT || NEXT === LENGTH) { NEXT = 0 }
$('#IMAGE_THUMB').css('background-image','url(/<?php echo $ITEM_CODE;?>/'+ALL_IMAGES[NEXT]+')');
setTimeout(function()
{
THUMB_ROLL(NEXT+2)
},800);
}
</script>
I have, onmouseover="THUMB_ROLL();"
But, I cannot seem to find a solution to stop the loop once onmouseout. Any help appreciated!
EDIT
<script>
var timer;
function THUMB_ROLL(NEXT)
{
LENGTH = ALL_IMAGES.length;
if(!LENGTH) { return false; }
if(!NEXT || NEXT === LENGTH) { NEXT = 0 }
$('#IMAGE_THUMB').css('background-image','url(/<?php echo $ITEM_CODE;?>/'+ALL_IMAGES[NEXT]+')');
timer = setTimeout(function()
{
THUMB_ROLL(NEXT+1)
},800);
}
$(document).on('mouseover','#IMAGE_THUMB',function()
{
THUMB_ROLL();
});
$(document).on('mouseout','#IMAGE_THUMB',function()
{
clearTimeout(timer);
});
</script>
Assign your timeout to a variable which store the timerID.
Then have the moueout clear the timeout, which should stop the flow.
var timer;
// mouseover event..
timer = setTimeout(function() {
THUMB_ROLL(NEXT+2)
},800);
// mouseout event
clearTimeout(timer);
Also it is a better idea to separate your concerns and attach the events inside the script tags instead of attaching them inline.

Pause Javascript for a few seconds on click

Ok i know this has to be a topic discussed earlier, but all the answers I found were a little complicated considering I'm new and still in the process of learning javascript.
I have the following code in the head section of my html
<script>
function timedText() {
setTimeout(function(){displayResult()},3000);
setTimeout(function(){displayResult1()},7000);
setTimeout(function(){displayResult2()},15000);
setTimeout(function(){timedText()},18000);
}
</script>
<script>
function change() {
setTimeout(function(){timedText()},1000);
}
</script>
<script>
function displayResult() {
document.getElementById("adimg_holder").style.bottom="0px";
document.getElementById("button1").style.backgroundPosition="bottom";
document.getElementById("button2").style.backgroundPosition="top";
document.getElementById("button3").style.backgroundPosition="top";
}
function displayResult1() {
document.getElementById("adimg_holder").style.bottom="370px";
document.getElementById("button1").style.backgroundPosition="top";
document.getElementById("button2").style.backgroundPosition="bottom";
document.getElementById("button3").style.backgroundPosition="top";
}
function displayResult2() {
document.getElementById("adimg_holder").style.bottom="739px";
document.getElementById("button1").style.backgroundPosition="top";
document.getElementById("button2").style.backgroundPosition="top";
document.getElementById("button3").style.backgroundPosition="bottom";
}
</script>
and the following html
<body onload="change()">
<div class="banner_area">
<div class="banner_wrapper">
<img src="images/image_holder.png" />
<div id="ad_holder">
<div id="adimg_holder">
<img class="ad_images" src="images/recruitment_banners.png" />
<img class="ad_images" src="images/training_banners.png" />
<img class="ad_images" src="images/staffing_banner.png" />
</div>
</div>
<div id="ad_buttons">
<div id="button1" style="background-image:url(images/buttonfirst.png);background-position:bottom;width:259px;height:41px" onclick="displayResult()"></div>
<div id="button2" style="background-image:url(images/buttonsecond.png);width:259px;height:41px" onclick="displayResult1()"></div>
<div id="button3" style="background-image:url(images/buttonthird.png);width:259px;height:41px" onclick="displayResult2()"></div>
</div>
</div>
</div>
So the buttons toggle different positions, and the script cycles through the positions at time intervals
Now what I'm trying to achieve is that when the positions are being cycled through, if I click on one of the button, it jumps to the related position and stays that way for a while (say a few seconds) and then continue with the loop.
Hope I've made my motive easy to understand.
Here is how you can achieve this behavior: http://jsfiddle.net/PcZsT/12/
Here is the JavaScript:
function timedText() {
setTimeout(function() {
displayResult();
},2000);
setTimeout(function() {
displayResult1();
},6000);
setTimeout(function() {
displayResult2();
},14000);
setTimeout(function() {
timedText();
},15000);
}
(function change() {
setTimeout(function() {
timedText();
},1000);
}());
var locked = false;
function button1Handler() {
moveTop();
lockFor(3);
}
function button2Handler() {
moveBottom();
lockFor(3);
}
function lockFor(seconds) {
locked = true;
setTimeout(function () {
locked = false;
}, seconds * 1000);
}
function displayResult() {
if (locked) return;
moveTop();
}
function moveTop() {
document.getElementById("adimg_holder").style.bottom="0px";
document.getElementById("button1").style.backgroundPosition="bottom";
document.getElementById("button2").style.backgroundPosition="top";
document.getElementById("button3").style.backgroundPosition="top";
}
function displayResult1() {
if (locked) return;
moveMiddle();
}
function moveMiddle() {
document.getElementById("adimg_holder").style.bottom="370px";
document.getElementById("button1").style.backgroundPosition="top";
document.getElementById("button2").style.backgroundPosition="bottom";
document.getElementById("button3").style.backgroundPosition="top";
}
function displayResult2() {
if (locked) return;
moveBottom();
}
function moveBottom() {
document.getElementById("adimg_holder").style.bottom="739px";
document.getElementById("button1").style.backgroundPosition="top";
document.getElementById("button2").style.backgroundPosition="top";
document.getElementById("button3").style.backgroundPosition="bottom";
}
The functions above are the click handlers of respectively button1 and button2.
I've also changed the change function to бe IIFE (Immediately Invoked Function Expression) but it's not necessary, you don't have to do it because you want to be invoked onload.
you don't need so much code : just make one displayResult function that will call itself over and over :
var scrollParam = { scrollPos : 0, // current pos
scrollIncr : 370, // incr in px each call
loopScrollAfter : 700 // set scrollPos to >0 if above that number
delay : 3000, // std scroll delay
whichIsOnTop : 0, // index of the topmost item (button)
pauseDelay : 1000, // added delay if someone clicks
pauseRequired : 0 }; // current required click delay
var mainItem = document.getElementById("adimg_holder");
function displayResult()
{
mainItem.style.bottom=scrollParam.scrollPos +"px";
scrollParam.scrollPos += scrollParam.scrollIncr;
if (scrollParam.scrollPos>scrollParam.loopScrollAfter) scrollParam.scrollPos=0;
setBackgroundPosition("button1",0);
setBackgroundPosition("button2",1);
setBackgroundPosition("button3",2);
scrollParam.whichIsOnTop = (scrollParam.whichIsOnTop + 1) % 3;
// now setup the next call, taking into account if a pause is required
var requiredDelay = scrollParam.delay;
if (scrollParam.pauseRequired >0) {
requiredDelay += scrollParam.pauseRequired;
scrollParam.pauseRequired=0;
}
setTimeout(displayResult, requiredDelay);
}
var setBackgroundPosition(itemName, index ) {
document.getElementById(itemName).style.backgroundPosition=topOrBottom(index );
};
// returns "top" for the right index (==scrollParam.whichIsOnTop) and "bottom" for the others
var topOrBottom(thisIndex) { return (thisIndex == scrollParam.whichIsOnTop) ? "top" : "bottom"; };
// in the button click handler you should use :
scrollParam.pauseRequired += scrollParam.pauseDelay;
// to launch the scroll, just call :
displayResult(); // in the loaded() event handler for example.
Rq : you can change easily the parameters to have a smoother scrolling if you like.
you can make use of the setTimeout timing function itself.
write a function as:
function calldesiredFunctionafterPause (functionTobeCalled){
setTimeout(function(){functionTobeCalled,1000}) //increase the millisec as needed
}
and on the onClick call this function and pass the function name as parameter.
This will make the onclick function to be delayed for the required time

Track the scroll position beyond elements

I'm putting together a jQuery plugin. The plugin takes panels, and auto-sizes their height. So I start off with something like this:
<div class="panel">Test 1</div>
<div class="panel">Test 2</div>
<div class="panel">Test 3</div>
The code for that looks something like:
sizePanels: function(){
panels.each(function(){
$(this).height(docHeight);
});
},
There is a down button, that when clicked, will take the user to the next $(".panel):
nextPanel: function(){
$.scrollTo($(".panel:eq(" + panelIndex + ")"), 250, { easing: "swing" });
}
With that, I'm keeping track of the panel index that their on:
if (panelIndex < (panelCount - 1) ) {
panelIndex += 1;
}
I'm trying to figure out a way to track if they happen to scroll manually, and pass one of the elements, to then increase the "panelIndex", so that the button doesn't move them up instead of down because it was never incremented properly due to the user using the scroll bar instead of the button. This is what I have so far:
$(window).scroll(function(){
panels.each(function(index){
if ($(window).scrollTop() > $(this).scrollTop()) {
panelIndex = index;
// console.log(index);
}
});
if (panelIndex < panelCount - 1){
s.showDownButton();
}
});
The code excessively checks and feels somewhat overboard. is there a better way to do it?
An easy optimization is to only calculate the scrollTop once and to exit the each loop when a match is found. You can exit a $.each loop by returning false.
$(window).scroll(function(){
var scrollTop = $(window).scrollTop();
panels.each(function(index){
if (scrollTop > $(this).scrollTop()) {
panelIndex = index;
} else {
return false;
}
});
if (panelIndex < panelCount - 1){
s.showDownButton();
}
});
The next way that I would suggest optimizing this is to pre-calculate the scrollTop of each panel on page load (and when the viewport is resized). If you store these values in an array, then you can loop through them very quickly.
Here is some rough code to illustrate the idea:
var panelTops = [];
findPanelTops(); // calculate on load
$(window).on("resize", findPanelTops); // calculate on resize
function findPanelTops() {
panelTops = [];
panels.each(function(index) {
panelTops.push($(this).scrollTop());
});
}
$(window).scroll(function(){
var scrollTop = $(window).scrollTop();
for (var i = 0; i < panelTops.length; i++) {
if (scrollTop > panelTops[i]) {
panelIndex = i;
} else {
break;
}
};
if (panelIndex < panelCount - 1){
s.showDownButton();
}
});
The scroll event can fire a lot and very quickly, so you want to keep the amount of computation as minimal as possible. One way to get around all of this is to implement a scrollend handler. This will only fire when the scroll event has appeared to have stopped.
Here is some basic code for doing that. It will fire when the scroll event has stopped for more than 500ms:
var scrollTimeout = null;
function onScroll() {
if (scrollTimeout) {
clearTimeout(scrollTimeout);
}
scrollTimeout = setTimeout(onScrollEnd, 500);
}
function onScrollEnd() {
// Scrolling has stopped
// Do stuff ...
scrollTimeout = null;
}
$(window).on("scroll", onScroll);

Categories

Resources