;(function($, window, document, undefined){
var pluginName='stellar',
defaults={
scrollProperty: 'scroll',
positionProperty: 'position',
horizontalScrolling: true,
verticalScrolling: true,
horizontalOffset: 0,
verticalOffset: 0,
responsive: false,
parallaxBackgrounds: true,
parallaxElements: true,
hideDistantElements: true,
hideElement: function($elem){ $elem.hide(); },
showElement: function($elem){ $elem.show(); }},
scrollProperty={
scroll: {
getLeft: function($elem){ return $elem.scrollLeft(); },
setLeft: function($elem, val){ $elem.scrollLeft(val); },
getTop: function($elem){ return $elem.scrollTop();	},
setTop: function($elem, val){ $elem.scrollTop(val); }},
position: {
getLeft: function($elem){ return parseInt($elem.css('left'), 10) * -1; },
getTop: function($elem){ return parseInt($elem.css('top'), 10) * -1; }},
margin: {
getLeft: function($elem){ return parseInt($elem.css('margin-left'), 10) * -1; },
getTop: function($elem){ return parseInt($elem.css('margin-top'), 10) * -1; }},
transform: {
getLeft: function($elem){
var computedTransform=getComputedStyle($elem[0])[prefixedTransform];
return (computedTransform!=='none' ? parseInt(computedTransform.match(/(-?[0-9]+)/g)[4], 10) * -1:0);
},
getTop: function($elem){
var computedTransform=getComputedStyle($elem[0])[prefixedTransform];
return (computedTransform!=='none' ? parseInt(computedTransform.match(/(-?[0-9]+)/g)[5], 10) * -1:0);
}}
},
positionProperty={
position: {
setLeft: function($elem, left){ $elem.css('left', left); },
setTop: function($elem, top){ $elem.css('top', top); }},
transform: {
setPosition: function($elem, left, startingLeft, top, startingTop){
$elem[0].style[prefixedTransform]='translate3d(' + (left - startingLeft) + 'px, ' + (top - startingTop) + 'px, 0)';
}}
},
vendorPrefix=(function(){
var prefixes=/^(Moz|Webkit|Khtml|O|ms|Icab)(?=[A-Z])/,
style=$('script')[0].style,
prefix='',
prop;
for (prop in style){
if(prefixes.test(prop)){
prefix=prop.match(prefixes)[0];
break;
}}
if('WebkitOpacity' in style){ prefix='Webkit'; }
if('KhtmlOpacity' in style){ prefix='Khtml'; }
return function(property){
return prefix + (prefix.length > 0 ? property.charAt(0).toUpperCase() + property.slice(1):property);
};}()),
prefixedTransform=vendorPrefix('transform'),
supportsBackgroundPositionXY=$('<div />', { style: 'background:#fff' }).css('background-position-x')!==undefined,
setBackgroundPosition=(supportsBackgroundPositionXY ?
function($elem, x, y){
$elem.css({
'background-position-x': x,
'background-position-y': y
});
} :
function($elem, x, y){
$elem.css('background-position', x + ' ' + y);
}
),
getBackgroundPosition=(supportsBackgroundPositionXY ?
function($elem){
return [
$elem.css('background-position-x'),
$elem.css('background-position-y')
];
} :
function($elem){
return $elem.css('background-position').split(' ');
}
),
requestAnimFrame=(
window.requestAnimationFrame       ||
window.webkitRequestAnimationFrame ||
window.mozRequestAnimationFrame    ||
window.oRequestAnimationFrame      ||
window.msRequestAnimationFrame     ||
function(callback){
setTimeout(callback, 1000 / 60);
}
);
function Plugin(element, options){
this.element=element;
this.options=$.extend({}, defaults, options);
this._defaults=defaults;
this._name=pluginName;
this.init();
}
Plugin.prototype={
init: function(){
this.options.name=pluginName + '_' + Math.floor(Math.random() * 1e9);
this._defineElements();
this._defineGetters();
this._defineSetters();
this._handleWindowLoadAndResize();
this._detectViewport();
this.refresh({ firstLoad: true });
if(this.options.scrollProperty==='scroll'){
this._handleScrollEvent();
}else{
this._startAnimationLoop();
}},
_defineElements: function(){
if(this.element===document.body) this.element=window;
this.$scrollElement=$(this.element);
this.$element=(this.element===window ? $('body'):this.$scrollElement);
this.$viewportElement=(this.options.viewportElement!==undefined ? $(this.options.viewportElement):(this.$scrollElement[0]===window||this.options.scrollProperty==='scroll' ? this.$scrollElement:this.$scrollElement.parent()));
},
_defineGetters: function(){
var self=this,
scrollPropertyAdapter=scrollProperty[self.options.scrollProperty];
this._getScrollLeft=function(){
return scrollPropertyAdapter.getLeft(self.$scrollElement);
};
this._getScrollTop=function(){
return scrollPropertyAdapter.getTop(self.$scrollElement);
};},
_defineSetters: function(){
var self=this,
scrollPropertyAdapter=scrollProperty[self.options.scrollProperty],
positionPropertyAdapter=positionProperty[self.options.positionProperty],
setScrollLeft=scrollPropertyAdapter.setLeft,
setScrollTop=scrollPropertyAdapter.setTop;
this._setScrollLeft=(typeof setScrollLeft==='function' ? function(val){
setScrollLeft(self.$scrollElement, val);
}:$.noop);
this._setScrollTop=(typeof setScrollTop==='function' ? function(val){
setScrollTop(self.$scrollElement, val);
}:$.noop);
this._setPosition=positionPropertyAdapter.setPosition ||
function($elem, left, startingLeft, top, startingTop){
if(self.options.horizontalScrolling){
positionPropertyAdapter.setLeft($elem, left, startingLeft);
}
if(self.options.verticalScrolling){
positionPropertyAdapter.setTop($elem, top, startingTop);
}};},
_handleWindowLoadAndResize: function(){
var self=this,
$window=$(window);
if(self.options.responsive){
$window.bind('load.' + this.name, function(){
self.refresh();
});
}
$window.bind('resize.' + this.name, function(){
self._detectViewport();
if(self.options.responsive){
self.refresh();
}});
},
refresh: function(options){
var self=this,
oldLeft=self._getScrollLeft(),
oldTop=self._getScrollTop();
if(!options||!options.firstLoad){
this._reset();
}
this._setScrollLeft(0);
this._setScrollTop(0);
this._setOffsets();
this._findParticles();
this._findBackgrounds();
if(options&&options.firstLoad&&/WebKit/.test(navigator.userAgent)){
$(window).load(function(){
var oldLeft=self._getScrollLeft(),
oldTop=self._getScrollTop();
self._setScrollLeft(oldLeft + 1);
self._setScrollTop(oldTop + 1);
self._setScrollLeft(oldLeft);
self._setScrollTop(oldTop);
});
}
this._setScrollLeft(oldLeft);
this._setScrollTop(oldTop);
},
_detectViewport: function(){
var viewportOffsets=this.$viewportElement.offset(),
hasOffsets=viewportOffsets!==null&&viewportOffsets!==undefined;
this.viewportWidth=this.$viewportElement.width();
this.viewportHeight=this.$viewportElement.height();
this.viewportOffsetTop=(hasOffsets ? viewportOffsets.top:0);
this.viewportOffsetLeft=(hasOffsets ? viewportOffsets.left:0);
},
_findParticles: function(){
var self=this,
scrollLeft=this._getScrollLeft(),
scrollTop=this._getScrollTop();
if(this.particles!==undefined){
for (var i=this.particles.length - 1; i >=0; i--){
this.particles[i].$element.data('stellar-elementIsActive', undefined);
}}
this.particles=[];
if(!this.options.parallaxElements) return;
this.$element.find('[data-stellar-ratio]').each(function(i){
var $this=$(this),
horizontalOffset,
verticalOffset,
positionLeft,
positionTop,
marginLeft,
marginTop,
$offsetParent,
offsetLeft,
offsetTop,
parentOffsetLeft=0,
parentOffsetTop=0,
tempParentOffsetLeft=0,
tempParentOffsetTop=0;
if(!$this.data('stellar-elementIsActive')){
$this.data('stellar-elementIsActive', this);
}else if($this.data('stellar-elementIsActive')!==this){
return;
}
self.options.showElement($this);
if(!$this.data('stellar-startingLeft')){
$this.data('stellar-startingLeft', $this.css('left'));
$this.data('stellar-startingTop', $this.css('top'));
}else{
$this.css('left', $this.data('stellar-startingLeft'));
$this.css('top', $this.data('stellar-startingTop'));
}
positionLeft=$this.position().left;
positionTop=$this.position().top;
marginLeft=($this.css('margin-left')==='auto') ? 0:parseInt($this.css('margin-left'), 10);
marginTop=($this.css('margin-top')==='auto') ? 0:parseInt($this.css('margin-top'), 10);
offsetLeft=$this.offset().left - marginLeft;
offsetTop=$this.offset().top - marginTop;
$this.parents().each(function(){
var $this=$(this);
if($this.data('stellar-offset-parent')===true){
parentOffsetLeft=tempParentOffsetLeft;
parentOffsetTop=tempParentOffsetTop;
$offsetParent=$this;
return false;
}else{
tempParentOffsetLeft +=$this.position().left;
tempParentOffsetTop +=$this.position().top;
}});
horizontalOffset=($this.data('stellar-horizontal-offset')!==undefined ? $this.data('stellar-horizontal-offset'):($offsetParent!==undefined&&$offsetParent.data('stellar-horizontal-offset')!==undefined ? $offsetParent.data('stellar-horizontal-offset'):self.horizontalOffset));
verticalOffset=($this.data('stellar-vertical-offset')!==undefined ? $this.data('stellar-vertical-offset'):($offsetParent!==undefined&&$offsetParent.data('stellar-vertical-offset')!==undefined ? $offsetParent.data('stellar-vertical-offset'):self.verticalOffset));
self.particles.push({
$element: $this,
$offsetParent: $offsetParent,
isFixed: $this.css('position')==='fixed',
horizontalOffset: horizontalOffset,
verticalOffset: verticalOffset,
startingPositionLeft: positionLeft,
startingPositionTop: positionTop,
startingOffsetLeft: offsetLeft,
startingOffsetTop: offsetTop,
parentOffsetLeft: parentOffsetLeft,
parentOffsetTop: parentOffsetTop,
stellarRatio: ($this.data('stellar-ratio')!==undefined ? $this.data('stellar-ratio'):1),
width: $this.outerWidth(true),
height: $this.outerHeight(true),
isHidden: false
});
});
},
_findBackgrounds: function(){
var self=this,
scrollLeft=this._getScrollLeft(),
scrollTop=this._getScrollTop(),
$backgroundElements;
this.backgrounds=[];
if(!this.options.parallaxBackgrounds) return;
$backgroundElements=this.$element.find('[data-stellar-background-ratio]');
if(this.$element.data('stellar-background-ratio')){
$backgroundElements=$backgroundElements.add(this.$element);
}
$backgroundElements.each(function(){
var $this=$(this),
backgroundPosition=getBackgroundPosition($this),
horizontalOffset,
verticalOffset,
positionLeft,
positionTop,
marginLeft,
marginTop,
offsetLeft,
offsetTop,
$offsetParent,
parentOffsetLeft=0,
parentOffsetTop=0,
tempParentOffsetLeft=0,
tempParentOffsetTop=0;
if(!$this.data('stellar-backgroundIsActive')){
$this.data('stellar-backgroundIsActive', this);
}else if($this.data('stellar-backgroundIsActive')!==this){
return;
}
if(!$this.data('stellar-backgroundStartingLeft')){
$this.data('stellar-backgroundStartingLeft', backgroundPosition[0]);
$this.data('stellar-backgroundStartingTop', backgroundPosition[1]);
}else{
setBackgroundPosition($this, $this.data('stellar-backgroundStartingLeft'), $this.data('stellar-backgroundStartingTop'));
}
marginLeft=($this.css('margin-left')==='auto') ? 0:parseInt($this.css('margin-left'), 10);
marginTop=($this.css('margin-top')==='auto') ? 0:parseInt($this.css('margin-top'), 10);
offsetLeft=$this.offset().left - marginLeft - scrollLeft;
offsetTop=$this.offset().top - marginTop - scrollTop;
$this.parents().each(function(){
var $this=$(this);
if($this.data('stellar-offset-parent')===true){
parentOffsetLeft=tempParentOffsetLeft;
parentOffsetTop=tempParentOffsetTop;
$offsetParent=$this;
return false;
}else{
tempParentOffsetLeft +=$this.position().left;
tempParentOffsetTop +=$this.position().top;
}});
horizontalOffset=($this.data('stellar-horizontal-offset')!==undefined ? $this.data('stellar-horizontal-offset'):($offsetParent!==undefined&&$offsetParent.data('stellar-horizontal-offset')!==undefined ? $offsetParent.data('stellar-horizontal-offset'):self.horizontalOffset));
verticalOffset=($this.data('stellar-vertical-offset')!==undefined ? $this.data('stellar-vertical-offset'):($offsetParent!==undefined&&$offsetParent.data('stellar-vertical-offset')!==undefined ? $offsetParent.data('stellar-vertical-offset'):self.verticalOffset));
self.backgrounds.push({
$element: $this,
$offsetParent: $offsetParent,
isFixed: $this.css('background-attachment')==='fixed',
horizontalOffset: horizontalOffset,
verticalOffset: verticalOffset,
startingValueLeft: backgroundPosition[0],
startingValueTop: backgroundPosition[1],
startingBackgroundPositionLeft: (isNaN(parseInt(backgroundPosition[0], 10)) ? 0:parseInt(backgroundPosition[0], 10)),
startingBackgroundPositionTop: (isNaN(parseInt(backgroundPosition[1], 10)) ? 0:parseInt(backgroundPosition[1], 10)),
startingPositionLeft: $this.position().left,
startingPositionTop: $this.position().top,
startingOffsetLeft: offsetLeft,
startingOffsetTop: offsetTop,
parentOffsetLeft: parentOffsetLeft,
parentOffsetTop: parentOffsetTop,
stellarRatio: ($this.data('stellar-background-ratio')===undefined ? 1:$this.data('stellar-background-ratio'))
});
});
},
_reset: function(){
var particle,
startingPositionLeft,
startingPositionTop,
background,
i;
for (i=this.particles.length - 1; i >=0; i--){
particle=this.particles[i];
startingPositionLeft=particle.$element.data('stellar-startingLeft');
startingPositionTop=particle.$element.data('stellar-startingTop');
this._setPosition(particle.$element, startingPositionLeft, startingPositionLeft, startingPositionTop, startingPositionTop);
this.options.showElement(particle.$element);
particle.$element.data('stellar-startingLeft', null).data('stellar-elementIsActive', null).data('stellar-backgroundIsActive', null);
}
for (i=this.backgrounds.length - 1; i >=0; i--){
background=this.backgrounds[i];
background.$element.data('stellar-backgroundStartingLeft', null).data('stellar-backgroundStartingTop', null);
setBackgroundPosition(background.$element, background.startingValueLeft, background.startingValueTop);
}},
destroy: function(){
this._reset();
this.$scrollElement.unbind('resize.' + this.name).unbind('scroll.' + this.name);
this._animationLoop=$.noop;
$(window).unbind('load.' + this.name).unbind('resize.' + this.name);
},
_setOffsets: function(){
var self=this,
$window=$(window);
$window.unbind('resize.horizontal-' + this.name).unbind('resize.vertical-' + this.name);
if(typeof this.options.horizontalOffset==='function'){
this.horizontalOffset=this.options.horizontalOffset();
$window.bind('resize.horizontal-' + this.name, function(){
self.horizontalOffset=self.options.horizontalOffset();
});
}else{
this.horizontalOffset=this.options.horizontalOffset;
}
if(typeof this.options.verticalOffset==='function'){
this.verticalOffset=this.options.verticalOffset();
$window.bind('resize.vertical-' + this.name, function(){
self.verticalOffset=self.options.verticalOffset();
});
}else{
this.verticalOffset=this.options.verticalOffset;
}},
_repositionElements: function(){
var scrollLeft=this._getScrollLeft(),
scrollTop=this._getScrollTop(),
horizontalOffset,
verticalOffset,
particle,
fixedRatioOffset,
background,
bgLeft,
bgTop,
isVisibleVertical=true,
isVisibleHorizontal=true,
newPositionLeft,
newPositionTop,
newOffsetLeft,
newOffsetTop,
i;
if(this.currentScrollLeft===scrollLeft&&this.currentScrollTop===scrollTop&&this.currentWidth===this.viewportWidth&&this.currentHeight===this.viewportHeight){
return;
}else{
this.currentScrollLeft=scrollLeft;
this.currentScrollTop=scrollTop;
this.currentWidth=this.viewportWidth;
this.currentHeight=this.viewportHeight;
}
for (i=this.particles.length - 1; i >=0; i--){
particle=this.particles[i];
fixedRatioOffset=(particle.isFixed ? 1:0);
if(this.options.horizontalScrolling){
newPositionLeft=(scrollLeft + particle.horizontalOffset + this.viewportOffsetLeft + particle.startingPositionLeft - particle.startingOffsetLeft + particle.parentOffsetLeft) * -(particle.stellarRatio + fixedRatioOffset - 1) + particle.startingPositionLeft;
newOffsetLeft=newPositionLeft - particle.startingPositionLeft + particle.startingOffsetLeft;
}else{
newPositionLeft=particle.startingPositionLeft;
newOffsetLeft=particle.startingOffsetLeft;
}
if(this.options.verticalScrolling){
newPositionTop=(scrollTop + particle.verticalOffset + this.viewportOffsetTop + particle.startingPositionTop - particle.startingOffsetTop + particle.parentOffsetTop) * -(particle.stellarRatio + fixedRatioOffset - 1) + particle.startingPositionTop;
newOffsetTop=newPositionTop - particle.startingPositionTop + particle.startingOffsetTop;
}else{
newPositionTop=particle.startingPositionTop;
newOffsetTop=particle.startingOffsetTop;
}
if(this.options.hideDistantElements){
isVisibleHorizontal = !this.options.horizontalScrolling||newOffsetLeft + particle.width > (particle.isFixed ? 0:scrollLeft)&&newOffsetLeft < (particle.isFixed ? 0:scrollLeft) + this.viewportWidth + this.viewportOffsetLeft;
isVisibleVertical = !this.options.verticalScrolling||newOffsetTop + particle.height > (particle.isFixed ? 0:scrollTop)&&newOffsetTop < (particle.isFixed ? 0:scrollTop) + this.viewportHeight + this.viewportOffsetTop;
}
if(isVisibleHorizontal&&isVisibleVertical){
if(particle.isHidden){
this.options.showElement(particle.$element);
particle.isHidden=false;
}
this._setPosition(particle.$element, newPositionLeft, particle.startingPositionLeft, newPositionTop, particle.startingPositionTop);
}else{
if(!particle.isHidden){
this.options.hideElement(particle.$element);
particle.isHidden=true;
}}
}
for (i=this.backgrounds.length - 1; i >=0; i--){
background=this.backgrounds[i];
fixedRatioOffset=(background.isFixed ? 0:1);
bgLeft=(this.options.horizontalScrolling ? (scrollLeft + background.horizontalOffset - this.viewportOffsetLeft - background.startingOffsetLeft + background.parentOffsetLeft - background.startingBackgroundPositionLeft) * (fixedRatioOffset - background.stellarRatio) + 'px':background.startingValueLeft);
bgTop=(this.options.verticalScrolling ? (scrollTop + background.verticalOffset - this.viewportOffsetTop - background.startingOffsetTop + background.parentOffsetTop - background.startingBackgroundPositionTop) * (fixedRatioOffset - background.stellarRatio) + 'px':background.startingValueTop);
setBackgroundPosition(background.$element, bgLeft, bgTop);
}},
_handleScrollEvent: function(){
var self=this,
ticking=false;
var update=function(){
self._repositionElements();
ticking=false;
};
var requestTick=function(){
if(!ticking){
requestAnimFrame(update);
ticking=true;
}};
this.$scrollElement.bind('scroll.' + this.name, requestTick);
requestTick();
},
_startAnimationLoop: function(){
var self=this;
this._animationLoop=function(){
requestAnimFrame(self._animationLoop);
self._repositionElements();
};
this._animationLoop();
}};
$.fn[pluginName]=function (options){
var args=arguments;
if(options===undefined||typeof options==='object'){
return this.each(function (){
if(!$.data(this, 'plugin_' + pluginName)){
$.data(this, 'plugin_' + pluginName, new Plugin(this, options));
}});
}else if(typeof options==='string'&&options[0]!=='_'&&options!=='init'){
return this.each(function (){
var instance=$.data(this, 'plugin_' + pluginName);
if(instance instanceof Plugin&&typeof instance[options]==='function'){
instance[options].apply(instance, Array.prototype.slice.call(args, 1));
}
if(options==='destroy'){
$.data(this, 'plugin_' + pluginName, null);
}});
}};
$[pluginName]=function(options){
var $window=$(window);
return $window.stellar.apply($window, Array.prototype.slice.call(arguments, 0));
};
$[pluginName].scrollProperty=scrollProperty;
$[pluginName].positionProperty=positionProperty;
window.Stellar=Plugin;
}(jQuery, this, document));