jQuery wave animation on row of icons - javascript

I have a row of icons on my page and i want to create a wave animation effect when user hovers over them with the cursor.
I'm using this basic code for starters:
$('#icons > li')
.hover(function() {
$(this).animate({
'top': (-1 * hover_distance)
}, hover_speed);
}, function() {
$(this).animate({
'top': 0
}, hover_speed);
})
;
And it looks OK. But there is one issue: when you move your cursor frantically over the icons, the animation queue for every icon is becoming filled with lots of actions (up, down, up, down, up, down, etc) and icons is going up and down lots of times even if you stop to interact with the icons.
I want my icons to complete only one cycle (up and down) and then stop the animation. I'm looking for a most elegant (short, simple, light) solution for this.
PS: And you can't just use stop() because it will prevent the "wave effect" (i.e. when you move your cursor with one fast stroke over the icons and they move up and down in response, like a real wave).
PPS: Here's the JS-Fiddle:
http://jsfiddle.net/nZqLy/3/

You can use .stop() before the animations to stop the current animation or .stop(true) to cancel all animations in the queue. http://jsfiddle.net/nZqLy/9/
$('#icons > li').hover(function() {
$(this).stop(true).animate({
'top': (-1 * hover_distance)
}, hover_speed);
}, function() {
$(this).animate({
'top': 0
}, hover_speed);
});

I upvoted the answer by #jimjimmy1995, but just to provide an alternative way of doing the same animation which will be faster and more efficient:
$('#icons').on({
mouseenter:function(){
$(this).stop().animate({top:(-1*hover_distance)},hover_speed);
},
mouseleave:function(){
$(this).stop().animate({top:0},hover_speed);
}
},'li');
The only differences are:
The use of .on() is more transparent, but also allows for more extensibility (you can add more on events later, like mousemove or something, if you want)
Delegation of all li from #icons rather than making #icons > li the selector means the animation binding is only applied once, rather than many times (one time for each li) - this is the most important of the three changes
Using the native DOM name rather than the string (top vs 'top') is a best practice. It makes no difference for non-hyphenated words, but when you start dealing with marginTop vs 'margin-top' it makes a difference.
UPDATE
Found the solution:
$('#icons').on({
mouseenter:function(){
if(!$(this).is(':animated')){
$(this).animate({top:(-1*hover_distance)},hover_speed);
}
},
mouseleave:function(){
$(this).animate({top:0},hover_speed);
}
},'li');
Using the :animated selector checks if the item is in progress of being animated. The if logic will only perform the animation if it is not.
jsFiddle to prove it.

Related

Run function just before jQuery animation ends

I have a scroller that animates scrolling using prev/next buttons. The duration of the animation is calculated based on the distance moved, so the duration is variable.
$c.animate({ scrollLeft: scrollLeft },
duration, 'easeInOutExpo', function () {
if (showPrev === 0) $s.attr("data-prev", showPrev);
if (showNext === 0) $s.attr("data-next", showNext);
});
After the animation is done, I want to refresh the user interface (the buttons), which you can see is done by updating two data attributes.
To date, I've tried two different options:
1. Update the data attributes before the animation
I use a CSS transition delay to make updates happen later but cannot fully co-ordinate it with the variable duration of the animation - sometimes it can be way out.
2. Update the data attributes after the animation
I attach the update function directly after the animation, which works, but there is an obvious delay after the animation is finished before the updates kick in.
Neither is perfect.
Ideally what I would like to do is kick off the function just before the scroll animation is finished - so if the scroll duration is 1s I would like to update the attributes at duration - 0.2s (0.8s).
This seems entirely possible given that I have full control of the duration.
What I don't know is how to implement this using jQuery - possibly kick off two tasks in parallel?
I'm not a jQuery expert by any stretch of the imagination so any help would be appreciated.
Please try with this code
$c.animate({ scrollLeft: scrollLeft },
duration, 'easeInOutExpo', function () {
if (showPrev === 0) $s.attr("data-prev", showPrev);
if (showNext === 0) $s.attr("data-next", showNext);
time = setTimeout(function () {
//keep your code here
}, duration-0.2s);
});
duration-0.2s => make it correct, i just mentioned it as an example.

