Reputation: 227240
I am trying to use jQuery's .animate
to scroll to an element on a page, and then execute a callback.
After searching around, I found this function:
function scrollToElement(selector, callback){
var animation = {scrollTop: $(selector).offset().top};
$('html,body').animate(animation, 'slow', 'swing', callback);
}
This correctly scrolls to the element defined by 'selector', but callback is called twice (because $('html,body')
contains 2 elements).
I tried changing
$('html,body').animate
to:
$(document).animate
and:
$(window).animate
but, neither of those do anything.
I also tried changing the function to this:
$('html').animate(animation, 'slow', 'swing', function(){
$('body').animate(animation, 'slow', 'swing', callback);
});
but, this made the browser run the 1st animation and then the 2nd, so I had wait for both to run before the callback was ran (I dont't want that).
I figured out that $('body').scrollTop()
only works in Chrome, and $('html').scrollTop()
only works in Firefox.
So, is there a way (without needing to download a jQuery plugin) for me to scroll to a specific element in both Chrome and Firefox (I don't care about IE), and have a callback executed (once)?
EDIT:
I made a crude fix by making a boolean to check if the callback ran already, and if it was, don't run it again.
function scrollToElement(selector, callback){
var animation = {scrollTop: $(selector).offset().top};
var callback_running = false;
$('html,body').animate(animation, 'slow', 'swing', function(){
if(typeof callback == 'function' && !callback_running){
callback_running = true;
callback();
}
});
}
Upvotes: 3
Views: 5236
Reputation: 786
You should avoid animating both html and body elements. Your page animation will work correctly on every modern or old browser and the callback will run once (as it should) by the addition of a simple condition in your function.
function scrollToElement(selector, callback){
var scrollElem='html';
//animate body for webkit browsers that don't support html animation
if($.browser.webkit){
scrollElem='body';
}
var animation = {scrollTop: $(selector).offset().top};
$(scrollElem).animate(animation, 'slow', 'swing', callback);
}
Only webkit doesn't support "html" animation, so you change the "scrollElem" variable accordingly. In addition, scrolling a single element (html or body) works much better on older browsers (e.g. previous versions of Opera).
Upvotes: 0
Reputation: 4331
how about using a DIV that stretches over the full document body and do the animation on that DIV instead? you can't be sure how many more browser issues you can find otherwise (i.e. how many more browsers would not animate HTML nor BODY for instance)
Upvotes: 0
Reputation: 12197
I think this should work too
function scrollToElement(selector, callback){
var animation = {scrollTop: $(selector).offset().top};
$('html,body').animate(animation, 'slow', 'swing', function() {
if (typeof callback == 'function') {
callback();
}
callback = null;
});
}
Upvotes: 3
Reputation: 237855
If you are using jQuery 1.5 (or can upgrade to it), you can use the new $.Deferred
syntax.
$.fn.scrollToElement = function(selector, callback) {
var def = new $.Deferred(),
el = this;
$('html, body').animate({scrollTop: $(selector).offset().top}, 'slow', 'swing', def.resolve);
if (callback) {
def.promise().done(function(){
callback.call(el);
});
}
};
$('html, body').scrollToElement('#foo', function() {
alert('done scrolling');
});
Because a deferred object can only be resolved once, you can't have more than one call to the callback.
Upvotes: 1