jQuery scroll event fires twice - javascript

I'm working on making an infinite scroll function for a page and am running into an issue where it appears that the $( window ).scroll() function is firing twice. I've seen similar questions on stackoverflow, but they seem to be related to fast scrolls and not having something in place to check weather the request has already been sent. Here's the code that I'm working with right now, I imagine that its something easy that I'm missing here.
var loading = false;
var scrollcount = 0;
$( window ).scroll( function(){
scrollcount++;
var scrolling = $( document ).scrollTop();
var position = $( "#ajax-load" ).offset();
if( scrolling + $( window ).height() > position.top - 200 && loading == false ){
console.log( loading ); //returns 2 falses
console.log( scrollcount ); //returns 2 of the same number
console.log( $( document ).scrollTop() ); //returns same number
loading = true;
$( "#ajax-load" ).css( "display", "block" );
$.get( "url/to/my/script", info )
.done( function( data ){
//here's where the items are appended to the document
setTimeout( function(){
loading = false;
}, 5000 );
});
}
});
The face that when I log out the scrollcount variable and the return from scrollTop() I get the same number twice seems to tell me that the event is actually firing twice at the same time for some reason. It seems that if it were firing twice, one after the other, the loading variable would be set to false and not fire that second time. Like I said, I imagine that it's something super simple that I'm missing. Thanks for your help!

My first guess is you have the event listener applied twice, wherever this code is.
Try adding $(window).unbind('scroll'); before $(window).scroll
The loading variable would be false for when it fires twice because false is set on a timeout, after an async call

var timeout;
$(window).scroll(function() {
clearTimeout(timeout);
timeout = setTimeout(function() {
// do your stuff
}, 50);
});
Use this code.

I tinkered with this and tried some of the above solutions. None work 100% with major browsers for my use case (which was a simple scroll loader). To avoid using setTimeout and ONLY execute a specific function within a set duration, create this function object literal:
var hlpr = {
lastExec: new Date().getTime(),
isThrottled: function (timeout) {
if ((new Date().getTime() - this.lastExec) < timeout) {
console.log('returned');
return false;
}
this.lastExec = new Date().getTime();
return true;
}
};
And then add this to the function you have bound to your scroll event:
if (!hlpr.isThrottled(500))
return;
This way, the scrollbar event can fire off as weirdly as it wants but your attached function will never execute twice within a certain interval. The double-firing of the scroll event happens much faster than 500ms but since my usage was for a scroll loader, all I needed to ensure was that the function fired once within a 500ms window.

Related

Execute a second .click() event after the first is done

I would like to "chain" two .click() calls but cannot get it to work.
The code does work when I debug the JS code in my browser (so with a delay it seems to work)
I somehow need the first .click() to load the page (that's what the first event does) and only if that is done, I want the second .click() to execute.
My Code:
$.post("settings?type=mail&nr=" + nr, function(data){
if(data != ""){
alert(unescape(data));
// First click event -> realoads the page
$("#change_settings").click();
// Second click event -> navigates to a tab
// inside the page loaded by the first click event
$("#tab_mail_header" + nr + "").click();
}
});
EDIT: More Code
function load_change_settings_view(e){
e.preventDefault();
$("#content").empty();
// load settings.jsp in a div (test) inside the mainpage
$("#content").load("settings.jsp #test", function(){
// In here are a couple of .click() & .submit() functions but nothing else
});
});
$("#change_settings").click(function(e){
load_change_settings_view(e);
});
EDIT: I currently have this code:
$("#change_settings").click();
window.setTimeout(function () {
$("#tab_mail_header" + nr + "").click();
}, 1000);
I dont really like it though, as it is a timed delay and it may be the case (on a slow client) that that 1 second timeout will not be enough. I don't want to set the timeout too high as this slows down the workflow for users with a faster client...
I looked though a couple of post like these:
JQuery .done on a click event
Wait for a user event
How to wait till click inside function?
Wait for click event to complete
Does anyone have an idea on how to get this to work?
after a few more attempts I ended up with the following solution:
Code Snippet#1
$.post("settings?type=mail&nr=" + nr, function(data){
if(data != ""){
alert(unescape(data));
// create event object
var evt = document.createEvent('MouseEvents');
evt.initEvent('click', true, false);
// call method manually (not called by actual button click like its supposed to be)
// - pass event object
// - additional parameter to specify the tab the user is viewing
load_change_settings_view(evt, "tab_mail_header" + nr);
}
});
Code Snippet#2
function load_change_settings_view(e, p_tab){
e.preventDefault();
$("#content").empty();
// load settings.jsp in a div (test) inside the mainpage
$("#content").load("settings.jsp #test", function(){
// Go to previous tab (if one was selected)
var prev_tab = p_tab;
if(typeof prev_tab != 'undefined'){
$("#" + prev_tab).click();
}
// In here are a couple of .click() & .submit() functions but nothing else
});
});
feel free to comment if you have a better idea on how to solve this problem or if you have any other suggestions

JS delaying page load even though it's in document.ready

