3

I've got a container, with multiple <div>s inside, representing pages. As a user scrolls through these pages, I want to update the current page number label. To achieve this, I found I can use the :in-viewport selector. I tried following this example, and came up with this jsFiddle:

http://jsfiddle.net/j2aet/

This seems to work, but I do have a minor problem with it. And that is as I scroll down, my current page number doesn't change to "2" until the 2nd "image" element is partially hidden (on the top), and page 3 is partially visible. I'd like it to change to 2 sooner. See this screenshot for what I mean, this is when it changes to "2":

http://imgur.com/l40ddBs

Ideally, I'd like the current page number to change to "2" as soon as the 2nd "image" element is the element with a majority in view. For example, this is about the position I'd like the current page number to change to "2":

http://imgur.com/uKZRD4m (notice how #2 is is the majority in-view)

And it seems to get worse as I keep scrolling. In this next screenshot, the current page number still says 5, but the 5th image isn't in view at all anymore!

http://imgur.com/tr10aoU

Is there anything different I can do to achieve this effect? Right now I'm just using the in:viewport selector in combination with the :first selector:

$('.item:in-viewport:first')
lhan
  • 4,585
  • 11
  • 60
  • 105

2 Answers2

1

Here's a way you can try with checking the positions of each item as they scroll up. It's not perfect and it is more code but hopefully this will help give you some ideas.

Here's the fiddle example and below is the code.

$container.off('scroll').on('scroll', function () {
    var top = $(this).offset().top;

    $(".item").each(function()
    {  
        var itemTop = $(this).offset().top;
        var itemBottom = $(this).offset().top + $(this).height(); 
        if(itemTop >= (top + 25) && itemTop <= (top + 50))
            $cp.html($(this).attr("page"));
        else if(itemBottom >= (top + 400) && itemBottom <= (top + 425))
            $cp.html($(this).attr("page"));

    });
});
Matt
  • 335
  • 2
  • 4
1

Here's what I ended up going with:

$container.on('scroll', function () {
    // for each item in viewport, which one is in view the MOST?
    var diff = null;
    var num;
    $('.item:in-viewport').each(function(){
        var thisPage = $(this).attr("page");
        var thisTop = $(this).position().top;

        if(diff == null){
            diff = Math.abs(containerTop - thisTop);
            num = thisPage;
        } else {
            var temp = Math.abs(containerTop - thisTop);
            if(temp < diff) {
                diff = temp;
                num = thisPage;
            }
        }
    });
    diff = null;
    $cp.html(num);
});

http://jsfiddle.net/j2aet/1/

I was still able to use the :in-viewport selector, but rather than combining it with the :first selector, I just loop through each item in view and figure out which one is closest to the top of the container. There may be a more efficient way to do this, but this will work for now.

Update (for performance):

Following an idea I found here, we can opt to only update the page number after the user has finished scrolling for at least a half second:

$container.on('scroll', function () {
    if(this.scrollTo) clearTimeout(this.scrollTo);
    this.scrollTo = setTimeout(function () { updatePageNumber();}, 500);
});

function updatePageNumber(){
    // for each item in viewport, which one is in view the MOST?
    var diff = null;
    var num;
    $('.item:in-viewport').each(function(){
        var thisPage = $(this).attr("page");
        var thisTop = $(this).position().top;

        if(diff == null){
            diff = Math.abs(containerTop - thisTop);
            num = thisPage;
        } else {
            var temp = Math.abs(containerTop - thisTop);
            if(temp < diff) {
                diff = temp;
                num = thisPage;
            }
        }
    });
    diff = null;
    $cp.html(num);
}

http://jsfiddle.net/j2aet/2/

Community
  • 1
  • 1
lhan
  • 4,585
  • 11
  • 60
  • 105