Animating long sequences in jQuery - javascript

I have to make a long animation with jQuery, full of fadeOuts,fadeIns,slideIns,...
The problem I am having is that my code looks ugly and it is full of callback. Also, if I want to stop animation for some time like: slideOut->wait 5 seconds->slideIn I have to use delay and I am not sure if that is the best practice.
Example:
/* Slides */
var slide1 = $('div#slide1'),
slide2 = $('div#slide2'),
slide3 = $('div#slide3');
$(document).ready(function(){
slide1.fadeIn(function(){
slide2.fadeIn(function(){
slide3.fadeIn().delay(3000).fadeOut(function(){
slide2.fadeOut(function(){
slide1.fadeOut();
});
});
});
});
});
JSFIddle: http://jsfiddle.net/ZPvrD/6/
Question: Is there any other way of building animations in jQuery, possibly even some great plugin to help me solve this problem?
Thanks!

Here's the plugin you were looking for :) Does the exact same thing, but is much more flexible than your existing code http://jsfiddle.net/ZPvrD/11/
(function($){
$.fn.fadeInOut = function(middleDelay) {
middleDelay = middleDelay || 0;
var index = 0,
direction = 1, // 1: fading in; -1: fading out
me = this,
size = me.size();
function nextAnimation() {
// Before the first element, we're done
if (index === -1 ) { return; }
var currentEl = $(me.get(index)),
goingForward = direction === 1,
isLastElement = index === (size - 1);
// Change direction for the next animation, don't update index
// since next frame will fade the same element out
if (isLastElement && goingForward) {
direction = -1;
} else {
index += direction;
}
// At the last element, before starting to fade out, add a delay
if ( isLastElement && !goingForward) {
currentEl.delay(middleDelay);
}
if (goingForward) {
currentEl.fadeIn(nextAnimation);
} else {
currentEl.fadeOut(nextAnimation);
}
}
nextAnimation();
return this;
}
})(jQuery);
And you call it like
$('div.slideWrapper>div.slide').fadeInOut(3000);
This process of traversing up and down a list of jQuery elements waiting for each animation to finish could be abstracted so that it could be used for other things besides fadeIn and fadeOut. I'll leave that for you to try out if you feel adventurous.

Try this:
/* Slides */
var slide = $('div[id*="slide"]');
$( function(){
slide.each( function( k ){
$( this ).delay( 500 * k ).fadeIn();
});
});

