protonet / jquery.inview

A jQuery plugin that adds a bindable 'inview' event for detecting when an element is scrolled into view.
blog.protonet.info/post/2516624585/jquery-inview-plugin
Do What The F*ck You Want To Public License
1.67k stars 488 forks source link

Add offset? #78

Open jamesryan-dev opened 5 years ago

jamesryan-dev commented 5 years ago

Has any1 been able to add an offset so isInView triggers slightly before or after element leaves viewport?

svarup commented 5 years ago

With offset to preloading the image when it's getting close to entering the viewport. To do this, you can now add a data-offset attribute to your target element. For example:

<img data-offset="300">

also support visible area using third parameter on on, one event for example:

$('div').on('inview', function (event, visible, topOrBottomOrBoth) {
 if (visible == true) {
    // element is now visible in the viewport
    if (topOrBottomOrBoth == 'top') {
      // top part of element is visible
    } else if (topOrBottomOrBoth == 'bottom') {
      // bottom part of element is visible
    } else {
      // whole part of element is visible
    }
  } else {
    // element has gone out of viewport
  }
});

updated jquery.inview:

/**
 * author Christopher Blum
 *    - based on the idea of Remy Sharp, http://remysharp.com/2009/01/26/element-in-view-event-plugin/
 *    - forked from http://github.com/zuk/jquery.inview/
 */
(function (factory) {
  if (typeof define == 'function' && define.amd) {
    // AMD
    define(['jquery'], factory);
  } else if (typeof exports === 'object') {
    // Node, CommonJS
    module.exports = factory(require('jquery'));
  } else {
      // Browser globals
    factory(jQuery);
  }
}(function ($) {

  var inviewObjects = [], viewportSize, viewportOffset,
      d = document, w = window, documentElement = d.documentElement, timer;

  $.event.special.inview = {
    add: function(data) {
      inviewObjects.push({ data: data, $element: $(this), element: this });
      // Use setInterval in order to also make sure this captures elements within
      // "overflow:scroll" elements or elements that appeared in the dom tree due to
      // dom manipulation and reflow
      // old: $(window).scroll(checkInView);
      //
      // By the way, iOS (iPad, iPhone, ...) seems to not execute, or at least delays
      // intervals while the user scrolls. Therefore the inview event might fire a bit late there
      //
      // Don't waste cycles with an interval until we get at least one element that
      // has bound to the inview event.
      if (!timer && inviewObjects.length) {
         timer = setInterval(checkInView, 250);
      }
    },

    remove: function(data) {
      for (var i=0; i<inviewObjects.length; i++) {
        var inviewObject = inviewObjects[i];
        if (inviewObject.element === this && inviewObject.data.guid === data.guid) {
          inviewObjects.splice(i, 1);
          break;
        }
      }

      // Clear interval when we no longer have any elements listening
      if (!inviewObjects.length) {
         clearInterval(timer);
         timer = null;
      }
    }
  };

  function getViewportSize() {
    var mode, domObject, size = { height: w.innerHeight, width: w.innerWidth };

    // if this is correct then return it. iPad has compat Mode, so will
    // go into check clientHeight/clientWidth (which has the wrong value).
    if (!size.height) {
      mode = d.compatMode;
      if (mode || !$.support.boxModel) { // IE, Gecko
        domObject = mode === 'CSS1Compat' ?
          documentElement : // Standards
          d.body; // Quirks
        size = {
          height: domObject.clientHeight,
          width:  domObject.clientWidth
        };
      }
    }

    return size;
  }

  function getViewportOffset() {
    return {
      top:  w.pageYOffset || documentElement.scrollTop   || d.body.scrollTop,
      left: w.pageXOffset || documentElement.scrollLeft  || d.body.scrollLeft
    };
  }

  function checkInView() {
    if (!inviewObjects.length) {
      return;
    }

    var i = 0, $elements = $.map(inviewObjects, function(inviewObject) {
      var selector  = inviewObject.data.selector,
          $element  = inviewObject.$element;
      return selector ? $element.find(selector) : $element;
    });

    viewportSize   = viewportSize   || getViewportSize();
    viewportOffset = viewportOffset || getViewportOffset();

    for (; i<inviewObjects.length; i++) {
      // Ignore elements that are not in the DOM tree
      if (!$.contains(documentElement, $elements[i][0])) {
        continue;
      }

      var $element          = $($elements[i]),
          elementSize       = { height: $element[0].offsetHeight, width: $element[0].offsetWidth },
          elementOffset     = $element.offset(),
          wasInView         = $element.data('inview') || false;

      // Don't ask me why because I haven't figured out yet:
      // viewportOffset and viewportSize are sometimes suddenly null in Firefox 5.
      // Even though it sounds weird:
      // It seems that the execution of this function is interferred by the onresize/onscroll event
      // where viewportOffset and viewportSize are unset
      if (!viewportOffset || !viewportSize) {
        return;
      }

      var elBottom          = elementOffset.top + elementSize.height,
          offset            = $element.data('offset') || 0,
          isBottomVisible   = elBottom + offset >= viewportOffset.top && elementOffset.top <= viewportOffset.top,
          viewportBottom    = viewportOffset.top + viewportSize.height;
          isTopVisible      = elementOffset.top - offset <= viewportBottom && elBottom >= viewportBottom,
          inView            = elementOffset.top >= viewportOffset.top && elBottom <= viewportBottom,
          inViewWithOffset  = inView || isBottomVisible || isTopVisible || (elementOffset.top <= viewportOffset.top && elBottom >= viewportBottom);

        if (inViewWithOffset) {
          var visPart = (isTopVisible) ? 'top' : (isBottomVisible) ? 'bottom' : 'both';
          if (!wasInView || wasInView !== visPart) {
            $element.data('inview', visPart).trigger('inview', [true, visPart]);
          }
      } else if (!inView && wasInView) {
        $element.data('inview', false).trigger('inview', [false]);
      }
    }
  }

  $(w).on("scroll resize scrollstop", function() {
    viewportSize = viewportOffset = null;
  });

  // IE < 9 scrolls to focused elements without firing the "scroll" event
  if (!documentElement.addEventListener && documentElement.attachEvent) {
    documentElement.attachEvent("onfocusin", function() {
      viewportOffset = null;
    });
  }
}));