Jquery/CSS sequential element animation with callback - javascript

I am writing a script that will animate a set of jQuery Elements, but I'm running into some issues. Here are my requirements:
Sequential animations
Callback functionality after all animations are complete. Callback can be defined globally
Animation works on floated elements with
Entire solution can be js/jquery/css or a combination
Here's what I've gotten so far: http://jsfiddle.net/fmpeyton/cqAws/
HTML
<div class="block">Im a box</div>
<div class="block">me too</div>
<div class="block">and me!</div>
<div class="block">am I?</div>
<div class="block">yes.</div>
<div class="block">Im a box</div>
<div class="block">me too</div>
<div class="block">and me!</div>
<div class="block">am I?</div>
<div class="block">yes.</div>
<div class="block">Im a box</div>
<div class="block">me too</div>
<div class="block">and me!</div>
<div class="block">am I?</div>
<div class="block">yes.</div>
CSS
.block{
float:left;
width:100px;
background: red;
margin: 0 10px;
padding: 10px;
}
.hiddenForAnimation{ opacity:0; margin-top:-20px; }
JS
$(function(){
$('.block').addClass('hiddenForAnimation').each(function(i){
var delay = i * 200,
animationSpeed = 800;
$(this).delay(delay).animate({opacity: '1', marginTop: '0px'
}, animationSpeed, function(){ if(typeof afterPageAnimation === 'function' && i === $(this).length){ setTimeout(afterPageAnimation, delay + animationSpeed);} $(this).removeClass('hiddenForAnimation').attr('style',''); });
});
});
function afterPageAnimation(){ alert('animation is done!'); }
My issues:
Is there a better way to refactor this JS script to be sequential? Using delay() is effective, but not elegant.
The callback is not being executed directly after the animations
When the last element in a row finishes animating, the first element in the next row starts at the far right, then jumps to the left (I suspect margin-top has something to do with this.)
Thanks!

This works
http://jsfiddle.net/cqAws/12/
Remember: In positioning animations, use position:relative or position:absolute and play with top, left, right, bottom instead of margins.
It's better
EDIT: made it a little better.

new
$(function(){
j=0;
$('.block').each(function(i){
var interv = +(i*800);
var animationSpeed = 800;
$(this).toggleClass('hiddenForAnimation')
.delay(interv)
.animate({opacity: '1', marginTop: '0'},animationSpeed,function(){
j++;
$(this).delay(+(interv+animationSpeed))
.toggleClass('hiddenForAnimation')
.attr('style','');
if(j>=+($('.block').length)) afterPageAnimation();
});
});
});
function afterPageAnimation(){ alert('cool'); }
FIDDLE

For future viewers:
I've solved this by creating a small Jquery plugin found here: https://github.com/fillswitch/Jquery-Sequential-Animations
Hope this helps some other users in the future!

Related

Run Jquery only when an element is in the viewport

I'm trying to perform the Jquery function below when the element becomes visible in the viewport rather than on the page load. What would I need to change to allow that to happen? I'm using an external JS file to perform the Jquery, so keep that in mind.
Here's a piece of the HTML that is associated with the Jquery function -
<div class="skillbar clearfix " data-percent="70%">
<div class="skillbar-title" style="background: #FF704D;">
<span>Illustrator</span></div>
<div class="skillbar-bar" style="background: #FF704D;"></div>
<div class="skill-bar-percent">70%</div>
</div>
jQuery(document).ready(function(){
jQuery('.skillbar').each(function(){
jQuery(this).find('.skillbar-bar').animate({
width:jQuery(this).attr('data-percent')
},4000);
});
});
I once came across such problem and what I used is waypoints small library.
all you need is to include this library and do:
var waypoint = new Waypoint({
element: document.getElementById('waypoint'),
handler: function(direction) {
console.log('Element is in viewport');
}
})
Using CSS3 transitions instead of jQuery animations might be more performant and simpler. a cheap and nasty way of pushing it out of screen to demonstarate the effect.
There's a couple of things you'll need to do - firstly if you only want the animation to trigger when it's in the viewport then you'll need to check if anything is in the viewport on scroll. Then only update the bars width when it comes into view. If you want the effect to repeat every time it comes into viewport you'll need to set .skillbar-bar's width back to 0 if it's out of the viewport (just add an else statement to the viewport checking if)
I've added a 1000px margin-top and 400px margin-bottom in my example to .skillbar as a cheap and nasty way of demonstrating the effect
(function($){
$(document).ready(function(){
var $els = $('.skillbar'); // Note this must be moved to within event handler if dynamically adding elements - I've placed it for performance reasons
var $window = $(window);
$window.on('scroll', function(){
$els.each(function(){ // Iterate over all skillbars
var $this = $(this);
if($window.scrollTop() > $this.offset().top - $window.height()){ // Check if it's in viewport
$this.find('.skillbar-bar').css({'width' : $this.attr('data-percent')}); // Update the view with percentage
}
});
});
});
}(jQuery));
.skillbar{
margin-top: 1000px;
margin-bottom: 400px;
position: relative
}
.skillbar-bar{
transition: width 4s;
position: absolute;
height: 20px;
}
.skill-bar-percent{
position: absolute;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
Scroll down 1000px :)
<div class="skillbar clearfix " data-percent="70%">
<div class="skillbar-title">
<span>Illustrator</span></div>
<div class="skillbar-bar" style="background: #FF704D; width: 20%"></div>
<div class="skill-bar-percent">70%</div>
</div>
This might work for you.
var el = $('.yourElement'),
offset = el.offset(),
scrollTop = $(window).scrollTop();
//Check for scroll position
if ((scrollTop > offset.top)) {
// Code..
}