JQuery animations take two parameters (maximum), duration and complete, duration is the time in milliseconds for how long you want your animation to complete, or you can use "slow" or "fast", and the second params complete which is the callback function.
If don't want to use delay, you may make the previous animation slow.
e.g.
slide1.fadeIn(5000, function(){
slide2.fadeIn();
};

Related

Sliding Carousel - Pagination not directing to correct location

My carousel has been built so that it slides to the next frame automatically every 5 seconds, for which I have written:
var carousel = $('.carousel ul:first');
var pagination = $('.carousel ul:last');
function slide01() {
carousel.delay(5000).animate({'margin-left':'-100%'}, function() {
pagination.find('li').removeClass('on');
pagination.find(':nth-child(2)').addClass('on');
});
slide02();
};
function slide02() {
carousel.delay(5000).animate({'margin-left':'-200%'}, function() {
pagination.find('li').removeClass('on');
pagination.find(':nth-child(3)').addClass('on');
});
slide03();
};
function slide03() {
carousel.delay(5000).animate({'margin-left':'-300%'}, function() {
pagination.find('li').removeClass('on');
pagination.find(':nth-child(4)').addClass('on');
});
slide04();
};
function slide04() {
carousel.delay(5000).animate({'margin-left':'0%'}, function() {
pagination.find('li').removeClass('on');
pagination.find(':nth-child(1)').addClass('on');
});
slide01();
};
slide01();
While this is working, I'm having difficulties with the pagination buttons. They respond but instead of going to its appropriate page, it's going to the next page, which you can view in my FIDDLE.
This is the code I have written for each button:
pagination.find('li:nth-child(1)').click(function(){
carousel.stop().animate({'margin-left':'0%'});
});
pagination.find('li:nth-child(2)').click(function(){
carousel.stop().animate({'margin-left':'100%'});
});
pagination.find('li:nth-child(3)').click(function(){
carousel.stop().animate({'margin-left':'200%'});
});
pagination.find('li:nth-child(4)').click(function(){
carousel.stop().animate({'margin-left':'300%'});
});
What can I do to fix this?
I have updated your Javascript code to provide your desired functionality and made it more extendable. Please let me know if you have any questions.
var carousel = $('.carousel ul:first');
var pagination = $('.carousel ul:last');
var slideWidth = 200;
var autoSlideTiming = 5000;
var timeout = null;
// Buttons
$.each(pagination.children(), function(i){
$(this).click(function(){
pagination.find('li.on').removeClass('on');
//if you wanted to find the width dynamically
//var slideWidth = $(this).parent().parent().find('ul:eq(0) li:eq('+i+')').width();
carousel.stop().animate({'margin-left': -(i * slideWidth)});
$(this).addClass('on');
});
});
// Slider
function advanceSlide(){
var currIndex = $(".pagination li.on").index();
var nextIndex = ((pagination.children().length - 1) == currIndex) ? 0 : (currIndex + 1);
carousel.animate({'margin-left': -(nextIndex * slideWidth)}, function() {
pagination.find('li.on').removeClass('on');
pagination.find(':nth-child('+(nextIndex + 1)+')').addClass('on');
timeout = setTimeout(function(){advanceSlide()}, autoSlideTiming);
});
}
$('.carousel').hover(function(){
clearTimeout(timeout);
}, function(){
timeout = setTimeout(function(){advanceSlide()}, autoSlideTiming);
});
//init auto slide
timeout = setTimeout(function(){advanceSlide()}, autoSlideTiming);
Also here is an updated Fiddle
Explanation
The $.each is a jQuery utility function that works like a normal for loop (I would actually recommend a vanilla JavaScript for loop if you know how). This $.each accepts 2 arguments: a collection (pagination.children() in our case, which is an array of the li's), and a callback function. This callback function passes in i which is our zero-based index of the collection. So, it's not 1, 2, 3, 4 but 0, 1, 2, 3. In this loop this is each li, which it is attaching a click event that handles removing and applying your on class and also the animation. The -(i * slideWidth) is taking the current zero-based index and multiplying it times the slideWidth and then getting the negative value of that result. So, the first slide -(0 * 200) would animate the margin-left to -0 or 0, and the second slide -(1 * 200) would animate the margin-left to -200, which pulls it 200 pixels in the left direction making it slide. This will allow you to had new li's to your pagination and this code will still work!
The trick to making the advanceSlide() function dynamic, is finding what the index of the next slide is. To find out where we need to go we first must find where we are. That is what currIndex is for, which we find with the jQuery index() function, which returns the zero-based index of the li with the on class. Now the magic. The nextIndex variable is being set using a ternary operator to find the value. A ternary is just programmer shorthand for a basic if then else statement. So if the ternary in this code was written out the normal way it would look like this:
var nextIndex;
if( (pagination.children().length - 1) == currIndex ){
nextIndex = 0;
}else{
nextIndex = currIndex + 1;
}
Remeber our currIndex is zero-based, but our collection (pagination.children()) length (number of items in the total collection) is not zero-based so we must minus 1 from it. Then we are checking if that value is equal to our currIndex because if this is the last item in our collection we need to set our nextIndex back to 0 so the slideshow loops back around. If it's not the last index we just add 1 to advance to the next slide. After we find that nextIndex we do our animation and apply our class.
I know this is a little long winded, but it's important that you fully understand the code you implement! Please let me know if you have nay further questions!
You have a "Maximum call stack size exceeded." error. Yours slide0X function calls another slide0Y function and so on. You should invoke slide0Y only once the slide0X animation ended (inside the complete callback).
Moreover in your click callbacks, margin-left should be negative since you want to slide to the left.
BTW, I would implement a function slideTo(slideId) so that it will work for n slides, instead of writing a function for each slide.

Difficulty with setInterval loop using class of Divs

$(document).ready(function fadeIt() {
$("#cool_content > div").hide();
var sizeLoop = $("#cool_content > div").length;
var startLoop = 0;
$("#cool_content > div").first().eq(startLoop).fadeIn(500);
setInterval(function () {
$("#cool_content > div").eq(startLoop).fadeOut(1000);
if (startLoop == sizeLoop) {
startLoop = 0
} else {
startLoop++;
}
$("#cool_content > div").eq(startLoop).fadeIn(1500);
}, 2000);
});
Here I want a class of divs to animate, infinitely!
However, because the interval is set to two seconds there is period where no div is showing!
What would be an appropriate way to loop the animation of these divs?
I thought about using a for loop but couldn't figure out how to pass a class of divs as arguments. All your help is appreciated.
Thanks!
Ok, generally, you should know that Javascript is a single threaded environment. Along with this, the timer events are generally not on time accurately. I'm not sure how jQuery is doing fadeIn and fadeOut, but if it's not using CSS3 transitions, it's going to be using timeOut and Intervals. So basically, there's a lot of timer's going on.
If you go with the for loop on this one, you'd be blocking the single thread, so that's not the way to go forward. You'd have to do the fade in/out by yourself in the setInterval.
Setting the opacity on each interval call. Like div.css('opacity', (opacity -= 10) + '%')
If you're trying to fade in and out sequentially, I think maybe this code would help
var opacity = 100,
isFadingIn = false;
window.setInterval(function() {
if (isFadingIn) {
opacity += 10;
if (opacity === 100) isFadingIn = false;
} else {
opacity -= 10;
if (opacity === 0) isFadingIn = true;
}
$('#coolContent > div').css('opacity', opacity + '%');
}, 2000);
Consider the following JavaScript / jQuery:
$(function(){
var divs = $('#cool_content > div').hide();
var curDiv;
var counter = 0;
var doUpdate = function(){
// Hide any old div
if (curDiv)
curDiv.fadeOut(1000);
// Show the new div
curDiv = divs.eq(counter);
curDiv.fadeIn(1000);
// Increment the counter
counter = ++counter % divs.length;
};
doUpdate();
setInterval(doUpdate, 2000);
});
This loops infinitely through the divs. It's also more efficient than your code because it only queries the DOM for the list of divs once.
Update: Forked fiddle
instead of
if (startLoop == sizeLoop)
{
startLoop = 0
}
else
{
startLoop++;
}
use
startLoop =(startLoop+1)%sizeLoop;
Check the demo http://jsfiddle.net/JvdU9/ - 1st div is being animated just immediately after 4th disappears.
UPD:
Not sure I've undestood your question, but I'll try to answer :)
It doesn't matter how many divs you are being looped - 4, 5 or 10, since number of frames are being calculated automatically
x=(x+1)%n means that x will never be greater than n-1: x>=0 and x<n.
x=(x+1)%n is just shorten equivalent for
if(x<n-1)
x++;
else
x=0;
as for me first variant is much readable:)
And sorry, I gave you last time wrong demo. Correct one - http://jsfiddle.net/JvdU9/2/

