pstanton
pstanton

Reputation: 36640

scroll page so that element is visible

I've just tried prototype's scrollTo function and as the documentation states, it

Scrolls the window so that element appears at the top of the viewport

I'd like a function that

  1. only scrolls if the element is not entirely visible within the viewport
  2. scrolls so that the element appears at the center of the viewport

does anyone know of such a function in prototype, scriptaculous or stand-alone?

Upvotes: 9

Views: 5514

Answers (3)

dma_k
dma_k

Reputation: 10639

My solution does not cover 100% of what is requested, but perhaps someone finds it useful.

/**
 * Scroll container so that given element becomes visible. Features:
 * <ol>
 * <li>If element is already visible, then no action is taken.
 * <li>If element is above view port, the viewport is scrolled upwards so that element becomes visible at the top.
 * <li>If element is below view port, the viewport is scrolled downwards so that element becomes visible at the bottom.
 * </ol>
 *
 * @param element
 *          optional string (selector) or jQuery object that controls the scrolling of the element
 * @param options
 *          optional extra settings
 * @param options.animationSpeed
 *          if defined, then scrolling is animated; determines time in milliseconds after which the element should
 *          be scrolled into viewport
 * @param options.heightScale
 *          double number from 0 to 1; when scrolling the element from bottom sometimes it is desirable to scroll
 *          element close to the top; e.g. to scroll it to the center specify 0.5; to scroll it to the top specify 0
 * @param options.complete
 *          function to be called after animation is completed; if there is no animation, the function is called straight away
 */
$.fn.scrollTo = function(element, options) {
    options = options || {};
    var elementTop = element.offset().top;
    var containerTop = this.offset().top;
    var newScrollTop = null;

    if (elementTop < containerTop) {
        // Scroll to the top:
        newScrollTop = Math.round(this.scrollTop() + elementTop - containerTop);
    } else {
        // Scroll to the bottom:
        var elementBottom = elementTop + element.outerHeight(true);
        var containerHeight = this.height();

        if (elementBottom > containerTop + containerHeight) {
            if (options.heightScale != null) {
                if (options.heightScale === 0) {
                    // This will effectively turn the formulae below into "elementTop - containerTop":
                    containerHeight = element.outerHeight(true);
                } else {
                    containerHeight *= options.heightScale;
                }
            }

            newScrollTop = Math.round(this.scrollTop() + elementBottom - containerTop - containerHeight);
        }
    }

    if (newScrollTop !== null) {
        if (options && options.animationSpeed) {
            this.animate({
                scrollTop : newScrollTop
            }, {
                "duration" : options.animationSpeed,
                "complete" : options.complete
            });
        } else {
            this.scrollTop(newScrollTop);

            if ($.isFunction(options.complete)) {
                options.complete();
            }
        }
    } else {
        if ($.isFunction(options.complete)) {
            options.complete();
        }
    }

    return this;
};

Demo

Upvotes: 0

Rumble
Rumble

Reputation: 142

Here is an alternative approach, that uses some of Prototype's built in functionality for working with the viewport and scroll dimensions...

function scrollToCenterOfElement(id){
  // Cache element and property lookups...

  var element = $(id);
  var height = element.measure('height');
  var top = element.cumulativeOffset().top;
  var scroll = document.viewport.getScrollOffsets();
  var dimensions = document.viewport.getDimensions();

  // Checks to see if the top offset plus the height of the element is greater
  // than the sum of the viewport height and vertical scroll offset, which means
  // that the element has yet to be fully scrolled in to view, or if the 
  // top offset is smaller than the vertical scroll offset, which means the element
  // has already been (at least partly) scrolled out of view..

  if ((top + height > dimensions.height + scroll.top) || (top < dimensions.height + scroll.top)) {

    // Scroll window to sum of top offset plus half the height of the element
    // minus half of the viewport height, thus centering the element vertically.
    window.scrollTo(0, top + (height / 2) - (dimensions.height / 2));

  }

}

scrollToCenterOfElement('my-element');

Upvotes: 3

gblazex
gblazex

Reputation: 50109

I guess you need something like this (demo):

window.height

function getWindowHeight() {
  var body  = document.body;
  var docEl = document.documentElement;
  return window.innerHeight || 
         (docEl && docEl.clientHeight) ||
         (body  && body.clientHeight)  || 
         0;
}

Scroll

function scrollElemToCenter(id, duration) {
  var el = document.getElementById(id);
  var winHeight = getWindowHeight();
  var offsetTop = el.offsetTop;
  if (offsetTop > winHeight) { 
    var y = offsetTop - (winHeight-el.offsetHeight)/2;
    // wo animation: scrollTo(0, y);
    scrollToAnim(y, duration);
  }
}

Animation (optional, you can use script.aculo.us, etc.)

function interpolate(source,target,pos) { return (source+(target-source)*pos); }
function easing(pos) { return (-Math.cos(pos*Math.PI)/2) + 0.5; }

function scrollToAnim(targetTop, duration) {
  duration || (duration = 1000);
  var start    = +new Date,
      finish   = start + duration,
      startTop = getScrollRoot().scrollTop,
      interval = setInterval(function(){
        var now = +new Date, 
            pos = (now>finish) ? 1 : (now-start)/duration;
        var y = interpolate(startTop, targetTop, easing(pos)) >> 0;
        window.scrollTo(0, y);
        if(now > finish) { 
          clearInterval(interval);
        }
      }, 10);
}  

get scroll root

var getScrollRoot = (function() {
  var SCROLL_ROOT;
  return function() {
    if (!SCROLL_ROOT) {
      var bodyScrollTop  = document.body.scrollTop;
      var docElScrollTop = document.documentElement.scrollTop;
      window.scrollBy(0, 1);
      if (document.body.scrollTop != bodyScrollTop)
        (SCROLL_ROOT = document.body);
      else 
        (SCROLL_ROOT = document.documentElement);
      window.scrollBy(0, -1);
    }
    return SCROLL_ROOT;
  };
})();

Upvotes: 7

Related Questions