Passing optional callback functions with parameters - javascript

I'm trying in different ways to create a small scrolling method which can take an optional effect callback function. Imagine I can run scrollToEl(el, flash) which would first scroll down to the element and the flash it. How would I normally go about this?
This is what I've done..but it's not really working.
scrollToEl : function(el, callback) {
// flash the selected product
var self = this;
$('html, body').animate({
scrollTop: el.offset().top - 50
}, 1000, 'swing', callback); // how to pass el here as well paired with callback?
}
flash : function(el) {
// flash the selected product
el.animate({
opacity : 0.4
}, 100, 'swing', function() {
el.animate({
opacity : 1
}, 1000, 'swing');
});
},
I want to use it like this:
var el = $('#element');
scrollToEl(el, flash); // how to pass in to the callback function flash?

You can use a closure:
scrollToEl : function(el, callback) {
// flash the selected product
var self = this;
// Get completion callback if any
var completion;
if (callback) {
completion = function() {
callback(el); // See below if you prefer `el` to be `this` in the callback
};
}
else {
completion = $.noop;
}
$('html, body').animate({
scrollTop: el.offset().top - 50
}, 1000, 'swing', completion);
}
More about closures: Closures are not complicated
If you want the callback to receive the element as this, you can use jQuery.proxy instead of your own wrapper function:
scrollToEl : function(el, callback) {
// flash the selected product
var self = this;
$('html, body').animate({
scrollTop: el.offset().top - 50
}, 1000, 'swing', callback ? $.proxy(callback, el) : $.noop);
}
It comes to the same thing, because proxy creates a function. But it doesn't introduce a closure over the context of the call to scrollToEl.

It would be more normal for a callback to have the affected element as this rather than as a parameter:
flash : function() {
// flash the selected product
$(this).animate({
opacity : 0.4
}, 100, 'swing', function() {
$(this).animate({
opacity : 1
}, 1000, 'swing');
});
}
and then use a closure that uses .call or .apply to bind el to this when it's invoked:
$('html, body').animate({
scrollTop: el.offset().top - 50
}, 1000, 'swing', callback ? function() {
callback.call(el);
} : undefined);

Related

Uncaught TypeError: Cannot read property 'top' of undefined (jquery)

scrollTo: function (target, callback) {
var offset = $(target).offset().top - this.config.navHeight;
$('html, body').animate({
scrollTop: offset
}, this.config.scrollSpeed, this.config.easing, callback);
},
unbindInterval: function () {
clearInterval(this.t);
this.$win.unbind('scroll.onePageNav');
}
};
OnePageNav.defaults = OnePageNav.prototype.defaults;
$.fn.onePageNav = function (options) {
return this.each(function () {
new OnePageNav(this, options).init();
});
};
})(jQuery, window, document);
I get this error when I'm scrolling down each time Developer Console runs.
(In my website all js are minified and combined to one file.)
The problem is The Classes and Shows elements do not have a href target, its value is just #, so jQuery is not able to find any result for $('#') that is why t(i).offset() is returning undefined.
The solution could be is to check whether the target element exists in the scrollTo method like
scrollTo: function (target, callback) {
var $target = $(target);
if (!$target.length) {
return;
}
var offset = $target.offset().top - this.config.navHeight;
$('html, body').animate({
scrollTop: offset
}, this.config.scrollSpeed, this.config.easing, callback);
}

Not repeating the function once waypoint reached

Here is the JS for the waypoint call and graph bar function. It repeats every time the waypoint is reached and I would like it to recognise that the waypoint has been reached once already ad not to repeat function. Thanks for your help. :)
$.getScript('http://imakewebthings.com/jquery-waypoints/waypoints.min.js', function() {
$('#jack_bellamy').waypoint(function() {
setTimeout(function start (){
$('.bar').each(function(i)
{
var $bar = $(this);
$(this).append('<span class="count"></span>')
setTimeout(function(){
$bar.css('width', $bar.attr('data-percent'));
}, i*100);
});
$('.count').each(function () {
$(this).prop('Counter',0).animate({
Counter: $(this).parent('.bar').attr('data-percent')
}, {
duration: 2000,
easing: 'swing',
step: function (now) {
$(this).text(Math.ceil(now) +'%');
}
});
});
}, 500)
});
});
If you don't want a waypoint to keep triggering you can destroy it. To ensure it only runs once, you can destroy it at the end of your handler. The this keyword refers to the waypoint instance you can call destroy on within the handler.
$('#jack_bellamy').waypoint(function() {
// all that animation stuff you mentioned
this.destroy();
});

Onclick is not working

