Is there any way to make two jQuery animations run (properly) simultaneously? - javascript

I have an event listener that calls two animation actions. Unfortunately their starts are staggered by a small amount (e.g. the first in the function starts first).
Does anyone know a way to properly sync them up?
Here's my code:
$("#nav ul li a").hover(
function(){
$(lastBlock).children("div").animate({width: "0px"}, { queue:false, duration:400, easing:"swing" });
$(this).children("div").animate({width: maxWidth+"px"}, { queue:false, duration:400, easing:"swing"});
lastBlock = this;
}
);
Because the first animation runs slightly before the second, it causes the overall width to become momentarily unequal, which looks a bit funky.

There was a recent disussion about this exact topic on the jQuery dev list. They created a few test cases you might wanna look at. Specially the Johns test.
Here's the discussion topic btw.

The trick is to have a single interval/callback in which all elements are updated.
You can find an example in my post here:
Can I implement a callback with each animation step in jQuery?
What you end up is basically:
var el1 = $("#element1");
var el2 = $("#element2");
var animation = new AnimationTimeline( {
easing: "swing"
, onstep: function( stepValue, animprops )
{
// This is called for every animation frame. Set the elements:
el1.css( { left: ..., top: ... } );
el2.css( { left: ..., top: ... } );
}
});
// And start it.
animation.start();

Related

Rearranging js to accommodate more flexibility

How can I make this js affect only the child elements of the original hovered element without giving all of the individual .g_scroll or .left/.right tags id's?
function loopRight(){
$('.g_scroll').stop().animate({scrollLeft:'+=20'}, 'fast', 'linear', loopRight);
}
function loopLeft(){
$('.g_scroll').stop().animate({scrollLeft:'-=20'}, 'fast', 'linear', loopLeft);
}
function stop(){
$('.g_scroll').stop();
}
$('#right').hover(function () {
loopRight().children();
},function () {
stop();
});
$('#left').hover(function () {
loopLeft();
},function () {
stop();
});
JSfiddle for (confusing, but necessary) html structure: https://jsfiddle.net/6rbn18cL/
To demonstrate how it would have to be renamed: https://jsfiddle.net/z9u3azqy/
So here, I "merged" both arrow handlers.
Then, there is a calculation needed to determine the "scroll" speed, based on width to be scrolled, which may no always be 100% of the element's width.
This script allows you to easily determine a speed for 100% scrolling.
Then, it calculates the speed if there is already a distance scrolled.
$(document).ready(function(){
function moveit(arrow){
// Adjust you delay here
var delay = 2000; // delay to scroll 100%
var animationDelay;
var slider = arrow.siblings(".g_scroll");
var distance = slider.width();
var scrolled = slider.scrollLeft()+1; // +1 is to avoid infinity in the math below
if(arrow.hasClass("scroller_l")){
distance = -distance;
animationDelay = -distance * (-distance/delay)*(-distance+scrolled);
}else{
animationDelay = distance * (distance/delay)*(distance-scrolled);
}
slider.stop().animate({scrollLeft:distance}, animationDelay, 'linear');
}
function stop(arrow){
arrow.siblings(".g_scroll").stop();
}
$('.scroller_l, .scroller_r').hover(function(){
moveit($(this));
},function() {
stop($(this));
});
}); // ready
CodePen
--First answer--
First, you can't use the same id more than once.
So I removed id="left" and id="right" from your HTML.
Now the trick is to pass which arrow is hovered to your functions, using $(this).
And find the .g_scroll element which is a sibling of it.
$(document).ready(function(){
function loopRight(arrow){
arrow.siblings(".g_scroll").stop().animate({scrollLeft:'+=20'}, 'fast', 'linear', loopRight);
}
function loopLeft(arrow){
arrow.siblings(".g_scroll").stop().animate({scrollLeft:'-=20'}, 'fast', 'linear', loopLeft);
}
function stop(arrow){
arrow.siblings(".g_scroll").stop();
}
$('.scroller_r').hover(function(){
loopRight($(this));
},function() {
stop($(this));
});
$('.scroller_l').hover(function(){
loopLeft($(this));
},function() {
stop($(this));
});
});
CodePen
You can pass the event object and find the proper container from there.
$('.scroller_l').hover(loopRight, stop);
$('.scroller_r').hover(loopLeft, stop);
This is done automatically if you pass functions as parameters like the above.
To find the scrolling container dynamically for each instance you can use the classes to find the container relative to the current target:
var el = $(ev.currentTarget),
parent = el.closest('.country_holder'),
container = parent.find('.g_scroll');
See a working example here.
At this point you can ask yourself whether loopRight and loopLeft can be combined in one function. The only difference is the '-=20' and '+=20'.
With polymorphism you can refactor this even further.

