/** * Snowbridge Parallax * Version: 1.0.5 * URL: @ThemeMountain * Description: Parallax plugin * Requires: jQuery 1.10+ * Author: ThemeMountain * Copyright: Copyright 2013 ThemeMountain * License: Attribution-NonCommercial 3.0 (http://creativecommons.org/licenses/by-nc/3.0/) */ ;(function( $, document, window, undefined ) { 'use strict'; var SnowBridge = function( element, options ){ // Settings var settings = $.extend( {}, $.fn.snowBridge.tmpOpts, options ); // jQuery el var container = $( element ); // Startup data container .data( 'animating', false ) .data( 'setup', false ); // Fade in/out settings.fadeInOut = container.is( '[data-fade-in-out="false"]' ) ? false : settings.fadeInOut; // Check parallax factor if( settings.parallaxFactor > 1 ) settings.parallaxFactor = 1; // Setup done container.data( 'setup', true ); // Preload preload( container, settings ); }; /** * Preload Images * @param container (required) object; * @param settings (required) array; */ function preload( container, settings ){ // Construct parallax element var parallaxEl = $( '
' ).prependTo( $( container ) ); // Set target container if( mobile ) parallaxEl.css({ height: '' }).addClass( 'tmp-mobile' ); // Add preloader to parllax element var loader = $( '' ); loader.appendTo( parallaxEl ); // Check if retina var src = container.data( 'src' ); var imgExt = src.substr( ( src.lastIndexOf( '.' ) ) ); if( window.isRetinaDevice() && settings.retinaSupport || window.isRetinaDevice() && container.is( '[data-retina]' ) ){ if( !mobile && !settings.retinaSupportMobile || mobile && settings.retinaSupportMobile ){ if( !src.match( /\.(svg)/i ) ){ if( !container.data( 'retina' ) ) src = container.data( 'src' ).replace( imgExt, settings.retinaSuffix + imgExt ); else src = container.data( 'retina' ); } } } // Preload image of container container // and add it to the parallax elelment $( '' ) .attr( 'src', src ) .one( 'load', function(){ // Append image $( this ) .attr( 'src', src ) .appendTo( parallaxEl ); // Remove loader parallaxEl.find( '.tm-loader' ).remove(); // Call loaded loaded( container, settings ); }) .on( 'error', function(){ console.log( 'Error src:' + src ); }); } /** * Once loaded set options, positions * and add necessary events * @param container (required) object; * @param settings (required) array; */ function loaded( container, settings ){ // Media var mediaContainer = container.find( '.tm-parallax' ); var media = container.find( '.tmp-media' ); // Set image as bkg of parallax container var parallaxURL = media.attr( 'src' ); mediaContainer.css({ 'background-image': 'url(' + parallaxURL + ')' }); media.hide(); // Check opacity setting mediaContainer.css({ opacity: 0 }); // Fade in if( tSupport && isElementVisible( container ) ){ mediaContainer.css({ visibility: 'visible', transitionProperty: 'opacity', transitionDuration: 1000 + 'ms', opacity: 1 }); }else{ mediaContainer.css({ visibility: 'visible' }).animate({ opacity: 1 }); } // Add window events $( window ).on( 'resize', function(){ resizeParallax( container, settings ); requestScroll( container, settings ); }).resize(); $( window ).on( 'scroll', function(){ requestScroll( container, settings ); }); // Snowbridge section loaded callback if( settings.onLoaded ) settings.onLoaded(); } /** * Prevent rAF from stacking up */ var requestScroll = function( container, settings ){ if ( !container.data( 'animating' ) ) { window.requestAnimationFrame( function () { updateParallaxElement( container, settings ); }); container.data( 'animating', true ); } }; /** * Resize container and images */ var resizeParallax = function( container, settings ){ // Container references var mediaContainer = container.find( '.tm-parallax' ); var winH = $( window ).height(); var containerH = container.height(); var scrollDistContainer = ( container.height() + winH ) * settings.parallaxFactor; // Set parallax container height if( settings.fullscreen || settings.scaleContainer ){ mediaContainer.css({ height: scrollDistContainer + 'px' }); } // Check that setup is done if( !container.data( 'setup' ) ) return false; // Save data container.data( 'scrollDistContainer', scrollDistContainer ); }; /** * Scroll parllax container, its images * and fade images in/out as moving in/out of view */ var updateParallaxElement = function( container, settings ){ var media = container.find( '.tm-parallax' ); if( isElementVisible( container ) ){ if( mobile ) return false; // Distance remaining until container is fully out of view up top var distanceRemaining = ( container.height() + container.offset().top ) - $( window ).scrollTop(); // Current y position of container container var parallaxElTransY = container.offset().top - $( window ).scrollTop(); // container y position of image // centered vertically var yPosImg = 0 - media.height() / 2; // Current y position of image var mediaTransY = ( yPosImg / 2 * ( distanceRemaining / container.data( 'scrollDistContainer') ) ) * settings.parallaxFactor; // Animate if( tSupport ){ media.css({ transform: 'translate3d( 0px' + ', ' + mediaTransY + 'px' + ', 0px)', visibility: 'visible' }).removeClass( 'out-of-view' ); }else{ media.css({ top: mediaTransY + 'px', visibility: 'visible' }); } // Opacity if( settings.fadeInOut ) { var opacity; var adjustedOpacity; var winH = $( window ).height(); var threshold = settings.fadeThreshold > 1 ? container.height() * 0.5 : container.height() * settings.fadeThreshold; if( parallaxElTransY <= threshold ){ opacity = Math.abs( distanceRemaining / threshold ) > 1 ? 1 : Math.abs( distanceRemaining / threshold ); } if( parallaxElTransY <= winH && parallaxElTransY >= winH - threshold ){ opacity = ( winH - parallaxElTransY ) / threshold > 1 ? 1 : ( winH - parallaxElTransY ) / threshold; } media.css({ opacity: ( Math.ceil( opacity * 100 ) / 100 ).toFixed( 2 ) }); } }else{ if( !mobile ) media.css({ visibility: 'hidden' }).addClass( 'out-of-view' ); } container.data( 'animating', false ); }; /** * Check for whether container * is in/out of the viewport */ var isElementVisible = function( element ){ var winTop = $( window ).scrollTop(); var winBottom = winTop + $( window ).height(); var elTop = element.offset().top; var elBottom = elTop + element.outerHeight(); return ( winBottom >= elTop && winTop <= elBottom ); }; /** * Check if retina deivce */ window.isRetinaDevice = function(){ var mediaQuery = '(-webkit-min-device-pixel-ratio: 1.5), (min--moz-device-pixel-ratio: 1.5), (-o-min-device-pixel-ratio: 3/2), (min-resolution: 1.5dppx)'; if ( this.devicePixelRatio > 1 || this.matchMedia && this.matchMedia( mediaQuery ).matches ) return true; return false; }; // Animation support & prefixing var t = document.body || document.documentElement; var s = t.style; var tSupport = s.transition !== undefined || s.WebkitTransition !== undefined || s.MozTransition !== undefined || s.MsTransition !== undefined || s.OTransition !== undefined; var property = [ 'WebkitTransform', 'MozTransform', 'OTransform', 'msTransform' ]; var prefix; for( var i in property ){ if( s[ property[ i ] ] !== undefined ){ prefix = '-' + property[ i ].replace( 'Transform', '' ).toLowerCase(); } } var transform = prefix + '-transform'; var transitionProperty = prefix + '-transition-property'; var transitionDuration = prefix + '-transition-duration'; // Mobile Check var mobile = false; if( navigator.userAgent.match(/Android/i) || navigator.userAgent.match(/webOS/i) || navigator.userAgent.match(/iPhone/i) || navigator.userAgent.match(/iPad/i) || navigator.userAgent.match(/iPod/i) || navigator.userAgent.match(/BlackBerry/i) || navigator.userAgent.match(/Windows Phone/i) ){ mobile = true; } // http://paulirish.com/2011/requestanimationframe-for-smart-animating/ // http://my.opera.com/emoller/blog/2011/12/20/requestanimationframe-for-smart-er-animating // requestAnimationFrame polyfill by Erik Möller. fixes from Paul Irish and Tino Zijdel // MIT license (function() { var lastTime = 0; var vendors = ['ms', 'moz', 'webkit', 'o']; for( var x = 0; x < vendors.length && !window.requestAnimationFrame; ++x ) { window.requestAnimationFrame = window[vendors[x]+'RequestAnimationFrame']; window.cancelAnimationFrame = window[vendors[x]+'CancelAnimationFrame'] || window[vendors[x]+'CancelRequestAnimationFrame']; } if ( !window.requestAnimationFrame ) window.requestAnimationFrame = function( callback, element ) { var currTime = new Date().getTime(); var timeToCall = Math.max( 0, 16 - ( currTime - lastTime ) ); var id = window.setTimeout( function() { callback(currTime + timeToCall); }, timeToCall); lastTime = currTime + timeToCall; return id; }; if ( !window.cancelAnimationFrame ) window.cancelAnimationFrame = function( id ) { clearTimeout( id ); }; }()); // Plugin $.fn.snowBridge = function( options ) { // Iterate through each DOM element and return it return this.each(function() { var element = $( this ); // Return early if this element already has a plugin instance if ( element.data( 'snowBridge' ) ) return; // Pass options var snowbridge = new SnowBridge( this, options ); // Store plugin object in this element's data element.data( 'snowBridge', snowbridge ); }); }; // Default $.fn.snowBridge.tmpOpts = { fullscreen: false, // Whether parallax sections take screen dimensions or set dimensions parallaxFactor: 0.6, // Rate of parallax scrolling fadeInOut: false, // Fade parallax image in/out when entering/exiting viewport fadeThreshold: 0.5, // Threshold in pixels before fading starts/ends retinaSupport: true, // Check for retina displays and serve retina image retinaSupportMobile: false, // Whether swap should occur on mobile devices retinaSuffix: '@2x', // Retina image suffix // Callback onLoaded: null // Callback: after parllax section has loaded }; })( jQuery, document, window );