jquery: a better way to tie an animation to scrollbar position

var AddFootnoteScrollIndicator = function(){
$('.mobileFootnote').on('scroll touchmove', function (event) {
var scrollTop = that.$mobileFootnote.scrollTop();
if (scrollTop <= 20){
var opacity = 1 - (scrollTop/20);
$('.scroll-down-indicator').css({'opacity': opacity });
}
});
};
As the user scrolls down, the indicator slowly fades out until it is gone. They scroll back up, the indicator slowly re-appears. They stop in the middle, the indicator is half-visible.
Code works fine, but modifying the opacity via .css() seems expensive. Is there a more clever way of doing this via css or...
I don't want to delay the .on() polling because the animation needs to respond quickly to the scroll.
Any ideas?
When it comes to scroll events, modifying the css via javascript is the only way to go. There is not a way with pure CSS to detect scroll positions like you can with media queries and screen sizes.
The jquery css() function is setting the element.style.opacity property under the hood. You are only one short abstraction layer from the actual element property, so it is not "expensive".
The most costly part of that call would be the $('.scroll-down-indicator') selector, as it has to perform a DOM traversal to find elements with the class name.

Scroll snapping to a page section

So I have two sections of content near the top of my page and I’d like for users who have scrolled down to near the top of the second section to get “scroll snapped” to the top of the second one once they have stopped scrolling.
I think it should be possible using jQuery but I haven’t been able to figure it out. Here are my examples:
Without my attempt: http://codepen.io/jifarris/pen/gaVgBp
With my broken attempt: http://codepen.io/jifarris/pen/gaVgQp
Basically I can’t figure out how to make it try scrolling to the spot only once, after scrolling has stopped. It’s kind of just freaking out.
I love how the recently introduced scroll snap points CSS feature handles scroll snapping and I’d almost prefer to use it – for the browsers that support it, at least – but it seems like it only works for items that take up 100% of the viewport height or width, and it seems like it’s for scrolling within an element, not the page itself.
The top section has a fixed height, so this really can be handled with pixel numbers.
And for reference, here’s the heart of the code from my attempt:
$(function() {
$(document).on('scroll', function() {
var top = $(document).scrollTop();
if (top > 255 && top < 455) {
$('html, body').animate({scrollTop: '356'}, 500);
$('body').addClass('hotzone');
} else {
$('body').removeClass('hotzone');
}
});
});
KQI's answer contains most of the steps required to create a well functioning section-scroll for use in your application/webpage.
However, if you'd just want to experiment yourself, developing your script further, the first thing you'll have to do is add a timeout handler. Otherwise your logic, and therefor scrollAnimation, will trigger every single pixel scrolled and create a buggy bouncing effect.
I have provided a working example based on your script here:
http://codepen.io/anon/pen/QjepRZ?editors=001
$(function() {
var timeout;
$(document).on('scroll', function() {
clearTimeout(timeout);
timeout = setTimeout(function() {
var top = $(document).scrollTop();
if (top > 255 && top < 455) {
$('body').animate({
scrollTop: '356'
}, 500);
$('body').addClass('hotzone');
} else {
$('body').removeClass('hotzone');
}
}, 50);
});
});
Good luck!
All right, there are couple of things you gonna have to deal with to get a good result: which are performance, call stack queue, easing.
Performance wise you should drop jQuery animate and use VelocityJs which gives a smoother transition, better frame per second (fps) to avoid screen glitches especially on mobiles.
Call stack: you should wrap whatever logic you have to animate the scrolltop with 'debounce' function, set the delay for let say 500mm and check the scrolling behavior. Just so you know, the 'scroll' listener your using is firing on each pixel change and your script will go crazy and erratic. (It is just gonna be a moment of so many calc at the same time. Debounce will fix that for you)
Easing: make the transition looks cool not just dry snappy movement.
Remember, 'easing' with Velocity starts with 'mina.' i.e.
'Mina.easingFnName'
Finally, your logic could be right, i am in my phone now cannot debug it but try to simplify it and work with a single problem at once, be like i.e.
If ( top > 380 ) // debounce(...)