How to not re-render the whole list in Mithril

I've used react for quite some time and wanted to try out Mithril.js.
Went through the documentation and the examples and liked what I saw, so I said I should get my hands dirty and start coding!
I have a smiple API call that receives a JSON data and then outputs ul list with all the items. I've integrated GSAP TweenMax for animations and what I'm trying to achieve is very simple - I fade everything in, on onload and then onclick I want to fade to fade an element out and remove it from DOM / data.
What seems to be happening is that the element is fading out, the whole ul list is being re-rendered and that element remains in the DOM with 0 opacity:
var Item = {
list: function() {
return m.request({method: 'GET', url: '/api/items'});
}
}
var dm = {
controller: function(data) {
var items = Item.list();
return {
items: items,
remove: function(item) {
items().data.splice(items().data.indexOf(item), 1);
}
}
},
view: function(ctrl) {
return m('ul', [
ctrl.items().data.map(function(item, id){
return m('li',{
key: id,
config: fadesIn,
onclick: fadeOut(ctrl.remove.bind(this, item))
}, item.title);
})
]);
}
}
var fadesIn = function(element){
var tl = new TimelineMax();
tl.from(element, .5, {opacity: 0});
}
var fadeOut = function(callback) {
return function(e) {
m.redraw.strategy('none');
TweenMax.to(e.target, .5, {opacity: 0, onComplete: function() {
m.startComputation();
callback();
m.endComputation();
}});
}
}
m.mount(document.getElementById('test'), dm);
I'm very new.. started reading up just yesterday.
Getting animation libraries to work with Mithril can be tricky. When libraries manipulate DOM state the synchronization with Mithril state can be broken.
Fortunately that wasn't the case: What you're missing is the isInitialized parameter for the config function, which is false only on the first call. Testing for that makes the fade-in only happen once:
var fadesIn = function(element, isInit){
if(isInit) return;
var tl = new TimelineMax();
tl.from(element, .5, {opacity: 0});
}
In this simple example the redrawing can also be simplified, I've made a fiddle with a working example:
http://jsfiddle.net/ciscoheat/dkyc0ryc/
Since there are no DOM manipulations a call to m.redraw is enough to remove the div from DOM, but you're probably right in using start/endComputation when things get more complex. I would even move m.startComputation above the TweenMax.to call to make it extra safe, but if many other things are happening at the same time, that may block other redraws. You have to find a balance. :)
The call to m.redraw.strategy isn't needed in any case, I think. It's mostly used when you want nothing at all to happen (synchronously as well), but an async animation is starting so it won't have any effect.
Edit: Found another problem, the key cannot be set to the index of the map function, then it will change when an item is removed, messing up the redraw. I've updated the fiddle to use item.title as key instead.

Does jquery trigger slidedown animation on an element that is already slidedown?