I have an element that I want to have appear on my page after the user has been there for a few seconds. To achieve this, I used a sleep function and put it inside $(document).ready(). The idea is that the page will load and then the sleep will start. However what I'm seeing is that the sleep function is actually delaying the page load. Any idea what's wrong?
function sleep(miliseconds) {
var currentTime = new Date().getTime();
while (currentTime + miliseconds >= new Date().getTime()) {
}
}
$(document).ready(function(){
if ( $( ".email_tab" ).length ) {
sleep(8000);
$(".email_tab").toggleClass("email_tab_hide").toggleClass("email_tab_appear");
}
});
The sleep function consumes your browser cpus and freezes it.
What you want is:
$(document).ready(function(){
if ( $( ".email_tab" ).length ) {
setTimeout(function() {
$(".email_tab").toggleClass("email_tab_hide").toggleClass("email_tab_appear");
}, 8000);
}
});
Your problem may be that when the document is ready, and email_tab length is zero, the setTimeout won't run.
In JS you should use setTimeout(); instead custom sleep() function
setTimeout(function(){
$(".email_tab").toggleClass("email_tab_hide").toggleClass("email_tab_appear");
}, 8000)
The $(document).ready callback actually runs before the page is fully loaded. It just ensures that the whole DOM is available for manipulating. Your sleep busy-loop then delays the rest of the loading process. You should use setTimeout instead.

Timeout in Jquery to update the Dom?

Let's say I want to change the image source onClick and do some calculations. Whats happening right now is that the source changes when the the function exits. (Dom is busy?)
How can I make it so that the image source updates right away and then the function proceeds to the while loop?
HTML:
<img src="http://goo.gl/l55G2P" id="ImageSrc">
JS:
$( "#ImageSrc" ).click(function() {
new_imgsrc="http://goo.gl/wBhyee";
$("#ImageSrc").attr('src',new_imgsrc);
test = 0
do {
test = test + 1;
console.log(test);
} while (test != 50000);
});
Here's a JSFiddle.
Not sure I understand you correctly but you could use a timeout:
setTimeout(function() {
//while loop here
}, 1000);
Where 1000 means 1 second. You could shrink this value so it doesn't wait as long.
As click() has no callback for onComplete I am not sure of a better way to achieve this.
This problem is actually a bit trickier than just setting a timeout, if you want to do it right.
The problem with setting is a timeout is that the timeout has to be large enough for the image to load. If the image isn't loaded within the timeout, it'll still have to wait for the loop.
What you have to do instead is actually give it a chance to update the DOM in between each step. To do this, you need to set up a timeout (or interval, but I prefer the timeout method since you have better control) that triggers every 0ms (basically, as fast as possible). However, since these are all timeouts, it has a chance to update the DOM in between two of these when it is ready.
console.clear();
$( "#ImageSrc" ).click(function() {
loading_imgsrc="http://goo.gl/wBhyee";
$("#ImageSrc").attr('src',loading_imgsrc);
console.log("changed");
doTest(0);
});
function doTest(test) {
test = test + 1;
console.log(test);
if (test < 1000) {
setTimeout((function() { return function() { doTest(test); }})(), 0);
}
}
JSFiddle: http://jsfiddle.net/E3zvL/4/

How to use this function in jQuery

