(function () {
'use strict';
// Wrapper for rellax.js
angular
.module('mohistory')
.directive('rellaxWrapper', rellaxWrapper);
rellaxWrapper.$inject = ['$timeout', '$rootScope', '$location'];
/**
* Wrapper for Rellax.js, parallax library, by Moe Amaya (@moeamaya). Watches $location
* for url changes, deletes any previous Rellax objects, then recreates them for the
* new page. Also, watches the viewport width, removing Rellax when the page is below 1160.
* Rellax will only return when URL is changed, i.e. on route change.
* @memberof mohistory
* @name rellaxWrapper
* @ngdoc directive
* @param {Object} $timeout AngularJS wrapper for window.timeout
* @param {Object} $rootScope AngularJS Global scope object
* @param {Object} $location AngularJS service that wraps window.location
*/
function rellaxWrapper($timeout, $rootScope, $location) {
var directive = {
link: rellaxWrapperLink,
restrict: 'AE',
};
return directive;
/**
* Link function responsible for all DOM manipulation.
* @function rellaxWrapperLink
* @memberof rellaxWrapper
* @param {Object} scope AngularJS directive scope
* @param {Object} element JQuery element on which the directive is activated
* @param {Object} attrs attributes of the element on which the directive is activated
*/
function rellaxWrapperLink(scope, element, attrs) {
// Booleans used for the async Rellax setup
var rellaxActive = false;
var settingUpRellax = false;
// URL from $location.absURL
var curUrl = '';
// JQuery object used to reduce direct DOM queries
var bodyElem = $('body');
// Store the current instances of Rellax
var relClassRellax = null;
var bgShapeRellax = null;
// Watch for any changes in the URL
scope.$watch(function () {
return $location.absUrl();
}, function (value) {
// If the URL has changed, we're not currently setting up rellax,
// rellax has already been setup at least once, and the viewport is
// greater than 1160
if (value !== curUrl && !settingUpRellax && rellaxActive && $rootScope.viewport.width > 1160) {
cleanUpRellax();
setUpRellax();
curUrl = value;
}
// For the most part, this sets up Rellax on first load
if (!settingUpRellax && !rellaxActive && $rootScope.viewport.width > 1160) {
setUpRellax();
}
});
// Watch the viewport width, if it ever goes below 1160, turn off
// Rellax.
scope.$watch(function () {
return $rootScope.viewport.width;
}, function (value) {
if (value <= 1160) {
cleanUpRellax();
}
});
// Clean up, Rellax objects if/when the directive is removed.
scope.$on('$destroy', function () {
cleanUpRellax();
});
/**
* Setup Rellax objects for the current page. Parallax is
* applied to elements with the class of 'rellax' or 'bg-shape'.
* In order to prevent memory leaks, i.e. unreachable assigned
* memory, only create Rellax objects when the associated objects
* are null (the cleanup function sets these objects to null when
* finished). Timeout is used to allow the HTML to generate
* before trying to select elements.
* @function setUpRellax
* @memberof rellaxWrapperLink
*/
function setUpRellax() {
rellaxActive = true;
settingUpRellax = true;
$timeout(function () {
var relClassElems = bodyElem.find('.rellax');
var bgShapeElems = bodyElem.find('.bg-shape');
if (relClassRellax === undefined || relClassRellax === null) {
relClassRellax = new Rellax('.rellax', {
speed: -2,
center: true,
round: true
});
}
if (bgShapeRellax === undefined || bgShapeRellax === null) {
bgShapeRellax = new Rellax('.bg-shape', {
speed: -2,
center: true,
round: true
});
}
settingUpRellax = false;
}, 2000);
}
/**
* Cleanup Rellax by destroying all linking variables and ensuring all
* style attributes are removed from the effected elements. The biggest
* concern, is to eliminate the possibility of memory leaks.
* @function cleanUpRellax
* @memberof rellaxWrapperLink
*/
function cleanUpRellax() {
// Since, settingUpRellax is async, we need to make sure we don't try
// to cleanup Rellax while we're waiting to set it up.
if (!settingUpRellax) {
var relClassElems = bodyElem.find('.rellax');
var bgShapeElems = bodyElem.find('.bg-shape');
if (relClassRellax !== undefined && relClassRellax !== null) {
if (relClassRellax.destroy) {
relClassRellax.destroy();
}
relClassRellax = null;
}
if (bgShapeRellax !== undefined && bgShapeRellax !== null) {
if (bgShapeRellax.destroy) {
bgShapeRellax.destroy();
}
bgShapeRellax = null;
}
if (relClassElems.length > 0) {
relClassElems.removeAttr('style');
}
if (bgShapeElems.length > 0) {
bgShapeElems.removeAttr('style');
}
rellaxActive = false;
}
}
}
}
// ------------------------------------------
// Rellax.js - v1.0.0
// Buttery smooth parallax library
// Copyright (c) 2016 Moe Amaya (@moeamaya)
// MIT license
//
// Thanks to Paraxify.js and Jaime Cabllero
// for parallax concepts
// GitHub page: https://github.com/proustibat/demo-rellax
//
// Modified by Sierra Gregg to work with
// AngularJS directive. Major changes included
// removing declaration of global Rellax object
// and unlinking listeners in the destroy method.
// Original comments were left in for future reference.
// ------------------------------------------
var Rellax = function (el, options) {
var self = Object.create(Rellax.prototype);
var posY = 0; // set it to -1 so the animate function gets called at least once
var screenY = 0;
var posX = 0;
var screenX = 0;
var blocks = [];
var pause = false;
// check what requestAnimationFrame to use, and if
// it's not supported, use the onscroll event
var loop = window.requestAnimationFrame ||
window.webkitRequestAnimationFrame ||
window.mozRequestAnimationFrame ||
window.msRequestAnimationFrame ||
window.oRequestAnimationFrame ||
function (callback) {
setTimeout(callback, 1000 / 60);
};
// check which transform property to use
var transformProp = window.transformProp || (function () {
var testEl = document.createElement('div');
if (testEl.style.transform == null) {
var vendors = ['Webkit', 'Moz', 'ms'];
for (var vendor in vendors) {
if (testEl.style[vendors[vendor] + 'Transform'] !== undefined) {
return vendors[vendor] + 'Transform';
}
}
}
return 'transform';
})();
// limit the given number in the range [min, max]
var clamp = function (num, min, max) {
return (num <= min) ? min : ((num >= max) ? max : num);
};
// Default Settings
self.options = {
speed: -2,
center: false,
round: true,
vertical: true,
horizontal: false,
callback: function () {},
};
// User defined options (might have more in the future)
if (options) {
Object.keys(options).forEach(function (key) {
self.options[key] = options[key];
});
}
// If some clown tries to crank speed, limit them to +-10
self.options.speed = clamp(self.options.speed, -10, 10);
// By default, rellax class
if (!el) {
el = '.rellax';
}
var elements = document.querySelectorAll(el);
// Now query selector
if (elements.length > 0) {
self.elems = elements;
}
// The elements don't exist
else {
console.log("The elements you're trying to select don't exist.");
}
// Let's kick this script off
// Build array for cached element values
// Bind scroll and resize to animate method
var init = function () {
screenY = window.innerHeight;
screenX = window.innerWidth;
setPosition();
// Get and cache initial position of all elements
for (var i = 0; i < self.elems.length; i++) {
var block = createBlock(self.elems[i]);
blocks.push(block);
}
// @Edit: Removed anonymous function wrapper for animate.
window.addEventListener('resize', animate);
// Start the loop
update();
// The loop does nothing if the scrollPosition did not change
// so call animate to make sure every element has their transforms
animate();
};
// We want to cache the parallax blocks'
// values: base, top, height, speed
// el: is dom object, return: el cache values
var createBlock = function (el) {
var dataPercentage = el.getAttribute('data-rellax-percentage');
var dataSpeed = el.getAttribute('data-rellax-speed');
var dataZindex = el.getAttribute('data-rellax-zindex') || 0;
// initializing at scrollY = 0 (top of browser), scrollX = 0 (left of browser)
// ensures elements are positioned based on HTML layout.
//
// If the element has the percentage attribute, the posY and posX needs to be
// the current scroll position's value, so that the elements are still positioned based on HTML layout
var posY = self.options.vertical ? (dataPercentage || self.options.center ? (window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop) : 0) : 0;
var posX = self.options.horizontal ? (dataPercentage || self.options.center ? (window.pageXOffset || document.documentElement.scrollLeft || document.body.scrollLeft) : 0) : 0;
var blockTop = posY + el.getBoundingClientRect().top;
var blockHeight = el.clientHeight || el.offsetHeight || el.scrollHeight;
var blockLeft = posX + el.getBoundingClientRect().left;
var blockWidth = el.clientWidth || el.offsetWidth || el.scrollWidth;
// apparently parallax equation everyone uses
var percentageY = dataPercentage ? dataPercentage : (posY - blockTop + screenY) / (blockHeight + screenY);
var percentageX = dataPercentage ? dataPercentage : (posX - blockLeft + screenX) / (blockWidth + screenX);
if (self.options.center) {
percentageX = 0.5;
percentageY = 0.5;
}
// Optional individual block speed as data attr, otherwise global speed
// Check if has percentage attr, and limit speed to 5, else limit it to 10
var speed = dataSpeed ? clamp(dataSpeed, -10, 10) : self.options.speed;
if (dataPercentage || self.options.center) {
speed = clamp(dataSpeed || self.options.speed, -5, 5);
}
var bases = updatePosition(percentageX, percentageY, speed);
// ~~Store non-translate3d transforms~~
// Store inline styles and extract transforms
var style = el.style.cssText;
var transform = '';
// Check if there's an inline styled transform
if (style.indexOf('transform') >= 0) {
// Get the index of the transform
var index = style.indexOf('transform');
// Trim the style to the transform point and get the following semi-colon index
var trimmedStyle = style.slice(index);
var delimiter = trimmedStyle.indexOf(';');
// Remove "transform" string and save the attribute
if (delimiter) {
transform = " " + trimmedStyle.slice(11, delimiter).replace(/\s/g, '');
} else {
transform = " " + trimmedStyle.slice(11).replace(/\s/g, '');
}
}
return {
baseX: bases.x,
baseY: bases.y,
top: blockTop,
left: blockLeft,
height: blockHeight,
width: blockWidth,
speed: speed,
style: style,
transform: transform,
zindex: dataZindex
};
};
// set scroll position (posY, posX)
// side effect method is not ideal, but okay for now
// returns true if the scroll changed, false if nothing happened
var setPosition = function () {
var oldY = posY;
var oldX = posX;
if (window.pageYOffset !== undefined) {
posY = window.pageYOffset;
} else {
posY = (document.documentElement || document.body.parentNode || document.body).scrollTop;
}
if (window.pageXOffset !== undefined) {
posX = window.pageXOffset;
} else {
posX = (document.documentElement || document.body.parentNode || document.body).scrollLeft;
}
if (oldY != posY && self.options.vertical) {
// scroll changed, return true
return true;
}
if (oldX != posX && self.options.horizontal) {
// scroll changed, return true
return true;
}
// scroll did not change
return false;
};
// Ahh a pure function, gets new transform value
// based on scrollPosition and speed
// Allow for decimal pixel values
var updatePosition = function (percentageX, percentageY, speed) {
var result = {};
var valueX = (speed * (100 * (1 - percentageX)));
var valueY = (speed * (100 * (1 - percentageY)));
result.x = self.options.round ? Math.round(valueX) : Math.round(valueX * 100) / 100;
result.y = self.options.round ? Math.round(valueY) : Math.round(valueY * 100) / 100;
return result;
};
//
var update = function () {
if (setPosition() && pause === false) {
animate();
}
// loop again
if (loop) {
loop(update);
}
};
// Transform3d on parallax element
var animate = function () {
for (var i = 0; i < self.elems.length; i++) {
var percentageY = ((posY - blocks[i].top + screenY) / (blocks[i].height + screenY));
var percentageX = ((posX - blocks[i].left + screenX) / (blocks[i].width + screenX));
// Subtracting initialize value, so element stays in same spot as HTML
var positions = updatePosition(percentageX, percentageY, blocks[i].speed); // - blocks[i].baseX;
var positionY = positions.y - blocks[i].baseY;
var positionX = positions.x - blocks[i].baseX;
var zindex = blocks[i].zindex;
// Move that element
// (Set the new translation and append initial inline transforms.)
var translate = 'translate3d(' + (self.options.horizontal ? positionX : '0') + 'px,' + (self.options.vertical ? positionY : '0') + 'px,' + zindex + 'px) ' + blocks[i].transform;
self.elems[i].style[transformProp] = translate;
}
self.options.callback(positions);
};
self.destroy = function () {
if (self.elems && self.elems.length) {
for (var i = 0; i < self.elems.length; i++) {
self.elems[i].style.cssText = blocks[i].style;
}
window.removeEventListener('resize', animate);
pause = true;
}
};
if (self.elems && self.elems.length > 0) {
init();
return self;
}
};
})();