deleted
deleted

Reputation: 782

Prevent triggering a scroll event by an animated scrollTop when using jQuery

I am working on a fullscreen scrolling script. It is supposed to scroll in fixed steps, to the previous or next element, each typically occupying the full height of the page. This is a good example.

I have a scroll event callback which contains an animated scrollTop, triggering the scroll event again and getting caught in a loop. I have tried a few things such as flags, but none seem to work for me.

Here's the code:

function pageDown() {
    // Some stuff, not important
    if (currentIndex+1 === pageCount) nextIndex = 0;
    else nextIndex = currentIndex+1;
    nextPage = $pages.eq(nextIndex);

    // Important stuff
    $('html body').animate({
        scrollTop: nextPage.offset().top
     }, 400, function() {preventScroll=false});
}

function pageUp() {
    // Some stuff, not important
    if (currentIndex === 0) nextIndex = pageCount-1;
    else nextIndex = currentIndex-1;
    nextPage = $pages.eq(nextIndex);

    // Important stuff
    $('html body').animate({
        scrollTop: nextPage.offset().top
     }, 400, function() {preventScroll=false});
}

var lastScroll = 0,
    preventScroll = false;
$(window).on('scroll', function() {
    var currentScroll = $(window).scrollTop();
    if(!preventScroll) {
        preventScroll = true;
        if (currentScroll > lastScroll) pageDown();
        else pageUp();
    }
    lastScroll = currentScroll;
});

Upvotes: 3

Views: 2899

Answers (1)

spenibus
spenibus

Reputation: 4409

The main issue I have witnessed when testing this out is that the complete callback of jQuery's animate fires before the final scroll event it generates. Note that seems to only happen when scrolling down for some reason.

After experimenting with a 2 steps lock, where I used a flag with 3 states to cancel that final scroll event, which worked fairly well, I explored further as it was less cooperative with the rollover logic that is present in your original code (jumping to the opposite end when reaching an end).

I came up with the following code, which records the target position to be reached and ignores all scroll events as long as the current position does not match the target.

This also implements the rollover logic and must be combined with the associated HTML and CSS to work properly, as we need some blank space (a single pixel on each side here) to allow for a scroll event to be fired at the top and bottom. We also initiate a first scroll as to correctly position the first element and allow the rollover to work immediately.

I hope the comments in the code will provide the additional information necessary to understand the logic being used.

A working demo is available in this jsfiddle

HTML:

<div class="pageScroller">
    <div class="bumper"></div>
    <div class="page" style="background-color:red;"></div>
    <div class="page" style="background-color:green;"></div>
    <div class="page" style="background-color:blue;"></div>
    <div class="page" style="background-color:violet;"></div>
    <div class="page" style="background-color:cyan;"></div>
    <div class="bumper"></div>
</div>

CSS:

html, body {
    height:100%;
    margin:0;
    padding:0;
}
.pages {
    padding:1px 0;
    background-color:yellow;
}
.pageScroller, .page {
    height:100%;
}
.bumper {
    height:1px;    
}

JavaScript:

var $pages = $('.page');

var currentIndex  = 0;

var lastScroll    = 0;
var currentScroll = 0;
var targetScroll  = 1; // must be set to the same value as the first scroll

function doScroll(newScroll) {

    $('html, body').animate({
        scrollTop: newScroll
    }, 400);   
}

$(window).on('scroll', function() {

    // get current position
    currentScroll = $(window).scrollTop();

    // passthrough
    if(targetScroll == -1) {
        // no target set, allow execution by doing nothing here
    }
    // still moving
    else if(currentScroll != targetScroll) {
        // target not reached, ignore this scroll event
        return;
    }
    // reached target
    else if(currentScroll == targetScroll) {
        // update comparator for scroll direction
        lastScroll = currentScroll;
        // enable passthrough
        targetScroll = -1;
        // ignore this scroll event
        return;
    }

    // get scroll direction
    var dirUp = currentScroll > lastScroll ? false : true;

    // update index
    currentIndex += (dirUp ? -1 : 1);

    // reached before start, jump to end
    if(currentIndex < 0) {
        currentIndex = $pages.length-1;
    }
    // reached after end, jump to start
    else if(currentIndex >= $pages.length) {
        currentIndex = 0;
    }    

    // get scroll position of target
    targetScroll = $pages.eq(currentIndex).offset().top;

    // scroll to target
    doScroll(targetScroll);   
});

// scroll to first element
$(window).scrollTop(1)

Upvotes: 3

Related Questions