JQuery effect is stuttering when loading a YouTube iframe

I am very new to JS/JQuery/JQueryUI but have made a few things work on a new site I'm working on.
I've set up a basic navbar where a .click makes different divs slide into view with .show while the other three pop out of existence with .hide. I was extremely proud of myself even though this is super basic.
My issue is that one of these divs contains a YouTube iframe. To get it to stop playing when another div is shown, I just remove the src with .attr (clunky, I know). This means that since the source is re-appended to the iframe each time, going back to that div is slower than I want it to be, and jQuery stutters.
I've put a stripped down version into a JSFiddle. Any suggestions on improving the performance would be greatly appreciated!
PS: The video I have as a placeholder is hilarious and you should enjoy it! :)
HTML:
<div class="button" id="home">1</div>
<div class="button" id="about">2</div>
<div class="button" id="latest">3</div>
<div class="button" id="contact">4</div>
<div class="home"><iframe class="video" id="homeVid"
src="https://www.youtube.com/embed/gspaoaecNAg?controls=0?showinfo=0?rel=0?enablejsapi=1"
frameborder="0" allowfullscreen></iframe></div>
<div class="content about"></div>
<div class="content latest"></div>
<div class="content contact"></div>
CSS
.content {
width: 600px;
height: 480px;
display: none;
clear:both
}
.home, .video {
width: 600px;
height: 480px;
display: flex;
clear:both;
background-color: #CCC
}
.about {background-color: #F00}
.latest {background-color: #0F0}
.contact {background-color: #00F}
.button {
width: 25px;
height: 25px;
border: 1px solid black
}
JavaScript
$(document).ready(function() {
var urlhome = $('#homeVid').attr('src');
$('#home').click(function() {
$('.home').show('slide', {direction: 'right', easing: 'swing'}, 400);
$('.about, .contact, .latest').hide(0);
$('#homeVid').attr('src', urlhome);
});
$('#about').click(function() {
$('.about').show('slide', {direction: 'right', easing: 'swing'}, 400);
$('.home, .contact, .latest').hide(0);
$('#homeVid').attr('src', ' ');
});
$('#latest').click(function() {
$('.latest').show('slide', {direction: 'right', easing: 'swing'}, 400);
$('.home, .contact, .about').hide(0);
$('#homeVid').attr('src', ' ');
});
$('#contact').click(function() {
$('.contact').show('slide', {direction: 'right', easing: 'swing'}, 400);
$('.home, .about, .latest').hide(0);
$('#homeVid').attr('src', ' ');
});
});
Indeed, adding and removing the iframe is costly in terms of performance. Instead we must stop the playback and hide it.
This necessitates to insert it differently into the document, using the YouTube Player API Reference for iframe Embeds. Then we do this:
HTML
<div class="content home">
<div id="player"></div>
</div>
var player;
JavaScript
$(window).load(function(){
player = new YT.Player('player', {
height: '480',
width: '600',
videoId: 'gspaoaecNAg',
});
});
We can simply use player.stopVideo(); whenever we hide the home element. But if only it was so simple.
Using jQuery's hide() has side effects, because the way it hides elements is by setting their CSS to display:none which effectively removes them from the document. This destroys the iframe and recreates it on show(), which presents the same performance issue as before.
We need something more subtle, hiding the elements by putting them aside. For this we use positionning:
.hidden {
position:fixed;
left:200%;
}
This puts them further on the right of the document, outside the viewport and since the units are relative, it can never be vsible no matter how much we stretch the window. This necessitates a few changes in HTML, plus some others for an optimization I will detail further below.
HTML:
<div class="button" id="home">1</div>
<div class="button" id="about">2</div>
<div class="button" id="latest">3</div>
<div class="button" id="contact">4</div>
<div class="content home">
<div id="player"></div>
</div>
<div class="content about hidden"></div>
<div class="content latest hidden"></div>
<div class="content contact hidden"></div>
We have added the class hidden to all elements not visible at the start. We also added a class describing the elements themselves and set to the id of their corresponding button. And we have the content class in each element.
JavaScript:
var player;
$(window).load(function(){
player = new YT.Player('player', {
height: '480',
width: '600',
videoId: 'gspaoaecNAg',
});
});
$(document).ready(function() {
var all = $('.content');
$('.button').click(function() {
all.addClass('hidden');
player.stopVideo();
$('.'+this.id).animate({
'left': '0px',
easing: 'swing'
}, 400, function(){
$(this).removeClass('hidden')
.removeAttr('style');
});
});
});
This has been optimized to avoid checking each element individually. The first part has been explained before, here is how the rest goes:
var all = $('.content');
This selects all the .content elements and keeps them referenced outside the callback in the variable all, so we only have to do this once when the document loads.
We create the callback on all button elements. The next step assumes a click event has been received.
We set all .content elements to hidden. Effectively this should only affect the one currently not hidden.
We stop the video. This will only affect the embedded iframe and we don't bother checking which .content element is active because stopping an already stopped video does nothing special.
Using the id of the button that triggered the click event, we select the corresponding .content element.
We replace show() with animate() and use it to modify the CSS property that is used in the class hidden. This will slide the element from its hidden position to it's normal position.
The animation has a callback executed when it's done. We use it to first remove the hidden class from our now visible element, then to remove the style attribute in which our animation has set left:0px;, as leaving this there would interfere later.
And we're done. This should now be smooth. A demo is available on this JSFiddle.

How can I hover multiple elements in order with using jquery?

I want to hover all div under .wrapper div in order with a delay when the page is loaded. How can I do this with using jquery?
HTML
<div class="wrapper">
<div class="first"></div>
<div class="second"></div>
<div class="third"></div>
</div>
Jquery
$('.wrapper').children().each(function(){
$(this).trigger('hover');
});
https://jsfiddle.net/drxvr1hn/
.trigger('hover') has been deprecated as it caused a great deal of maximum stack exceeded errors.
Deprecated in jQuery 1.8, removed in 1.9: The name "hover" used as a shorthand for the string "mouseenter mouseleave". It attaches a single event handler for those two events, and the handler must examine event.type to determine whether the event is mouseenter or mouseleave. Do not confuse the "hover" pseudo-event-name with the .hover() method, which accepts one or two functions.
Trying to trigger the hover state via jQuery is a very browser/cpu intensive process and a lot of re-rendering of a page to ensure that your call is correct. Therefore the ability was removed but is possible with some JS but will almost certainly cause speed issues and/or stack issues which can cause browser crashes.
A good alternative would be to use classes like below:
$(document).ready(function() {
$('.wrapper div').on('mouseover', function() {
$('.wrapper div').addClass('hover');
}).on('mouseleave', function() {
$('.wrapper div').removeClass('hover');
});
});
.wrapper > div {
width: 100%;
height: 20px;
margin-bottom: 20px;
}
.first {
background-color: #468966;
}
.second {
background-color: #FFF0A5;
}
.third {
background-color: #FFB03B;
}
.first.hover {
background-color: #B64926;
}
.second.hover {
background-color: #8E2800;
}
.third.hover {
background-color: #464A66;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class="wrapper">
<div class="first"></div>
<div class="second"></div>
<div class="third"></div>
</div>
you need to set the timeOut interval
$(window).scroll(function() {
$('. wrapper'). children().each(function(index){
var _this = this;
setTimeout( function(){ $(_this).trigger('hover'); }, 200*index);
});
});

Controlling CSS Float with JQuery

I am writing something similar to the JQuery UI accordion, but vertical. I have it working pretty well with one exception. When you click the third tab, it floats left and shows the required text as expected, but it moves to a position before the second tab. Making the tab order 132 rather than 123. In every other state the numbers are ok.
Any thoughts on making the float stop in the correct order
I am aware of other vertical accordions that could be used but js is one of my weaker areas, I'm doing this more for learning.
I have it saved on a jsfiddle
My Javascript Code
$(document).ready(function(){
$("#1").css("background-color","#191970");
$("#1").css("width", "50px");
$("#1").css("float", "left");
$("#2").css("background-color","#191970");
$("#2").css("width", "50px");
$("#2").css("float", "right");
$("#3").css("background-color","#191970");
$("#3").css("width", "50px");
$("#3").css("float", "right");
$("#boxmain").css("background-color", "#CCC");
$("#boxmain").css("width", "400px");
$("#boxmain").text($("#onet").text());
$('p').hide();
$("#1").click(function() {
$("#2").css("float", "right");
$("#3").css("float", "right");
$("#boxmain").effect("highlight", {color: '#DDD'}, 900);
$("#boxmain").text($("#onet").text());
});
$("#2").click(function() {
$("#2").css("float", "left");
$("#3").css("float", "right");
$("#boxmain").effect("highlight", {color: '#DDD'}, 900);
$("#boxmain").text($("#twot").text());
});
$("#3").click(function() {
$("#3").css("float", "left");
$("#2").css("float", "left");
$("#boxmain").effect("highlight", {color: '#DDD'}, 900);
$("#boxmain").text($("#threet").text());
});
});
I can help you simplify this quite a lot. There's a lot to read, but you can see it working at jsfiddle first if you like. You don't need to swap about the floats, you can just swap about the different containers.
First, some CSS:
.accordion {
height:200px;
float: left;
border:#fff solid 1px;
border-radius: 10px 10px 10px 10px;
color:white;
width: 50px;
background: #191970;
}
.boxMain {
width: 400px;
background: #CCC;
}
Then HTML- notice how I use the accordion class to tidy it up:
<div style="height:200px;width:558px;" id="box">
<div id="1" class="accordion">1</div>
<div id="boxmain" class="accordion boxMain"></div>
<div id="2" class="accordion">2</div>
<div id="3" class="accordion">3</div>
</div>
<p id="onet">Number One Text</p>
<p id="twot">Number Two Text</p>
<p id="threet">Number Three Text</p>
Now the script. I have removed all the CSS statements because it's done with CSS instead. I'll explain the .click() method afterwards.
$(document).ready(function(){
$("#boxmain").text($("#onet").text());
$('p').hide();
$("#1").click(function() {
$("#boxmain").insertAfter(this);
$("#boxmain").effect("highlight", {color: '#DDD'}, 900);
$("#boxmain").text($("#onet").text());
});
$("#2").click(function() {
$("#boxmain").insertAfter(this);
$("#boxmain").effect("highlight", {color: '#DDD'}, 900);
$("#boxmain").text($("#twot").text());
});
$("#3").click(function() {
$("#boxmain").insertAfter(this);
$("#boxmain").effect("highlight", {color: '#DDD'}, 900);
$("#boxmain").text($("#threet").text());
});
});
The click method uses the concept of "this" to refer to the element that click() is running on. In the case of $("#1").click() $(this) refers to #1. Instead of trying to shuffle floats around, you move the #boxmain element around instead.
Your divs are ordered that way in the markup. You won't be able to get the effect you're going for by changing float directions. Instead you can move your boxmain div around. Consider this code instead :
http://jsfiddle.net/Lanny/4snqy/18/

jQuery Slide Left Animation

I have four div's or td's and what I am trying to do is if I click on any one, then all of them slide left, the remaining three should disappear, and the one I clicked should remain.
I also want a neat animation effect.
I have tried this but it gives some error for style:
//xControl is a link inside the td
$(xControl).parent().siblings().each(function fn() {
setTimeout(function fn() {
$(this).animate({ width: 'toggle' });
}, 800);
});
I have also tried:
$(this).hide("slide", { direction: "right" }, 500);
This doesn't work either.
What am I doing wrong?
$(xControl).on("click", function(){
var thisDiv = $(this);
var sibs = thisDiv.siblings();
sibs.animate({'left':'-=150'},{queue:false,complete:function(){
$(this).animate({'opacity':0});
}
});
thisDiv.animate({'left':'-=150'},{queue:false});
});
This is more or less how I would do it. You can do whatever animations you want in there besides a slide and fade. You can use the jquery-ui built in functions, but I like animate personally for the more customizable animations.
Here's my fiddle to show what it looks like http://jsfiddle.net/AXkxQ/1/
Give this a try, might start you out:
http://jsfiddle.net/M8scf/2/
.item
{
width:50px;
height:50px;
margin:0 5px 0 0;
display:inline-block;
position:relative;
background:lime;
cursor:pointer;
}
<div class="con">
<div class="item">DIV 1</div>
<div class="item">DIV 2</div>
<div class="item">DIV 3</div>
<div class="item">DIV 4</div>
</div>
$(".con .item").click(function() {
$(this).siblings().hide("slow");
});​
You can try with:
$(xControl).parent().siblings().each(function fn() {
setTimeout(function fn() {
$(this).animate({ left: '-=500px' });
}, 800);
});
you have to make sure you have in your css the possition set as relative xControl{position: relative;}

Categories

Resources