I created a relatively small dynamic banner rotation script with icons at the bottom for bringing a particular banner into focus. Firing a mouseenter over a banner pauses the show, but sometimes when I mouseout from my banner, the delay for certain banners gets shortened. I'd even understand if it just happened once, but the delay is then set for that shorter amount of time every time the banner comes back around in the rotation, and often the shortening happens in one other place in the list of banners, as well. Sometimes this can be corrected by an as yet undetermined set of actions. I'm starting to suspect that my logic is catching the loop in the middle somewhere and so the process branches out, runs two loops, which appear to speed up the calling of the showNextBanner function. Not sure how to solve this. I've put in tests to see if it's currently in play mode, to no avail.
I include what I think are the relevant parts of the code below.
var firstRun = true;
var play = true;
var current = 0;
var banners = $$( '.banner' );
banners.invoke( 'hide' );
var images = $$( '.image' );
var icons = $$( '.icon' );
//dynamically clones an initial icon to match the number of banners
initIcons();
banners.invoke( 'observe', 'mouseenter', function( field ) {
play = false;
});
banners.invoke( 'observe', 'mouseleave', function( field ) {
if( !play ) {
play = true;
showNextBanner().delay(3);
}
});
icons.invoke( 'observe', 'click', function( field ) {
play = false;
hideBanner( current );
showBanner( findObj( icons, field.findElement()));
});
showNextBanner().delay(3);
function hideBanner( which ) {
icons[ which ].src = blankIconSRC;
banners[ which ].hide();
}
function showBanner( which ) {
icons[ which ].src = selectedIconSRC;
banners[ which ].show();
current = which;
}
// loops the hiding and showing of icons
// (mouseenter sets play to false)
function showNextBanner() {
if( play ) {
if( !firstRun ) {
if( ++current == banners.length ) current = 0;
var previous = 0;
( current == 0 )? previous = banners.length - 1: previous = current - 1;
hideBanner( previous );
} else {
icons[0].src = selectedIconSRC;
firstRun = false;
}
showBanner( current );
showNextBanner.delay(3);
}
}
}());
After all that, the client wants a jQuery solution so he can have a slide-in effect not available via scriptaculous. So all that work is down the drain. The good news is that I can just use jCarousel, probably, and tweak the stylesheet. Thanks for the help!
I suspect what is happening is that you've got multiple .delay calls stacking up. So you've got one with less than 3 seconds remaining and showNextBanner is called again, setting another delay timer.
As I read the docs, it appears .delay is intended to put gaps in the jquery event pipeline, rather than actually delay function calls. You may benefit from switching to calling setTimeout instead of delay, so that you get a handle to the timeout, which you can then cancel before setting a new timeout (or cancel if play is set to false, then reset when play is true again) This is mentioned in the JQuery docs for .delay
My guess is that since you don't "cancel" the delay()'ed function, they hang around for too long, but they don't do anything when they fire, because play is false. But once play is true again, the all start having an effect again.
You can save the returned value for delay() and cancel the timer by using clearTimeout() with the value.
However, I'd also suggest that you use a single container for all the banners (and maybe put the the icons in there too), and set the mouseenter/mouseleave events on that, rather than on individual banners. Then there's just a single element that'll start/stop the banner rotation. If you also split everything up in specific functions that play and stop the rotation, and one to show a specific banner, you can possibly get a cleaner code structure.
Here's an example (it's just something I put together for fun rather than an edit of your code, so it's quite different. Sorry. But hopefully you can still use for something)
Related
I'm trying to limit the user's ability to click on an object to a certain time limit. I looked around and found that apparently, setTimeout() is the correct function to use for this type of thing. I've applied the function to my code, but its not working. I'm thinking/know now that the problem is that the setTimeout in my code isn't limiting the actual click event, which I need to do. Here is a snippet of my click code:
function clickRun(event) {
var $objectVersion = correspondingObject(event.target.id);
if (isAnyVisible() == false) { // none open
$objectVersion.makeVisible();
} else if (isAnyVisible() && $objectVersion.isVisible()) { //click already open div
$objectVersion.makeInvisible();
} else if (isAnyVisible() && $objectVersion.isVisible()==false) { //different div open
searchAndDestroy();
$objectVersion.delay(600).makeVisible();
};
};
$('.ChartLink').click(function(event) {
setTimeout(clickRun(event),5000);
});
I've also created a JSFiddle to represent what I'm talking about: http://jsfiddle.net/FHC7s/
Is there a way to achieve limiting the actual click detection on the page?
I think the easiest way to do it is to keep track of the time of the previous click and if the current click is too soon after that, then don't do anything:
onClick = function(){
if(new Date().getTime() - lastCheck < MIN_CLICK_SPACING) return;
}
Have a look at this JSFiddle, I've set it up so you can have the button disable itself for time duration after detecting a click. Just make sure to remember how your closures are operating with your setTimeouts.
Your code contains an error... your line should be
setTimeout(function(){clickRun(event)},5000);
but even then I don't think that's exactly what you're looking for; that code will "delay" the click by 5 seconds, not actually prevent more clicks. If your true intent is to ignore all clicks after a certain amount of time, then I would go with mowwalker's answer; there's no way to stop the clicks, but you can check to see if you should honor them or not.
EDIT: For clarity, the question is why doesn't the code below work as expected (why does it not animate for the duration of the while loop), how can I improve it and how can I red in the unit that it should travel via a user input slider.
My aim is to have shape animates it's way down the screen.
A button will start and stop the animation. There will also be an input for the rate of change or speed at which it travels.
I can make it continuously travel down the screen but the following code doesn't work - I've used ble as a test variable, in the final scenario I'd hope that this would be replaced with something similar to while(stop != true) or something similar.
startDescent.onclick = function(){
startDescent.disabled = true; //prevent it being clicked again
//animation process
var ble = 1;
while(ble < 10){
console.log(ble);
testDrillbit.animate('top', '+=1',{
duration: 1000,
onChange: canvas.renderAll.bind(canvas),
onComplete: function(){
startDescent.disabled = false;
}
});
ble++;
}
};
the +=1 increment should also read in from a user input box, any suggestions on how to achieve this would also be very welcome. Thanks for all and any help.
I believe you are making use of Fabric JS to provide the animation logic. My answer is based on that assumption.
The issue has to do with your intepretation of how the animate function works. It is not a synchronous call. So your loop will essentially initialize the animate action 10 times, not execute 10 animations. Given that the action you defined was to move object "testDrillBit" down 1 pixel over a period of 1 seconds, it would probably appear like nothing happened.
To approximate the operation you are trying to perform, you would need to employ a callback that basically indicates when the animation is complete, do it again, until the user hits their "stop" button. This would probably cause a jerky animation though. Alternatively you can set an arbitrarly large endpoint for the animation and add an abort handler, but you would then need to determine your rate of change (pixels/time) to come up with the right duration.
It's not clear that this library is appropriate for your implementation, but I cannot offer an alternative at this time. The code example below illustrates the second option while illustrating the points you had asked for, a stop mechanism, arbitray rate of change etc. The significant change is rather than specifying +=1 for the rate of change, we alter the duration it takes for the animation to complete and animate over a larger distance (in this case the canvas height).
First, we add a stop button and an input for our speed:
<button id="stop" disabled="true" onclick="stop=true;">Stop</button>
<form>
<input type="text" id="speed" value="10" />
</form>
Then, in our script box we make sure we can use these values and then employ them in the onclick handler.
var stopBtn = document.getElementById('stop');
var speedBox = document.getElementById('speed');
var stop = false;
startDescent.onclick = function() {
// Get our speed, in case the user changes it. Speed here is actually the duration
// of the animation, not a true velocity. But, we can do something like entering 0.5
// and "slow down" the animation
var speed = 10000 / (new Number(speedBox.value));
stop = false; // ensure that we won't abort immediately
stopBtn.disabled = false; // enable the stop button
startDescent.disabled = true;
// I chose canvas.height as an arbitrary fixed distance. Not this won't stop the
// the element from rolling out of the canvas, its just a fixed value.
// The significant change is the addition of the "abort" function which basically
// polls our stop variable to determine whether the animation should be aborted.
testDrillbit.animate('top', "+="+canvas.height, {
duration: speed,
abort: function () {
// If the user has clicked the stop button, flip our buttons
if (stop) {
startDescent.disabled = false;
stopBtn.disabled = true;
}
return stop;
},
onChange: canvas.renderAll.bind(canvas),
onComplete: function() {
startDescent.disabled = false;
stopBtn.disabled = true;
}
});
};
The above code should allow the user to alter the "speed" by stretching or shortening the amount of time to perform the animation. In addition you have your mechanism to stop the animation partway through the execution.
Heyho,
I´am working on a project in my university and I´d like to use "Hammer.js".
I´ve downloaded the Carousel-Example and it works perfectly for me.
But I would like to start a the middle pane of my code and it´s not so simple I think.
It´s something like this:
http://img203.imageshack.us/img203/6326/schemeas.jpg
so Hammer.js starts always with the green screen. But I like to start with the yellow one.
I´ve added one swipe right to the init function but it looks horrible when the page is loading and could not be the goal ^^
I hope anyone of you have an idea how to solve my problem.
Try calling
carousel.showPane(1);
That will display the second pane instantly. You will want to put this near the bottom, right after where it says.
carousel.init();
If you're feeling adventurous you could try and make it automatically start with that pane as there's a variable inside the Carousel function called current_pane which is set to a default of 0 (the first pane). Altering this may work too but might require more code somewhere else. Experiment!
edit
NULL is right, it does animate it. Here's a more in depth method to set it without animation:
I found that the method responsible for changing which pane is showing was the setContainerOffset mthod which could be passed a variable to animate it. I previously told you to use showPane(2) but that then called
setContainerOffset(offset, true)
which caused the animation occur. What you should do instead is make a slightly different version of showPane...
this.setPane = function( index ) {
// between the bounds
index = Math.max(0, Math.min(index, pane_count-1));
current_pane = index;
var offset = -((100/pane_count)*current_pane);
setContainerOffset(offset, false);
};
You'll find it's almost identical to showPane except for the name and the fact that it calls setContainerOffset with animation: false. This will immediately show the pane of your choice and can be called using
carousel.setPane(index);
What I've done is added this to the init function so that it looks like this:
this.init = function() {
setPaneDimensions();
var c = this;
$(window).on("load resize orientationchange", function() {
setPaneDimensions();
c.setPane(current_pane);
//updateOffset();
})
};
Now you can change
var current_pane = 0;
to whatever you want and the carousel will always start with that pane when it's initialised! simple!
I've a scenario that requires me to detect animation stop of a periodically animated element and trigger a function. I've no control over the element's animation. The animation can be dynamic so I can't use clever setTimeout.
Long Story
The simplified form of the problem is that I'm using a third party jQuery sliding banners plugin that uses some obfuscated JavaScript to slide banners in and out. I'm in need of figuring out a hook on slideComplete sort of event, but all I have is an element id. Take this jsfiddle as an example and imagine that the javascript has been obfuscated. I need to trigger a function when the red box reaches the extremes and stops.
I'm aware of the :animated pseudo selector but I think it will need me to constantly poll the required element. I've gone through this, this, and this, but no avail. I've checked jquery promise but I couldn't figure out to use that in this scenario. This SO question is closest to my requirements but it has no answers.
P.S. Some more information that might be helpful:
The element isn't created by JavaScript, it is present on page load.
I've control over when to apply the plugin (that makes it periodically sliding banner) on the element
Most of the slideshow plugins I have used use changing classes at the end of the animation... You could extend the "addClass" method of jQuery to allow you to capture the class change as long as the plugin you use is using that method like it should:
(function($){
$.each(["addClass","removeClass"],function(i,methodname){
var oldmethod = $.fn[methodname];
$.fn[methodname] = function(){
oldmethod.apply( this, arguments );
this.trigger(methodname+"change");
return this;
}
});
})(jQuery);
I threw together a fiddle here
Even with obfuscated code you should be able to use this method to check how they are sending in the arguments to animate (I use the "options" object when I send arguments to animate usually) and wrap their callback function in an anonymous function that triggers an event...
like this fiddle
Here is the relevant block of script:
(function($){
$.each(["animate"],function(i,methodname){
var oldmethod = $.fn[methodname];
$.fn[methodname] = function(){
var args=arguments;
that=this;
var oldcall=args[2];
args[2]=function(){
oldcall();
console.log("slideFinish");
}
oldmethod.apply( this, args );
return this;
}
});
})(jQuery);
Well since you didn't give any indication as to what kind of animation is being done, I'm going to assume that its a horizontal/vertical translation, although I think this could be applied to other effects as well. Because I don't know how the animation is being accomplished, a setInterval evaluation would be the only way I can guess at how to do this.
var prevPos = 0;
var isAnimating = setInterval(function(){
if($(YOUROBJECT).css('top') == prevPos){
//logic here
}
else{
prevPos = $(YOUROBJECT).css('top');
}
},500);
That will evaluate the vertical position of the object every .5 seconds, and if the current vertical position is equal to the one taken .5 seconds ago, it will assume that animation has stopped and you can execute some code.
edit --
just noticed your jsfiddle had a horizontal translation, so the code for your jsfiddle is here http://jsfiddle.net/wZbNA/3/
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.)