Well I hope the question is self-explanatory. I have the following code:
$(window).scroll(function () {
var scrollTop = $(window).scrollTop();
if (scrollTop > sliderTop) {
$('#slider').slideDown(1000);
} else {
$('#slider').slideUp(1000);
}
});
If I scroll down after a certain point the slideDown function will be called continuously. does JQuery animates it over and over again or is it smart enough to know that the element is already slide down? (currently I'm using a flag to check whether its already slide down).
The animation won't run again. See lines 459-468 in effect.js in the jquery src
doAnimation = function() {
// Operate on a copy of prop so per-property easing won't be lost
var anim = Animation( this, jQuery.extend( {}, prop ), optall );
// Empty animations, or finishing resolves immediately
if ( empty || data_priv.get( this, "finish" ) ) {
anim.stop( true ); // ** here - stopping right after started.
}
};
doAnimation.finish = doAnimation;
But I would still recommend against it to improve code readability. just use a boolean, it doesn't hurt...

jQuery animate within animate callback works only once

I have two pagination links which trigger a jQuery animation.
A callback function on the animation triggers a second animation.
This works fine, however, it only works the first time the function is called.
Each time after, the first of the 2 animations works and the second one does not, it merely changes the CSS without the effect.
I'm racking my brain here, to no effect (pun intended).
function switchItem(e) {
e.preventDefault();
// If not current piece
if($(this).hasClass('light')) {
/* VARIABLES */
var container = $('.portfolio footer .portfolio_content');
var link = $(this).attr('id');
var newItem = $(this).html();
/*===================================================
* This is the Key part here
===================================================*/
/* FIRST ANIMATION */
container.animate({
'right':'-100%'
}, 300, 'swing',
// Callback Function
function() {
$(this).html('').css({'left':'-100%'});
$.get('inc/portfolio/'+link+'.php', function(data) {
container.html(data);
/* SECOND ANIMATION */
container.animate({
'left':'0%'
}, 300, 'swing');
});
});
}
}
Here is the demonstration: http://eoghanoloughlin.com/my_site/#portfolio
See working sample here, your left is conflicting with your first right -100% animation and at the end if you don't reset the right then it will conflict with your second left animation
http://jsfiddle.net/PAdr3/2/
reset left before animating
container.css('left', 'auto');
and reset right when complete
container.animate({
'left':'0%'
}, 300, 'swing', function() {
$(this).css('right', 'auto');
});
When you replace the html in a page with new content, it will not automatically add listeners or other jquery enhancements that are added via javascript.
I suspect you need to apply these again after you put the content you fetched with $.get into the page.
Another alternative, is to add all the content in one load, but make the second page hidden. This is probably a nicer alternative than using $.get
I think that the animate function does not string until the first animate is ready. The docs say:
If supplied, the complete callback function is fired once the
animation is complete. This can be useful for stringing different
animations together in sequence.
Something like this might work:
function switchItem(e) {
e.preventDefault();
// If not current piece
if($(this).hasClass('light')) {
/* VARIABLES */
var container = $('.portfolio footer .portfolio_content');
var link = $(this).attr('id');
var newItem = $(this).html();
/*===================================================
* This is the Key part here
===================================================*/
/* FIRST ANIMATION */
container.animate({
'right':'-100%'
}, 300, 'swing',
// Callback Function
function() {
$(this).html('').css({'left':'-100%'});
$.get('inc/portfolio/'+link+'.php', function(data) {
container.html(data);
});
}).complete(
/* SECOND ANIMATION */
container.animate({
'left':'0%'
}, 300, 'swing');
);
}
}

JQuery shaking effect with some tilt/rotation

I am trying to create an effect where on button click, the div would shake left and right and tilt about 10deg each way so it looks like a natural motion when shaking an object with two hands. I am able to create the left and right shaking effect but can't seem to tie it in with rotation. I also need this to work in IE8, so css3 is not an option. I am using JQuery UI and .rotate() but if there is a better way please let me know. I need this to shake about 3-4 times on button click.
<div class="container">
<div class="globe-main" id="globe">
<div class="content"></div><!-- end .content -->
</div><!-- end .globe-main -->
</div><!-- end .container -->
<script>
var times = 4;
var loop = setInterval(rota, 300);
function rota() {
times--;
if(times === 0){clearInterval(loop);}
$(".globe-main").rotate({animateTo:10, duration: 500, });
//$(".globe-main").effect("shake", { times:3, distance:30 }, 800);
}
rota();
</script>
Here is what I have so far FIDDLE
Thank you
UPDATE
Here is the updated FIDDLE
jQuery has no method called rotate. This is probably where the problem lies.
Edit
Based on your comments, you could create your own queue I guess...
rotationAnimationQueue = {
queue: [],
addAnimation: function( $jQueryObject, params ) {
// add animation to the queue
this.queue.push( {
$jQueryObject: $jQueryObject,
params: params
} );
// if only animation in queue, begin processing
if ( this.queue.length === 1 ) this.processQueue();
},
processQueue: function() {
var self = this;
var animation = this.queue[ 0 ];
animation.params.callback = function() {
self.queue.shift();
if ( self.queue.length > 0 ) self.processQueue();
};
animation.$jQueryObject.rotate( animation.params );
}
};
See it in action here: http://jsfiddle.net/UnyYh/1/
You may want to modify the queue code so that it keeps track of one queue per jQuery object. This way if needed you could have shake animations happening on multiple objects at the same time instead of always doing the animations in sequence.

Categories

Resources