How do I stop a bouncy JQuery animation?

In a webapp I'm working on, I want to create some slider divs that will move up and down with mouseover & mouseout (respectively.) I currently have it implemented with JQuery's hover() function, by using animate() and reducing/increasing it's top css value as needed. This works fairly well, actually.
The problem is that it tends to get stuck. If you move the mouse over it (especially near the bottom), and quickly remove it, it will slide up & down continuously and won't stop until it's completed 3-5 cycles. To me, it seems that the issue might have to do with one animation starting before another is done (e.g. the two are trying to run, so they slide back and forth.)
Okay, now for the code. Here's the basic JQuery that I'm using:
$('.slider').hover(
/* mouseover */
function(){
$(this).animate({
top : '-=120'
}, 300);
},
/* mouseout*/
function(){
$(this).animate({
top : '+=120'
}, 300);
}
);
I've also recreated the behavior in a JSFiddle.
Any ideas on what's going on? :)
==EDIT== UPDATED JSFiddle
It isn't perfect, but adding .stop(true,true) will prevent most of what you are seeing.
http://jsfiddle.net/W5EsJ/18/
If you hover from bottom up quickly, it will still flicker because you are moving your mouse out of the div causing the mouseout event to fire, animating the div back down.
You can lessen the flicker by reducing the delay, however it will still be present until the delay is 0 (no animation)
Update
I thought about it and realized that there is an obvious solution to this. Hoverintent-like functionality!
http://jsfiddle.net/W5EsJ/20/
$(document).ready(function() {
var timer;
$('.slider').hover(
/* mouseover */
function(){
var self = this;
timer = setTimeout(function(){
$(self).stop(true,true).animate({
top : '-=120'
}, 300).addClass('visible');
},150)
},
/* mouseout*/
function(){
clearTimeout(timer);
$(this).filter(".visible").stop(true,true).animate({
top : '+=120'
}, 300).removeClass("visible");
}
);
});
You could use .stop() and also use the outer container position
$(document).ready(function() {
$('.slider').hover(
/* mouseover */
function(){
$(this).stop().animate({
top : $('.outer').position().top
}, 300);
},
/* mouseout*/
function(){
$(this).stop().animate({
top : $('.outer').position().top + 120
}, 300);
}
);
});
​
DEMO
Hope this helps
Couldn't reproduce your issue but I believe that hover is getting called multiple times. To work around this you can check if the div is already in animation. If yes, then don't run another animation again.
Add following piece of code to check if the div is already 'animating':
if ($(this).is(':animated')) {
return;
}
Code: http://jsfiddle.net/W5EsJ/2/
Reference:http://api.jquery.com/animated-selector/
I understand the problem and reproduced it, it happens when hovering from the bottom up. The hovering with the mouse is what's causing the problem since the animation function will be called when the mouse hovers over the image. You need to control what happens here by using mouse enter and mouse leave, check out a similar example: Jquery Animate on Hover
The reason it's like that is because the hover is getting queued up causing it to slide up and down multiple times. There's a plug-in called hoverIntent which fixes the issue. http://cherne.net/brian/resources/jquery.hoverIntent.html
If you do decide to use hoverIntent, the only thing you have to change in your code is .hover > .hoverIntent

jQuery's stop() seems to be blocking animations that haven't been queued yet

