Source: component.list-view-scroll.js

/**
 * @title list view scroll
 * @{@link https://github.com/typesettin/component.collection_list-view-scroll}
 * @author Yaw Joseph Etse
 * @copyright Copyright (c) 2014 Typesettin. All rights reserved.
 * @license MIT
 */

'use strict';
var classie = require('classie'),
	extend = require('util-extend'),
	events = require('events'),
	util = require('util'),
	docElem;

/**
 * A module that represents a listViewScroll object, a List view collection page.
 * @{@link https://github.com/typesettin/component.collection_list-view-scroll}
 * @author Yaw Joseph Etse
 * @copyright Copyright (c) 2014 Typesettin. All rights reserved.
 * @license MIT
 * @constructor listViewScroll
 * @requires module:classie
 * @requires module:util-extent
 * @requires module:util
 * @requires module:events
 */
var listViewScroll = function (options) {
	/** call event emitter */
	events.EventEmitter.call(this);

	/** module default configuration */
	var defaults = {
		// The viewportFactor defines how much of the appearing item has to be visible in order to trigger the animation
		// if we'd use a value of 0, this would mean that it would add the animation class as soon as the item is in the viewport. 
		// If we were to use the value of 1, the animation would only be triggered when we see all of the item in the viewport (100% of it)
		viewportFactor: 0.2,
		idSelector: 'list-view-scroll',
		initClass: 'list-view-scroll-init',
		animateClass: 'list-view-scroll-animate',
		sectionClass: 'list-view-scroll-section',
		useWindowAsParent: true,
		el: null,
		sections: null
	};
	docElem = window.document.documentElement;

	this.didScroll = false;
	/** extended default options */
	this.options = extend(defaults, options);
	this.init(this.options);
};

/** Inherit event emitter */
util.inherits(listViewScroll, events.EventEmitter);


/**
 * Sets up a new listViewScroll event listeners.
 */
listViewScroll.prototype.initEventListeners = function () {
	var scrollHandler = function () {
		if (!this.didScroll) {
			this.didScroll = true;
			setTimeout(function () {
				this._scrollPage(this.options);
			}.bind(this), 60);
		}
	}.bind(this);

	var resizeHandler = function () {
		// function delayed() {
		// this._scrollPage(this.options);
		// resizeTimeout = null;
		// }.bind(this);

		var resizeTimeout = setTimeout(function () {
			this._scrollPage(this.options);
			resizeTimeout = null;
		}.bind(this), 60);

		if (resizeTimeout) {
			clearTimeout(resizeTimeout);
		}
	}.bind(this);

	if (typeof window === 'object' && typeof window.document === 'object') {
		console.log("this.options.parentElement", this.options.parentElement)
		this.options.parentElement.addEventListener('scroll', scrollHandler, false);
		window.addEventListener('resize', resizeHandler, false);
	}
};
/**
 * Sets up a new listViewScroll component.
 */
listViewScroll.prototype.init = function (options) {
	this.options = options;
	this.options.el = document.getElementById(options.idSelector);
	this.options.sections = this.options.el.getElementsByClassName(options.sectionClass);

	this.options.parentElement = (this.options.useWindowAsParent) ? (window || docElem) : this.options.el.parentElement;

	if (('ontouchstart' in window) || window.DocumentTouch && document instanceof DocumentTouch) {
		// console.log('has touch');
		return;
	}

	this.initEventListeners();
};

/**
 * handle touch start events
 * @returns {number} height of viewport element
 */
listViewScroll.prototype._getViewportH = function () {
	var client = docElem.clientHeight,
		inner = window.innerHeight;
	if (client > inner) {
		return inner;
	}
	else {
		return client;
	}
};
/**
 * get the scroll position of the viewport element
 */
listViewScroll.prototype._scrollY = function () {
	return this.options.parentElement.scrollTop || this.options.parentElement.pageYOffset;
};

/**
 * get element offset, http://stackoverflow.com/a/5598797/989439
 * @param {object} el element of scrolled element
 * @returns {object} top,left position of element
 */
listViewScroll.prototype._getOffset = function (el) {
	var offsetTop = 0,
		offsetLeft = 0;
	do {
		if (!isNaN(el.offsetTop)) {
			offsetTop += el.offsetTop + el.offsetParent.offsetTop;
		}
		if (!isNaN(el.offsetLeft)) {
			offsetLeft += el.offsetLeft + el.offsetParent.offsetLeft;
		}
	} while (el === el.offsetParent); //changed for linting

	return {
		top: offsetTop,
		left: offsetLeft
	};
};


/**
 * handle touch start events
 * @event touchStartHandler
 * @param {object} el element to check if in viewport
 * @param {object} h element to check if in viewport
 */
listViewScroll.prototype._inViewport = function (el, h) {
	var elH = el.offsetHeight,
		scrolled = this._scrollY(),
		viewed = scrolled + this._getViewportH(),
		elTop = this._getOffset(el).top,
		elBottom = elTop + elH;

	// if 0, the element is considered in the viewport as soon as it enters.
	// if 1, the element is considered in the viewport only when it's fully inside
	// value in percentage (1 >= h >= 0)
	h = h || 0;

	return (elTop + elH * h) <= viewed && (elBottom) >= scrolled;
};

/**
 * Keep visible sections in viewport
 * @fires - sectionInView
 * @fires - sectionOutView
 */
listViewScroll.prototype._scrollPage = function () {
	for (var x in this.options.sections) {
		if ((typeof this.options.sections[x] === 'object') && this._inViewport(this.options.sections[x], this.options.viewportFactor)) {
			classie.add(this.options.sections[x], this.options.animateClass);
			this.emit('sectionInView', x);
		}
		else if (typeof this.options.sections[x] === 'object') {
			// this add class init if it doesn't have it. This will ensure that the items initially in the viewport will also animate on scroll
			classie.add(this.options.sections[x], this.options.initClass);
			if (this.options.parentElement.scrollTop !== 0) {
				classie.remove(this.options.sections[x], this.options.animateClass);
			}
			this.emit('sectionOutView', x);
		}
	}

	this.didScroll = false;
};

module.exports = listViewScroll;

// If there is a window object, that at least has a document property,
// define Linotype
if (typeof window === 'object' && typeof window.document === 'object') {
	window.listViewScroll = listViewScroll;
}