3

I'm trying to get the div to snap to the center of the viewport, right now it just snaps to the top. I was trying to put an offset of 50% but can only get it in px's.

EDIT

I added a new fiddle where I tried to include $(window).scrollTop() / 2)

http://jsfiddle.net/kZY9R/84/

$("#item").offset().top - 100

var body = $("html, body");
var items = $(".item");
var animating = false;

$(window).scroll(function() {
    clearTimeout($.data(this, 'scrollTimer'));
    if (!animating) {
        $.data(this, 'scrollTimer', setTimeout(function() {

            items.each(function(key, value) {
                if ($(value).offset().top > $(window).scrollTop()) {
                    animating = true;
                    $(body).stop().animate( { scrollTop: $(value).offset().top }, 1000,'swing');
                    setTimeout(function() { animating = false; }, 2000);
                    return false;
                }
            });
        }, 50));
    }
});
user2252219
  • 835
  • 3
  • 17
  • 41

3 Answers3

5

I found this:

$('html, body').animate({scrollTop: $('#your-id').offset().top -100 }, 'slow');

Source: Run ScrollTop with offset of element by ID

Community
  • 1
  • 1
osanger
  • 2,276
  • 3
  • 28
  • 35
  • 2
    So I got this now. http://jsfiddle.net/kZY9R/81/ I still need to add a margin or something to get it halfway from the top though. That also only makes it so that its 100px from the top not a percentage. – user2252219 Jan 11 '16 at 15:58
3

Here's the trick to keep your viewport centralized on a particular div.

Prerequisites

You need to take into account the following three criteria to be able to centralize the viewport on a given item:

  1. height of the last item that appeared on the viewport.
  2. The distance of the last item from the top of the page, i.e. the offset().top of the item.
  3. The height value of the viewport (i.e the window object).

Calculating Vertical Position of the Item

The required scrollTop value for the window can be calculated as in the following:

var scrollValue = itemOffset  // offset of the item from the top of the page
                  - .5 * windowHeight // half the height of the window
                  + .5 * itemHeight;  // half the height of the item

You are basically, moving the top of your viewport to the item under view's top offset initially. This, as you've already experienced, snaps the item to the top of the window.

The real magic part comes when you subtract half of the window's height to go halfway along it vertically, and then shifting your view back down by adding half the item's height. This makes the item appear vertically centralized with regards to the viewport.

Note:

To be able to query the last item that appeared on the viewport, you have to iterate over all of the elements that have a top offset value (i.e. offset().top) less than or equal to that of the window's scrollTop value:

$.each($('.item'), function(i, value) {
   if ($(viewport).scrollTop() >= $(this).offset().top) {
        lastItemInView = $(this);
   }
});

With the above, the lastItemInView variable will always end up with the last element visible in the window.

Demo

Ahmad Baktash Hayeri
  • 5,802
  • 4
  • 30
  • 43
2

Not sure if you figured this out yet or not but I took some code from this answer (How to tell if a DOM element is visible in the current viewport?) that shows how to tell if an element is visible in the view port.

Using that I modified your code to loop through each item and find the first visible one in the viewport and then center that one also factoring in the margin-top you have. Let me know if this helps!

Fiddle: http://jsfiddle.net/kZY9R/86/

var body = $("html, body");
var items = $(".item");
var animating = false;

$(window).scroll(function() {
    clearTimeout($.data(this, 'scrollTimer'));
    if (!animating) {
        $.data(this, 'scrollTimer', setTimeout(function() {

            items.each(function(key, value) {
                if (elementInViewport(value)) {
                    animating = true;
                    var margin = parseInt($(value).css('margin-top'));
                    $('html,body').animate({
                        scrollTop: $(value).offset().top - ($(window).height() + margin - $(value).outerHeight(true)) / 2
                    }, 200);
                    setTimeout(function() {
                        animating = false;
                    }, 2000);
                    return false;
                }
            });
        }, 50));
    }
});

function elementInViewport(el) {
    var top = el.offsetTop;
    var left = el.offsetLeft;
    var width = el.offsetWidth;
    var height = el.offsetHeight;

    while (el.offsetParent) {
        el = el.offsetParent;
        top += el.offsetTop;
        left += el.offsetLeft;
    }

    return (
        top < (window.pageYOffset + window.innerHeight) &&
        left < (window.pageXOffset + window.innerWidth) &&
        (top + height) > window.pageYOffset &&
        (left + width) > window.pageXOffset
    );
}
Community
  • 1
  • 1
AtheistP3ace
  • 9,611
  • 12
  • 43
  • 43
  • 1
    @AthesitP3ace Ah cool. I just have a question. It seems kinda "buggy" when I use it. Like sometimes it doesn't adjust when I scroll or sometimes it will skip one of the items and adjust to the next one. http://jsfiddle.net/kZY9R/87/ Is this because the items are too small height wise? or the delay to recheck position is too short? – user2252219 Jan 13 '16 at 20:40
  • 2
    @user2252219 I think its because you want your timeout and animation duration to be equal. Try making them both 1000 ms. Seems a bit better for me when I do that. – AtheistP3ace Jan 13 '16 at 20:51
  • 1
    Ahh I see. http://jsfiddle.net/kZY9R/88/ Is there a way to make it activate sort of less often? Right now, I need to spam the scroll wheel to get it to move to the next item. – user2252219 Jan 13 '16 at 21:01
  • 1
    You could try doing something like this. It adds a custom `scrollEnd` function. This way the centering of an element won't happen unless the user has stopped scrolling. Let me know if its helps. http://jsfiddle.net/kZY9R/89/ – AtheistP3ace Jan 13 '16 at 21:08
  • 1
    So also keep in mind if you start scrolling while animation is happening it could cause issues. I would speed up the centering animation. Also remember I am only choosing the first item found in the view port. If there is only a small piece of element above visible that will be the one that is centered. When really I think you want the element that is "more" in the view to be centered. – AtheistP3ace Jan 13 '16 at 21:15
  • How can I tell it to adjust to the one that is more in view? Should I be useing `$(".item")[0].scrollIntoView();`? – user2252219 Jan 13 '16 at 21:21
  • I think it will require more code being add to find the first one in view, check if the next one is in view and then have some logic to determine which one has more visibility. Off hand i don't know that logic but maybe I can think of something. Heading out of work right now. – AtheistP3ace Jan 13 '16 at 21:23
  • Ok, I'll google and see what I can try to dig up. :) – user2252219 Jan 13 '16 at 21:36
  • I can try and see what I can cook up when I'm back at computer too =] – AtheistP3ace Jan 13 '16 at 21:36