Why will this code not work as an onclick ?
$('.mainz11').click (function() {
$(this).animate({
height: '280px'
}, 800);
}, function() {
$(this).animate({
height: '100px'
}, 800);
});
If you're trying to first expand the element and then contract it, it should probably be something like this:
$('.mainz11').click(function() {
// determine target heights
if ($(this).hasClass("expanded")) {
var targetHeight = 100;
} else {
var targetHeight = 280;
}
// animate
$(this).animate({
height: targetHeight
}, {
duration: 800,
complete: function() { $(this).toggleClass("expanded"); }
});
});
This could use some cleaning up, but it does the trick, and you can track expanded items easily this way.
See here: http://jsfiddle.net/mpQek/3/
The click function takes only a single function but you are passing 2 functions to it. You can try it this way:
$('.mainz11').click (function() {
$(this).animate({
height: '280px'
}, 800);
});
If you want to chain animations, put the next animation as the function to run on complete of the first animation:
http://api.jquery.com/animate/
$('.mainz11').click (function() {
$(this).animate({ height: '280px', 800,
function() { $('.mainz11').animate({ height: '100px'}, 800)
);
});

jQuery animation callback doesn't work

Why doesn't it fire the alert?
var $anchor = $(this);
$('.hide').val($(this).attr('href'));
$('html, body').animate({
scrollLeft: $($anchor.attr('href')).offset().left
}, {
queue: false,
duration: 1000,
easing: 'easeInOutCirc'
}, function () {
alert('test');
});
There are multiple different syntax options you can use with .animate(). When you pass a properties object and an options object (like you are doing), the completion function goes in the options object not as the third parameter like this:
var $anchor = $(this);
$('.hide').val($(this).attr('href'));
$('html, body').animate({
scrollLeft: $($anchor.attr('href')).offset().left
}, {
queue: false,
duration: 1000,
easing: 'easeInOutCirc',
complete: function () {
alert('test');
}
}
);
This is described in full in the jQuery .animate() doc.
.animate( properties, options )
properties - A map of CSS properties that the animation will move toward.
options - A map of additional options to pass to the method. Supported keys:
duration: A string or number determining how long the animation will run.
easing: A string indicating which easing function to use for the transition.
complete: A function to call once the animation is complete.
step: A function to be called after each step of the animation.
queue: A Boolean indicating whether to place the animation in the effects queue. If false, the animation will begin immediately. As of jQuery 1.7, the queue option can also accept a string, in which case the animation is added to the queue represented by that string.
specialEasing: A map of one or more of the CSS properties defined by the properties argument and their corresponding easing functions (added 1.4).
try to specify the third parameter as "complete" like so:
var $anchor = $(this);
$('.hide').val($(this).attr('href'));
$('html, body').animate({
scrollLeft: $($anchor.attr('href')).offset().left
}, {
queue: false,
duration: 1000,
easing: 'easeInOutCirc'
}, complete: function () {
alert('test');
});

A non-nested animation sequence in jQuery?

I'm trying to create an animation sequence with jQuery where one animation starts after the previous one is done. But I just can't wrap my head around it. I've tried to make use of the jQuery.queue, but I don't think I can use that because it seems to have one individual queue for each element in the jQuery array.
I need something like:
$('li.some').each(function(){
// Add to queue
$(this).animate({ width: '+=100' }, 'fast', function(){
// Remove from queue
// Start next animation
});
});
Is there a jQuery way to do this or do I have to write and handle my own queue manually?
You can make a custom .queue() to avoid the limitless nesting..
var q = $({});
function animToQueue(theQueue, selector, animationprops) {
theQueue.queue(function(next) {
$(selector).animate(animationprops, next);
});
}
// usage
animToQueue(q, '#first', {width: '+=100'});
animToQueue(q, '#second', {height: '+=100'});
animToQueue(q, '#second', {width: '-=50'});
animToQueue(q, '#first', {height: '-=50'});
Demo at http://jsfiddle.net/gaby/qDbRm/2/
If, on the other hand, you want to perform the same animation for a multitude of elements one after the other then you can use their index to .delay() each element's animation for the duration of all the previous ones..
$('li.some').each(function(idx){
var duration = 500;
$(this).delay(duration*idx).animate({ width: '+=100' }, duration);
});
Demo at http://jsfiddle.net/gaby/qDbRm/3/
The callback of .animate() actually accepts another .animate(), so all you would have to do would be
$(this).animate({ width: '+=100' }, 'fast', function(){
$(selector).animate({attr: val}, 'speed', function(){
});
});
and so on.
You could call the next one recursively.
function animate(item) {
var elem = $('li.some').eq(item);
if(elem.length) {
elem.animate({ width: '+=100' }, 'fast', function() {
animate(item + 1);
});
}
}
animate(0);
why not build up a queue?
var interval = 0; //time for each animation
var speed = 200;
$('li.some').each(function(){
interval++;
$(this).delay(interval * speed).animate({ width: '+=100' }, speed);
});
EDIT: added speed param
Thanks to everybody replying!
I thought I should share the outcome of my question. Here is a simple jQuery slideDownAll plugin that slides down one item at a time rather than all at once.
(function ($) {
'use strict';
$.fn.slideDownAll = function (duration, callback) {
var that = this, size = this.length, animationQueue = $({});
var addToAnimationQueue = function (element, duration, easing, callback) {
animationQueue.queue(function (next) {
$(element).slideDown(duration, easing, function () {
if (typeof callback === 'function') {
callback.call(this);
}
next();
});
});
};
return this.each(function (index) {
var complete = null,
easing = 'linear';
if (index + 1 === size) {
complete = callback;
easing = 'swing';
}
addToAnimationQueue(this, duration / size, easing, complete);
});
};
} (jQuery));
Not very well test, but anyways.
Enjoy!!

Categories