How does jQuery's stop() actually work?
If you look here (http://jsfiddle.net/hWTT6/), when you hover over the main blue box it should fade to red, and when you hover off it should fade back. The problem is it will completely fade to red (and then back to blue) even if the mouse has hovered off before the first fade was complete. The problem can more clearly be seen with the slide effect. Hover over the slide "button" and the main box will slide to blue, hover off, it will slide back. But try hovering on and off and on and off, before the first animation has completed. You'll see that all four animations are carried out. I included both examples here to show it is not just a problem with one effect or something.
I thought this would be easily fixed by adding a stop before the animations, as shown commented out in the code. But, if I do this the current animation will stop and the following one will never start. Almost as though stop is blocking an animation that is occurring after the call to stop.
What am I missing here?
Thanks.
You are missing that .stop() accepts two arguments. Both boolean, indicating:
- clearQueue (first)
- jumpToEnd (second)
So by calling $('#foo').stop( true, true ).doSomeOtherStuff() you should get your desired goal.
Reference: .stop()
The problem is the CSS is getting messed up by stopping at arbitrary points.
The fadeIn(), fadeOut(), slideUp() and slideDown() move from the current state to a new one and then revert to that - not to the original CSS.
You need to fix the CSS back in to a usable state to continue with after the .stop(), or more clearly specify the animation targets.
As the others have said, you can get the CSS to the correct position, by ensuring that when you stop the animation, it jumps to the end of it, rather than leaving everything in an arbitrary state.
UPDATE:
Take a look at the code in this update of your demonstration: http://jsfiddle.net/hWTT6/5/
It might not be exactly how you want it to perform, but the trick, if you do not want the animation to run its course, is to get the animation back in to a state that it can continue from in the way in which you desire.
$(function() {
$('#fade')
.mouseenter(function() {
$('#fg_fade').stop().animate({ 'opacity': 0 }, 'slow', function() {
$('#fg_fade').css('height', '100%');
});
})
.mouseleave(function() {
$('#fg_fade').stop().animate({ 'opacity': 1, 'height': '100%' }, 'slow');
});
$('#slide_fire')
.mouseenter(function() {
$('#fg_fade').stop().animate({ 'height': 0 }, 'slow', function() {
$('#fg_fade').css('opacity', 1);
});
})
.mouseleave(function() {
$('#fg_fade').stop().animate({ 'height': '100%' }, 'slow', function() {
$('#fg_fade').css('opacity', 1);
});
});
});
You could set the stop() options to (true, true) so that you cancel all events in cue and jump to the end of the previous animation. look at the fiddle:http://jsfiddle.net/hWTT6/4/
The stop method can be called in the following difference ways:
.stop(true);
//Same as:
.stop(true, false); //Empty the animation queue only
//Or
.stop(true,true); // Empties the animation queue AND jumps to the end
//Default
.stop()
//Same as
.stop(false,false);
There may be a better way using .animate instead: Demo Here
$(function() {
$('#fade')
.mouseenter(function() {
$('#fg_fade').stop().css('height', '10em').animate({'opacity' : '0'}, 'slow');
})
.mouseleave(function() {
$('#fg_fade').stop().css('height', '10em').animate({'opacity' : '1'}, 'slow');
});
$('#slide_fire')
.mouseenter(function() {
$('#fg_fade').stop().css('opacity', '1').animate({'height' : '0'}, 'slow');
})
.mouseleave(function() {
$('#fg_fade').stop().css('opacity', '1').animate({'height' : '10em'}, 'slow');
});
});
This way the animation stops when you want it to and still runs the next animation.
The problem with doing .stop then slideUp/slideDown or fadeIn/fadeOut is that the animation can end prematurely and keep an incorrect height/opacity.
The problem is caused by the way fadeIn, fadeOut etc. work. You may expect them to fade between 0 and 1. However, in reality they fade between 0 and whatever the "baseline" opacity is. You can see this here:
http://jsfiddle.net/hWTT6/7/
You'll notice I set the initial opacity to .5. Now when I call fadeIn it does not fade all the way in to 1 it fades to my baseline of .5. Your problem occurs when you stop the animation prematurely, the "baseline" becomes whatever the opacity is at the time it is stopped. Now when you call fadeIn on mouseleave it tries to fade to this new baseline and finds it is already there. You can see this illustrated by going here:
http://jsfiddle.net/hWTT6/8/ (original, but with .stop)
If you place your mouse over slide and then remove it half-way through the animation, it will stop in the middle. Now place your mouse back over slide and wait for the animation to complete. If you now remove your mouse, you will see that it slides back down to the place where the first animation was stopped. That is because this is the new "baseline".
The way that you would solve this is actually to replace fadeIn, fadeOut, etc. with a more explicit animation. For instance, use fadeTo to tell it to fade between 0 and 1:
http://jsfiddle.net/hWTT6/6/
Notice that since I am telling it to fade to 0 or 1 everything works. A similar thing could be done to replace slideUp and slideDown using animate.

Categories

Resources