I have the following jQuery function which is supposed to detect when an element first scrolls into view on a page:
function isScrolledIntoView(elem) {
var docViewTop = $(window).scrollTop();
var docViewBottom = docViewTop + $(window).height();
var elemTop = $(elem).offset().top;
var elemBottom = elemTop + $(elem).height();
return ((elemBottom <= docViewBottom) && (elemTop >= docViewTop));
}
My mind is mush today... how can I use this function?
Assume I want to detect when $('.guardrail360') comes into view.
Seems like you need to bind the scroll event and do the detection when that event fires.
$(window).scroll(function () {
isScrolledIntoView('.guardrail360');
});
There are several ways to do it. Just don't use a naive window.scroll implementation if you are concerned with user experience and/or performance.
It's a very, very, bad idea to attach handlers to the window scroll
event. Depending upon the browser the scroll event can fire a lot and
putting code in the scroll callback will slow down any attempts to
scroll the page (not a good idea). Any performance degradation in the
scroll handler(s) as a result will only compound the performance of
scrolling overall. Instead it's much better to use some form of a
timer to check every X milliseconds OR to attach a scroll event and
only run your code after a delay (or even after a given number of
executions - and then a delay).
-John Resig, creator of jQuery, on ejohn.org
Method 1: setInterval
First, there is the naive approach using timers:
var $el = $('.guardrail360');
var timer = setInterval(function() {
if (isScrolledIntoView($el)) {
// do stuff
// to run this block only once, simply uncomment the next line:
//clearInterval(timer);
}
}, 150);
Method 2: window.scroll event
Then there is the trivial, yet crazy inefficient way using the scroll event (depending on the browser, the scroll event will fire hundreds of times per second, so you do NOT want to run a lot of code in here, particularly not code that triggers browser reflows/redraws).
Ever visited a site where scrolling down the page felt sluggish and jittery? That is often caused by a piece of code like this one right here:
var $el = $('.guardrail360');
$(window).on('scroll', function() {
if (isScrolledIntoView($el)) {
// do stuff
}
});
Method 3: best of both worlds
The nifty hybrid approach for high traffic sites, as proposed by John Resig in the aforementioned blog post:
var $el = $('.guardrail360'),
didScroll = false;
$(window).scroll(function() {
didScroll = true;
});
var timer = setInterval(function() {
if ( didScroll ) {
didScroll = false;
if (isScrolledIntoView($el)) {
// do stuff
// to run this block only once, simply uncomment the next line:
//clearInterval(timer);
}
}
}, 250);
Method 4: Throttling / Debouncing
Throttling (minimum N milliseconds between invocations) or debouncing (only one invocation per 'burst') patterns can also be used to efficiently limit the rate at which your inner code executes. Assuming you'd be using Ben Alman's jQuery throttle/debounce plugin, the code looks like this:
var $el = $('.guardrail360');
// Throttling
$(window).on('scroll', $.throttle( 250, function(){
if (isScrolledIntoView($el)) {
// do stuff
}
}));
// Debouncing
$(window).on('scroll', $.debounce( 250, function(){
if (isScrolledIntoView($el)) {
// do stuff
}
}));
(Note that debouncing acts slightly differently from the other implementations, but that can sometimes be what you want, depending on your user experience scenario)
if ( isScrolledIntoView('.guardrail360') ) {
}
As suggested by the other answers, you should use act upon $(window).scroll(). In addition, in order to make the if statement run the first time the element is scrolled into view, I created this run_once function for you:
$('window').on('scroll', function() {
if ( isScrolledIntoView('.guardrail360') ) run_once(function() {
// ...
});
});
function run_once( callback ) {
var done = false;
return function() {
if ( !done ) {
done = true;
return callback.apply( this, arguments );
}
};
}

Restarting a setInterval() in Javascript/jQuery (without clearInterval)

I'm working on ui tabs built using jQuery. Everything works except for one issue - I did a setInterval that runs a function that does a trigger("click") so that it goes to the next tab after 5000 miliseconds. It runs through each tab fine, the issue is that if the user manually clicks on a tab, the timer for the setInterval does not restart back at 0. For example if a user were to start on tab1 at 0 miliseconds and clicks on tab2 at 2000 miliseconds, the setInterval doesn't go back to 0, it would start at 2000 and run to 5000 miliseconds and would subsequently goto tab3. I understand why it's happening, I just wonder if there were a way to restart the setInterval timing without having to do a clearInterval() and creating an entirely new setInterval(). Any insight would be appreciated.
Update
Thanks for the replies - The reason I was trying to avoid using clearInterval was because I was having issues of how to write the code in a way where the clearInterval would stop the setInterval completely. The code is setup to track whenever a user has clicked a tab. The problem is the auto change function utilizes trigger('click'), so it runs the clearInterval function I wrote also when the tabs auto-change. It seems to run fairly fine on its own, but once the user starts clicking on tabs, the setInterval behaves unusually and switches tabs unpredictably. I suspect what is happening is that several setIntervals are running at once... Here's the code (If you haven't guessed it already, I'm pretty new at javascript/jquery). I've commented out parts so that it's functional, but it still doesn't function as I intended (from first post).
// auto change tabs
if( options.interval ) {
function timerCom() {
if( !$(".controller").hasClass('paused') ) {
var i = $(".tab-current > a").attr("rel");
//alert(i);
if( i == 3 ) {i = 0};
$container
.find('a')
.eq(i)
.trigger('click');
}
}
//$("#promo-items > li > a").click(function () {
//var timer;
//if( timer != null ) {clearInterval(timer);}
timer = setInterval(timerCom, options.interval);
//});
}
No, there is no way to restart a timer set by setInterval without clearing the timer.
You can't really alter intervals or timeouts, only clear them. That said it should be a simple thing to create a function that clears the interval, and then starts a new but identical one immediately with a fresh time value.
var intervalID;
var resetTimer = function() {
if (intervalID) { clearInterval(intervalID) };
intervalID = setInterval(function() {
console.log('doing stuff!');
}, 5000);
};
timer = setInterval(function() {
timerCom();
}, options.interval);
I know this post is well over 2 years old, but I ran into a similar problem just now, and I found a solution.
I was writing an image scroller that would automatically shift to the next image after a set amount of time, and whenever I clicked the navigation buttons, the transitions moved double-time.
Here's my solution:
Make the interval variable (timer in your case) somewhat global.
i.e. in the options section (assuming it was defined earlier, and then later assigned), add a null timer variable.
var options = {
'interval',
//Other variables
'timer',
};
Then, call clearInterval twice when you handle the click event.
$("#promo-items > li > a").click(function () {
if( options.timer != null ) {
clearInterval(options.timer);
clearInterval(options.timer);
}
options.timer = setInterval(timerCom, options.interval);
});
Worked like a charm for me.
Again, sorry if this is wayyyy too late.

Categories