lewismcarey
4/16/2014 - 9:38 AM

inview.js

inview.js

/**
 * triggers a callback when an element gets into the viewport
 * uses a little bit of jQuery. elements auto receive the class `inview`
 *
 * the threshold parameter is on scale from 0 to 1 of the height.
 * 0 means it triggers directly, 1 means it must be full visible
 *
 * @example
 * // images needs to be 50% in view
 * inview.register("img", .5, function(item, inview_state) {
 *    console.log(item, inview_state);
 * });
 */

/**
 * requires the frameEvents lib, makes the events run at 60fps
 * https://github.com/jtangelder/frame-events
 * make sure you also have a requestAnimationFrame polyfill included.
 * see the github page of frame-events for a link
 */
var frameEvents = require('./frame-events');
var jQuery = require('jquery');

// holds all registered inview elements
var inview_elements = [];


/**
 * collects the reflow vars on scroll
 * @returns {{scrollY: Number}}
 */
function collectOnScrollData() {
  return {
      scrollY: window.scrollY,
      winHeight: window.innerHeight
  };
}


/**
 * raf handler
 * @method inViewElements
 * @param [ev]
 * @param scrollData
 */
function inViewElements(ev, scrollData) {
  var item, i, inview, threshold,
    scrollY = scrollData.scrollY,
    win_height = scrollData.winHeight;

  // now update the classes
  for(i = 0; i < inview_elements.length; i++) {
    item = inview_elements[i];
    // item must be already x% inview
    threshold = (item.height * item.threshold);

    inview = (
      (scrollY + (win_height - threshold) > item.top) &&
      (scrollY < item.top + item.height - threshold)
    );

    item.element.toggleClass('inview', inview);

    if(item.callback) {
      item.callback(inview, item, scrollData);
    }
  }
}


/**
 * update positions and run inview check
 * @method updateInViewElements
 * @param [ev]
 * @param [scrollData]
 */
function updateInViewElements(ev, scrollData) {
  var i, item;

  // collect offset, this will cause one reflow
  for(i = 0; i < inview_elements.length; i++) {
    item = inview_elements[i];
    item.top = item.element.offset().top;
    item.height = item.element.outerHeight();
  }

  inViewElements(ev, scrollData || collectOnScrollData());
}


/**
 * update in the next animation frame
 * @method rafUpdate
 */
function rafUpdate() {
  requestAnimationFrame(function() {
    updateInViewElements();
  });
}


frameEvents.on(window, "load", updateInViewElements, collectOnScrollData);
frameEvents.on(window, "resize", updateInViewElements, collectOnScrollData);
frameEvents.on(window, "scroll", inViewElements, collectOnScrollData);
rafUpdate();

// make sure the offset and heights are correct, every x ms
setInterval(rafUpdate, 5000);


module.exports = {

  /**
   * register function to add new inview elements
   * @method register
   * @param elements
   * @param threshold on scale from 0 to 1. 0 means it triggers directly, 1 means it must be full visible
   * @param [callback]
   */
  register: function(elements, threshold, callback) {
    jQuery(elements).each(function() {
      var el = jQuery(this);

      // attribute before the argument
      threshold = parseFloat(el.data("inview-threshold")) || threshold;

      inview_elements.push({
        element: el,
        threshold: threshold,
        callback: callback
      });
    });
  },


  /**
   * update all inview elements on the next animation frame
   * @method update
   */
  update: rafUpdate
};