Function not stopping when calling another one

I`m having a troubble with a simple thing.
I have an div, when clicked, an amination start (an infinite loop of images changing, simulating an animated gif).
But, when i click on the other div, the first one need to stop, and start just the other animation, and this goes on to every animation (will be 8 on total).
Here is the code for just one image loop:
var t1;
var t2;
var anim1 = new Array ('img/testes2/anim1_1.png','img/testes2/anim1_2.png');
var anim2 = new Array ('img/testes2/anim2_1.png','img/testes2/anim2_2.png');
var index = 1;
var verifica1 = 0;
var verifica2 = 0;
function rotateImage1(){
$('#imagemPrinc').fadeOut(0, function(){
$(this).attr('src', anim1[index]);
$(this).fadeIn(0, function(){
if (index == anim1.length-1){
index = 0;
}
else{
index++;
}
});
});
return false;
}
function stopTimer1(){
if(verifica1 = 1){
clearInterval(t2);
}
}
function muda1(){
if (verifica1 = 1){
//stopTimer2();
//$('#bgImagem').css({'background-image':'url(img/testes2/anim1_1.png)'});
t1 = setInterval(rotateImage1,500);
}
}
The same function for the second animation.
The verifica var, and the stopTimer function, i tried to make one stop, and just the other plays, but doesn't seems to be working. That's why it's commented on the code.
It will be easier to look the code running, so thats it ---HERE---
The clickable divs are those two Red Squares.
Someone can help me please!?
Thanks!
clearTimeout takes as argument the timer id returned by the setInterval function (here it's t1).
Instead of using fadeOut and fadeIn with a duration of 0, you should simply use hide and show.
As an aside, you can simplify this block :
if (index == anim1.length-1){
index = 0;
}
else{
index++;
}
in
index = [(index+1)%anim1.length];
And this is very wrong :
if(verifica1 = 1){
This is not a test : it always change verifica1 and is always true. You probably want ==.
Is there a point in your code where you (voluntarily) set verifica1 ?

how to make a <div> appear in slow motion

I want the javascript code to show a div in slow motion.
function showDiv(divID)
{
if(document.getElementById(divID).style.display=='none')
{
document.getElementById(divID).style.display='block';
}
}
Here div appears, but not in slow motion. Can anyone help ??
Thanks in advance
Dev..
There is no need of jQuery in this atall , its just a basic I am using your function to explain how thats done.
function showDiv(divID)
{
if(document.getElementById(divID).style.display=='none')
{
document.getElementById(divID).style.display='block';
}
}
What your function is doing is basically removing the whole Element from BOX Model ( the toggle of block and none removes the element totally from the BOX Model so it doesnt occupies any space or anything , this but may / may not cause some layout issues );
Now to animate it in slow motion you need a timing function.
a timing function is a simple mathematical function which gives the value of the property ( opacity in your case ) for a given time or depending on other parameters .
Other then that you also need to use properties like opacity in order to fade it (Opacity is a CSS property that defines the transparency of an element and its childrens )
So let us begin with a very basic show / hide using setTimeout Function in JS.
function getValue(t,dir){
if( dir > 0){
return 0.5*t; /* Y = mx + c */
}else{
return 1-(0.5*t);
}
/*
Here the slope of line m = 0.5.
t is the time interval.
*/
}
function animator(divID){
if(!(this instanceof animator)) return new animator(divID); /* Ignore this */
var Node = document.getElementById(divID),
start = new Date.getTime(), // The initiation.
now = 0,
dir = 1,
visible = true;
function step( ){
now = new Date.getTime();
var val = getValue( now - start,dir)
Node.style.opacity = val;
if( dir > 0 && val > 1 || dir < 0 && val < 0 ){
visible = !(visible*1);
// Optionally here u can call the block & none
if( dir < 0 ) { /* Hiding and hidden*/
Node.style.display = 'none'; // So if were repositioning using position:relative; it will support after hide
}
/* Our animation is finished lets end the continous calls */
return;
}
setTimeout(step,100); // Each step is executated in 100seconds
}
this.animate = function(){
Node.style.display = 'block';
dir *= -1;
start = new Date.getTime();
setTimeout(step,100);
}
}
now you can simply call the function
var magician = new animator('divName');
then toggle its animation by
magician.animate();
Now playing with the timing function you can create whatever possibilities you want as in
return t^2 / ( 2 *3.23423 );
or even higher polynomial equations like
return t^3+6t^2-38t+12;
As you can see our function is very very basic but it explains the point of how to make animations using pure js . you can later on use CSS3 module for animation and trigger those classes with javascript :-)
Or perhaps write a cross browser polyfill using CSS3 where available ( it is faster ) , and JS if not :-) hope that helps
Crossbrowser solution (without jQuery) :
HTML :
<div id="toChange" ></div>
CSS :
#toChange
{
background-color:red;
width:200px;
height:200px;
opacity:0;//IE9, Firefox, Chrome, Opera, and Safari
filter:alpha(opacity=0);//IE8 and earlier
}
Javascript :
var elem=document.getElementById("toChange");
var x=0;
function moreVisible()
{
if(x==1)clearInterval(t);
x+=0.05;
elem.style.opacity=x;
elem.style.filter="alpha(opacity="+(x*100)+")";
}
var t=setInterval(moreVisible,25);
Fiddle demonstration : http://jsfiddle.net/JgxW6/1/
So you have a few jQuery answers but I wouldn't recommend jQuery if fading the div is all you want.
Certainly jQuery makes things easier but it is a lot of overhead for a single simple functionality.
Here is someone that did it with pure JS:
Fade in and fade out in pure javascript
And a CSS3 example:
How to trigger CSS3 fade-in effect using Javascript?
You can use jquery $.show('slow') for the same, if you want to do the same without using jquery then you might be required to code something to show the effect yourself, you may have a look at source of jquery's show function http://james.padolsey.com/jquery/#v=1.6.2&fn=show . alternatively , you can also use fadein() for fade in effect in jquery
Yes you can do it using Jquery. Here is my sample example
$('#divID').click(function() {
$('#book').show('slow', function() {
// Animation complete.
});
});
For details clik here
Thanks.

Jquery - Carasol build finished and would like advice on best practice / neatening up my code

I have been building my own carasol over the past few days.
My Jquery is based on tutorials on the web and also from help and advice from SO.
I am not a Jquery guru just an enthusiast and think my code is a little sloppy, hence the post.
here is a link to the working code: http://jsfiddle.net/JHqBA/2/ (updated link)
basically what happens is:
if someone hits the page with a # values in the url it will show the appropriate slide and example would be www.hello.com#two, this would slide to slide two
if someone clicks the numbers it will show the appropriate slide
next and prev also slide through the slides.
The question is, is there anything i could have wrote better as i know there is alot of duplicate code.
I understand its a big ask but it would help me learn a little more (i think my code is a little old school)
if anyone has any questions please feel free to ask and ill answer what it does or is supposed to do.
Sluap
--- Edit ----
I have made only one aniamtion function now which has got rid of alot of duplicate code.
I have yet to look into on function but will do soon.
I would like to know more about the create a new function, outside of the jQuery ready block as i cant get this working or quite understand how i can get it to work sorry
any more tips would be great ill carry on working on this project till i am happy with it.
also is there a better way to write:
if ($slideNumber == 1) {
$('#prev').attr("class", "not_active")
$('#next').attr("class", "active")
}
else if ($slideNumber == divSum) {
$('#next').attr("class", "not_active");
$('#prev').attr("class", "active");
}
else {
$('#prev').attr("class", "active")
$('#next').attr("class", "active")
};
Jquery full:
$(document).ready(function () {
//////////////////////////// INITAL SET UP /////////////////////////////////////////////
//Get size of images, how many there are, then determin the size of the image reel.
var divWidth = $(".window").width();
var divSum = $(".slide").size();
var divReelWidth = divWidth * divSum;
//Adjust the image reel to its new size
$(".image_reel").css({ 'width': divReelWidth });
//set the initial not active state
$('#prev').attr("class", "not_active");
//////////////////////////// SLIDER /////////////////////////////////////////////
//Paging + Slider Function
rotate = function () {
var triggerID = $slideNumber - 1; //Get number of times to slide
var image_reelPosition = triggerID * divWidth; //Determines the distance the image reel needs to slide
//sets the active on the next and prev
if ($slideNumber == 1) {
$('#prev').attr("class", "not_active")
$('#next').attr("class", "active")
}
else if ($slideNumber == divSum) {
$('#next').attr("class", "not_active");
$('#prev').attr("class", "active");
}
else {
$('#prev').attr("class", "active")
$('#next').attr("class", "active")
};
//Slider Animation
$(".image_reel").animate({
left: -image_reelPosition
}, 500);
};
//////////////////////////// SLIDER CALLS /////////////////////////////////////////////
//click on numbers
$(".paging a").click(function () {
$active = $(this); //Activate the clicked paging
$slideNumber = $active.attr("rel");
rotate(); //Trigger rotation immediately
return false; //Prevent browser jump to link anchor
});
//click on next button
$('#next').click(function () {
if (!$(".image_reel").is(':animated')) { //prevent clicking if animating
var left_indent = parseInt($('.image_reel').css('left')) - divWidth;
var slideNumberOn = (left_indent / divWidth);
var slideNumber = ((slideNumberOn * -1) + 1);
$slideNumber = slideNumber;
if ($slideNumber <= divSum) { //do not animate if on last slide
rotate(); //Trigger rotation immediately
};
return false; //Prevent browser jump to link anchor
}
});
//click on prev button
$('#prev').click(function () {
if (!$(".image_reel").is(':animated')) { //prevent clicking if animating
var left_indent = parseInt($('.image_reel').css('left')) - divWidth;
var slideNumberOn = (left_indent / divWidth);
var slideNumber = ((slideNumberOn * -1) - 1);
$slideNumber = slideNumber;
if ($slideNumber >= 1) { //do not animate if on first slide
rotate(); //Trigger rotation immediately
};
}
return false; //Prevent browser jump to link anchor
});
//URL eg:www.hello.com#one
var hash = window.location.hash;
var map = {
one: 1,
two: 2,
three: 3,
four: 4
};
var hashValue = map[hash.substring(1)];
//animate if hashValue is not null
if (hashValue != null) {
$slideNumber = hashValue;
rotate(); //Trigger rotation immediately
return false; //Prevent browser jump to link anchor
};
});
Question and answer has been moved over to https://codereview.stackexchange.com/questions/8634/jquery-carasol-build-finished-and-would-like-advice-on-best-practice-neateni/8635#8635
1) Separation of Concerns
Start by refactorring your code in to more granular functions.
You can read more about SoF at http://en.wikipedia.org/wiki/Separation_of_concerns
Update:
E.g. Instead of having your reel resizing code inline, put it in it's own function, like this:
function setImageReelWidth () {
//Get size of images, how many there are, then determin the size of the image reel.
var divWidth = $(".window").width();
var divSum = $(".slide").size();
var divReelWidth = divWidth * divSum;
//Adjust the image reel to its new size
$(".image_reel").css({ 'width': divReelWidth });
}
This achieves 2 things:
a. First, it groups a block of code that is logically cohesive, removing it from the main code which results in a much cleaner code habitat.
b. It effectively gives a label to the code block via the function name that is descriptive of what it does, and therefore makes understanding of the code much simpler.
Later, you can also encapsulate the whole thing in it's own "class" (function) and you can move it into it's own js file.
2) The jQuery "on" function
Use the "on" function to attach your click events, rather than the "click" function.
http://api.jquery.com/on/
This has the added advantage of also binding it to future elements matching your selector, even though they do not exist yet.
3) The ready function
// I like the more succinct:
$(handler)
// Instead of:
$(document).ready(handler)
But you might like the more obvious syntax.
Those are just a few things to start with.
-- Update 1 --
Ok, StackOverflow is not really suited to a refactoring work in progress, but we'll make do. I think you should keep your original code block in your question, so that future readers can see where it started and how it systematically improved.
I would like to know more about the create a new function, outside of
the jQuery ready block as i cant get this working or quite understand
how i can get it to work sorry
I am not familiar with jsfiddle.net, but it looks cool and helpful, but might also be a bit confusing if you don't know what is going on. I am not sure I do :), but I think that script editor window results in a .js file that is automatically referenced by the html file.
So here is an example of a function defined outside of the ready block, but referenced from within.
function testFunction () {
alert ('it works');
}
$(document).ready(function () {
testFunction();
// ... other code
});
This should pop up an alert box that says, "it works" when the page is loaded.
You can try it for yourself.
Then, once you got that working, you can refactor other logically cohesive blocks of code into their own functions. Later you can wrap them all up into their own javascript 'class'. But we'll get to that.

Categories