File: /home/guardianhealthsolutions/public_html/ScrollTrigger-master/ScrollTrigger.js
/**
* Written by Erik Terwan on 03/07/16.
*
* Erik Terwan - development + design
* https://erikterwan.com
* https://github.com/terwanerik
*
* MIT license.
*/
(function (root, factory) {
if (typeof define === 'function' && define.amd) {
// AMD. Register as an anonymous module.
define([], factory);
} else if (typeof module === 'object' && module.exports) {
// Node. Does not work with strict CommonJS, but
// only CommonJS-like environments that support module.exports,
// like Node.
module.exports = factory();
} else {
// Browser globals (root is window)
root.ScrollTrigger = factory();
}
}(this, function () {
'use strict';
return function(defaultOptions, bindTo, scrollIn) {
/**
* Trigger object, represents a single html element with the
* data-scroll tag. Stores the options given in that tag.
*/
var Trigger = function(_defaultOptions, _element) {
this.element = _element;
this.defaultOptions = _defaultOptions;
this.showCallback = null;
this.hideCallback = null;
this.visibleClass = 'visible';
this.hiddenClass = 'invisible';
this.addWidth = false;
this.addHeight = false;
this.once = false;
var xOffset = 0;
var yOffset = 0;
this.left = function(_this){
return function(){
return _this.element.getBoundingClientRect().left;
};
}(this);
this.top = function(_this){
return function(){
return _this.element.getBoundingClientRect().top;
};
}(this);
this.xOffset = function(_this){
return function(goingLeft){
var offset = xOffset;
// add the full width of the element to the left position, so the
// visibleClass is only added after the element is completely
// in the viewport
if (_this.addWidth && !goingLeft) {
offset += _this.width();
} else if (goingLeft && !_this.addWidth) {
offset -= _this.width();
}
return offset;
};
}(this);
this.yOffset = function(_this){
return function(goingUp){
var offset = yOffset;
// add the full height of the element to the top position, so the
// visibleClass is only added after the element is completely
// in the viewport
if (_this.addHeight && !goingUp) {
offset += _this.height();
} else if (goingUp && !_this.addHeight) {
offset -= _this.height();
}
return offset;
};
}(this);
this.width = function(_this) {
return function(){
return _this.element.offsetWidth;
};
}(this);
this.height = function(_this) {
return function(){
return _this.element.offsetHeight;
};
}(this);
this.reset = function(_this) {
return function() {
_this.removeClass(_this.visibleClass);
_this.removeClass(_this.hiddenClass);
};
}(this);
this.addClass = function(_this){
var addClass = function(className, didAddCallback) {
if (!_this.element.classList.contains(className)) {
_this.element.classList.add(className);
if ( typeof didAddCallback === 'function' ) {
didAddCallback();
}
}
};
var retroAddClass = function(className, didAddCallback) {
className = className.trim();
var regEx = new RegExp('(?:^|\\s)' + className + '(?:(\\s\\w)|$)', 'ig');
var oldClassName = _this.element.className;
if ( !regEx.test(oldClassName) ) {
_this.element.className += " " + className;
if ( typeof didAddCallback === 'function' ) {
didAddCallback();
}
}
};
return _this.element.classList ? addClass : retroAddClass;
}(this);
this.removeClass = function(_this){
var removeClass = function(className, didRemoveCallback) {
if (_this.element.classList.contains(className)) {
_this.element.classList.remove(className);
if ( typeof didRemoveCallback === 'function' ) {
didRemoveCallback();
}
}
};
var retroRemoveClass = function(className, didRemoveCallback) {
className = className.trim();
var regEx = new RegExp('(?:^|\\s)' + className + '(?:(\\s\\w)|$)', 'ig');
var oldClassName = _this.element.className;
if ( regEx.test(oldClassName) ) {
_this.element.className = oldClassName.replace(regEx, "$1").trim();
if ( typeof didRemoveCallback === 'function' ) {
didRemoveCallback();
}
}
};
return _this.element.classList ? removeClass : retroRemoveClass;
}(this);
this.init = function(_this){
return function(){
// set the default options
var options = _this.defaultOptions;
// parse the options given in the data-scroll attribute, if any
var optionString = _this.element.getAttribute('data-scroll');
if (options) {
if (options.toggle && options.toggle.visible) {
_this.visibleClass = options.toggle.visible;
}
if (options.toggle && options.toggle.hidden) {
_this.hiddenClass = options.toggle.hidden;
}
if (options.showCallback) {
_this.showCallback = options.showCallback;
}
if (options.hideCallback) {
_this.hideCallback = options.hideCallback;
}
if (options.centerHorizontal === true) {
xOffset = _this.element.offsetWidth / 2;
}
if (options.centerVertical === true) {
yOffset = _this.element.offsetHeight / 2;
}
if (options.offset && options.offset.x) {
xOffset+= options.offset.x;
}
if (options.offset && options.offset.y) {
yOffset+= options.offset.y;
}
if (options.addWidth) {
_this.addWidth = options.addWidth;
}
if (options.addHeight) {
_this.addHeight = options.addHeight;
}
if (options.once) {
_this.once = options.once;
}
}
// parse the boolean options
var parsedAddWidth = optionString.indexOf("addWidth") > -1;
var parsedAddHeight = optionString.indexOf("addHeight") > -1;
var parsedOnce = optionString.indexOf("once") > -1;
// check if the 'addHeight' was toggled via the data-scroll tag, that overrides the default settings object
if (_this.addWidth === false && parsedAddWidth === true) {
_this.addWidth = parsedAddWidth;
}
if (_this.addHeight === false && parsedAddHeight === true) {
_this.addHeight = parsedAddHeight;
}
if (_this.once === false && parsedOnce === true) {
_this.once = parsedOnce;
}
// parse callbacks
_this.showCallback = _this.element.hasAttribute('data-scroll-showCallback') ? _this.element.getAttribute('data-scroll-showCallback') : _this.showCallback;
_this.hideCallback = _this.element.hasAttribute('data-scroll-hideCallback') ? _this.element.getAttribute('data-scroll-hideCallback') : _this.hideCallback;
// split the options on the toggle() parameter
var classParts = optionString.split('toggle(');
if (classParts.length > 1) {
// the toggle() parameter was given, split it at ) to get the
// content inside the parentheses, then split them on the comma
var classes = classParts[1].split(')')[0].split(',');
// Check if trim exists if not, add the polyfill
// courtesy of MDN
if (!String.prototype.trim) {
String.prototype.trim = function () {
return this.replace(/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g, '');
};
}
// trim and remove the dot
_this.visibleClass = classes[0].trim().replace('.', '');
_this.hiddenClass = classes[1].trim().replace('.', '');
}
// adds the half of the offsetWidth/Height to the x/yOffset
if (optionString.indexOf("centerHorizontal") > -1) {
xOffset = _this.element.offsetWidth / 2;
}
if (optionString.indexOf("centerVertical") > -1) {
yOffset = _this.element.offsetHeight / 2;
}
// split the options on the offset() parameter
var offsetParts = optionString.split('offset(');
if (offsetParts.length > 1) {
// the offset() parameter was given, split it at ) to get the
// content inside the parentheses, then split them on the comma
var offsets = offsetParts[1].split(')')[0].split(',');
// remove the px unit and parse as integer
xOffset += parseInt(offsets[0].replace('px', ''));
yOffset += parseInt(offsets[1].replace('px', ''));
}
// return this for chaining
return _this;
};
}(this);
};
// the element to detect the scroll in
this.scrollElement = window;
// the element to get the data-scroll elements from
this.bindElement = document.body;
// the scope to call the callbacks in, defaults to window
this.callScope = window;
// the Trigger objects
var triggers = [];
// attached callbacks for the requestAnimationFrame loop,
// this is handy for custom scroll based animation. So you
// don't have multiple, unnecessary loops going.
var attached = [];
// the previous scrollTop position, to determine if a user
// is scrolling up or down. Set that to -1 -1 so the loop
// always runs at least once
var previousScroll = {
left: -1,
top: -1
};
// the loop method to use, preferred window.requestAnimationFrame
var loop = window.requestAnimationFrame ||
window.webkitRequestAnimationFrame ||
window.mozRequestAnimationFrame ||
window.msRequestAnimationFrame ||
window.oRequestAnimationFrame ||
function(callback){ setTimeout(callback, 1000 / 60); };
// if the requestAnimationFrame is looping
var isLooping = false;
/**
* Initializes the scrollTrigger
*/
var init = function(_this) {
return function(defaultOptions, bindTo, scrollIn) {
// check if bindTo is not undefined or null,
// otherwise use the document.body
if (bindTo != undefined && bindTo != null) {
_this.bindElement = bindTo;
} else {
_this.bindElement = document.body;
}
// check if the scrollIn is not undefined or null,
// otherwise use the window
if (scrollIn != undefined && scrollIn != null) {
_this.scrollElement = scrollIn;
} else {
_this.scrollElement = window;
}
// Initially bind all elements with the data-scroll attribute
_this.bind(_this.bindElement.querySelectorAll("[data-scroll]"));
// return 'this' for chaining
return _this;
};
}(this);
/**
* Binds new HTMLElement objects to the trigger array
*/
this.bind = function(_this) {
return function(elements) {
// check if an array is given
if (elements instanceof HTMLElement) {
// if it's a single HTMLElement just create an array
elements = [elements];
}
// get all trigger elements, e.g. all elements with
// the data-scroll attribute and turn it from a NodeList
// into a plain old array
var newTriggers = [].slice.call(elements);
// map all the triggers to Trigger objects, and initialize them
// so the options get parsed
newTriggers = newTriggers.map(function (element, index) {
var trigger = new Trigger(defaultOptions, element);
return trigger.init();
});
// add to the triggers array
triggers = triggers.concat(newTriggers);
if (triggers.length > 0 && isLooping == false) {
isLooping = true;
// start the update loop
update();
} else {
isLooping = false;
}
// return 'this' for chaining
return _this;
};
}(this);
/**
* Returns a trigger object from a htmlElement object (e.g. via querySelector())
*/
this.triggerFor = function(_this) {
return function(htmlElement){
var returnTrigger = null;
triggers.each(function(trigger, index) {
if (trigger.element == htmlElement) {
returnTrigger = trigger;
}
});
return returnTrigger;
};
}(this);
/**
* Removes a Trigger by its HTMLElement object, e.g via querySelector()
*/
this.destroy = function(_this) {
return function(htmlElement) {
triggers.each(function(trigger, index) {
if (trigger.element == htmlElement) {
triggers.splice(index, 1);
}
});
// return 'this' for chaining
return _this;
};
}(this);
/**
* Removes all Trigger objects from the Trigger array
*/
this.destroyAll = function(_this) {
return function() {
triggers = [];
// return 'this' for chaining
return _this;
};
}(this);
/**
* Resets a Trigger object, removes all added classes and then removes it from the triggers array. Like nothing
* ever happened..
*/
this.reset = function(_this) {
return function(htmlElement) {
var trigger = _this.triggerFor(htmlElement);
if (trigger != null) {
trigger.reset();
var index = triggers.indexOf(trigger);
if (index > -1) {
triggers.splice(index, 1);
}
}
// return 'this' for chaining
return _this;
};
}(this);
/**
* Does the same as .reset() but for all triggers
*/
this.resetAll = function(_this) {
return function() {
triggers.each(function(trigger, index) {
trigger.reset();
});
triggers = [];
// return 'this' for chaining
return _this;
};
}(this);
/**
* Attaches a callback that get's called every time
* the update method is called
*/
this.attach = function(_this) {
return function(callback) {
// add callback to array
attached.push(callback);
if (!isLooping) {
isLooping = true;
// start the update loop
update();
}
// return 'this' for chaining
return _this;
};
}(this);
/**
* Detaches a callback
*/
this.detach = function(_this) {
return function(callback) {
// remove callback from array
var index = attached.indexOf(callback);
if (index > -1) {
attached.splice(index, 1);
}
return _this;
};
}(this);
// store _this for use in the update function scope (strict)
var _this = this;
/**
* Gets called everytime the browser is ready for it, or when the user
* scrolls (on legacy browsers)
*/
function update() {
// FF and IE use the documentElement instead of body
var currentTop = !_this.bindElement.scrollTop ? document.documentElement.scrollTop : _this.bindElement.scrollTop;
var currentLeft = !_this.bindElement.scrollLeft ? document.documentElement.scrollLeft : _this.bindElement.scrollLeft;
// if the user scrolled
if (previousScroll.left != currentLeft || previousScroll.top != currentTop) {
_this.scrollDidChange();
}
if (triggers.length > 0 || attached.length > 0) {
isLooping = true;
// and loop again
loop(update);
} else {
isLooping = false;
}
}
this.scrollDidChange = function(_this) {
return function() {
var windowWidth = _this.scrollElement.innerWidth || _this.scrollElement.offsetWidth;
var windowHeight = _this.scrollElement.innerHeight || _this.scrollElement.offsetHeight;
// FF and IE use the documentElement instead of body
var currentTop = !_this.bindElement.scrollTop ? document.documentElement.scrollTop : _this.bindElement.scrollTop;
var currentLeft = !_this.bindElement.scrollLeft ? document.documentElement.scrollLeft : _this.bindElement.scrollLeft;
var onceTriggers = [];
// loop through all triggers
triggers.each(function(trigger, index){
var triggerLeft = trigger.left();
var triggerTop = trigger.top();
if (previousScroll.left > currentLeft) {
// scrolling left, so we subtract the xOffset
triggerLeft -= trigger.xOffset(true);
} else if (previousScroll.left < currentLeft) {
// scrolling right, so we add the xOffset
triggerLeft += trigger.xOffset(false);
}
if (previousScroll.top > currentTop) {
// scrolling up, so we subtract the yOffset
triggerTop -= trigger.yOffset(true);
} else if (previousScroll.top < currentTop){
// scrolling down so then we add the yOffset
triggerTop += trigger.yOffset(false);
}
// toggle the classes
if (triggerLeft < windowWidth && triggerLeft >= 0 &&
triggerTop < windowHeight && triggerTop >= 0) {
// the element is visible
trigger.addClass(trigger.visibleClass, function(){
if (trigger.showCallback) {
functionCall(trigger, trigger.showCallback);
}
});
trigger.removeClass(trigger.hiddenClass);
if (trigger.once) {
// remove trigger from triggers array
onceTriggers.push(trigger);
}
} else {
// the element is invisible
trigger.addClass(trigger.hiddenClass);
trigger.removeClass(trigger.visibleClass, function(){
if (trigger.hideCallback) {
functionCall(trigger, trigger.hideCallback);
}
});
}
});
// call the attached callbacks, if any
attached.each(function(callback) {
callback.call(_this, currentLeft, currentTop, windowWidth, windowHeight);
});
// remove the triggers that are 'once'
onceTriggers.each(function(trigger){
var index = triggers.indexOf(trigger);
if (index > -1) {
triggers.splice(index, 1);
}
});
// save the current scroll position
previousScroll.left = currentLeft;
previousScroll.top = currentTop;
};
}(this);
function functionCall(trigger, functionAsString) {
var params = functionAsString.split('(');
var method = params[0];
if (params.length > 1) {
params = params[1].split(')')[0]; // get the value between the parentheses
// check if there are multiple attributes
if (params.indexOf("', '") > -1) {
params = params.split("', '");
} else if (params.indexOf("','") > -1) {
params = params.split("','");
} else if (params.indexOf('", "') > -1) {
params = params.split('", "');
} else if (params.indexOf('","') > -1) {
params = params.split('","');
} else {
// nope, just a single parameter
params = [params];
}
} else {
params = [];
}
// remove all quotes from the parameters
params = params.map(function (param) {
return removeQuotes(param);
});
if (typeof _this.callScope[method] == "function") {
// function exists in the call scope so let's try to call it. Some methods don't like to have the HTMLElement
// passed as 'this', so retry without that if it fails.
try {
_this.callScope[method].apply(trigger.element, params);
} catch (e) {
// alright let's try again
try {
_this.callScope[method].apply(null, params);
} catch (e) {
// ah to bad.
}
}
}
}
// removes quotes from a string, e.g. turns 'foo' or "foo" into foo
// typeof foo is string
function removeQuotes(str) {
str = str + ""; // force a string
if (str[0] == '"') {
str = str.substr(1);
}
if (str[0] == "'") {
str = str.substr(1);
}
if (str[str.length - 1] == '"') {
str = str.substr(0, str.length - 1);
}
if (str[str.length - 1] == "'") {
str = str.substr(0, str.length - 1);
}
return str;
}
// Faster than .forEach
Array.prototype.each = function(a) {
var l = this.length;
for(var i = 0; i < l; i++) {
var e = this[i];
if (e) {
a(e,i);
}
}
};
return init(defaultOptions, bindTo, scrollIn);
};
}));