/*! * Name : Just Another Parallax [Jarallax] * Version : 1.8.0 * Author : _nK https://nkdev.info * GitHub : https://github.com/nk-o/jarallax */ (function (window) { 'use strict'; // Adapted from https://gist.github.com/paulirish/1579671 if(!Date.now) { Date.now = function () { return new Date().getTime(); }; } if(!window.requestAnimationFrame) { (function () { var vendors = ['webkit', 'moz']; for (var i = 0; i < vendors.length && !window.requestAnimationFrame; ++i) { var vp = vendors[i]; window.requestAnimationFrame = window[vp+'RequestAnimationFrame']; window.cancelAnimationFrame = window[vp+'CancelAnimationFrame'] || window[vp+'CancelRequestAnimationFrame']; } if (/iP(ad|hone|od).*OS 6/.test(window.navigator.userAgent) // iOS6 is buggy || !window.requestAnimationFrame || !window.cancelAnimationFrame) { var lastTime = 0; window.requestAnimationFrame = function (callback) { var now = Date.now(); var nextTime = Math.max(lastTime + 16, now); return setTimeout(function () { callback(lastTime = nextTime); }, nextTime - now); }; window.cancelAnimationFrame = clearTimeout; } }()); } // test if css property supported by browser // like "transform" var tempDiv = document.createElement('div'); function isPropertySupported (property) { var prefixes = ['O','Moz','ms','Ms','Webkit']; var i = prefixes.length; if (tempDiv.style[property] !== undefined) { return true; } property = property.charAt(0).toUpperCase() + property.substr(1); while (--i > -1 && tempDiv.style[prefixes[i] + property] === undefined) { } return i >= 0; } var supportTransform = isPropertySupported('transform'); var supportTransform3D = isPropertySupported('perspective'); var ua = navigator.userAgent; var isAndroid = ua.toLowerCase().indexOf('android') > -1; var isIOs = /iPad|iPhone|iPod/.test(ua) && !window.MSStream; var isFirefox = ua.toLowerCase().indexOf('firefox') > -1; var isIE = ua.indexOf('MSIE ') > -1 // IE 10 or older || ua.indexOf('Trident/') > -1 // IE 11 || ua.indexOf('Edge/') > -1; // Edge var isIElt10 = document.all && !window.atob; // IE 9 or older var wndW; var wndH; function updateWndVars () { wndW = window.innerWidth || document.documentElement.clientWidth; wndH = window.innerHeight || document.documentElement.clientHeight; } updateWndVars(); // list with all jarallax instances // need to render all in one scroll/resize event var jarallaxList = []; // Jarallax instance var Jarallax = (function () { var instanceID = 0; function Jarallax_inner (item, userOptions) { var _this = this, dataOptions; _this.$item = item; _this.defaults = { type : 'scroll', // type of parallax: scroll, scale, opacity, scale-opacity, scroll-opacity speed : 0.5, // supported value from -1 to 2 imgSrc : null, imgWidth : null, imgHeight : null, elementInViewport : null, zIndex : -100, noAndroid : false, noIos : true, // events onScroll : null, // function(calculations) {} onInit : null, // function() {} onDestroy : null, // function() {} onCoverImage : null // function() {} }; dataOptions = JSON.parse(_this.$item.getAttribute('data-jarallax') || '{}'); _this.options = _this.extend({}, _this.defaults, dataOptions, userOptions); // stop init if android or ios if(!supportTransform || isAndroid && _this.options.noAndroid || isIOs && _this.options.noIos) { return; } // fix speed option [-1.0, 2.0] _this.options.speed = Math.min(2, Math.max(-1, parseFloat(_this.options.speed))); // custom element to check if parallax in viewport var elementInVP = _this.options.elementInViewport; // get first item from array if(elementInVP && typeof elementInVP === 'object' && typeof elementInVP.length !== 'undefined') { elementInVP = elementInVP[0]; } // check if dom element if(!elementInVP instanceof Element) { elementInVP = null; } _this.options.elementInViewport = elementInVP; _this.instanceID = instanceID++; _this.image = { src : _this.options.imgSrc || null, $container : null, $item : null, width : _this.options.imgWidth || null, height : _this.options.imgHeight || null, // fix for some devices // use instead of background image - more smoothly useImgTag : isIOs || isAndroid || isIE, // position absolute is needed on IE9 and FireFox because fixed position have glitches position : !supportTransform3D || isFirefox ? 'absolute' : 'fixed' }; if(_this.initImg()) { _this.init(); } } return Jarallax_inner; }()); // add styles to element Jarallax.prototype.css = function (el, styles) { if(typeof styles === 'string') { if(window.getComputedStyle) { return window.getComputedStyle(el).getPropertyValue(styles); } return el.style[styles]; } // add transform property with vendor prefixes if(styles.transform) { if (supportTransform3D) { styles.transform += ' translateZ(0)'; } styles.WebkitTransform = styles.MozTransform = styles.msTransform = styles.OTransform = styles.transform; } for(var k in styles) { el.style[k] = styles[k]; } return el; }; // Extend like jQuery.extend Jarallax.prototype.extend = function (out) { out = out || {}; for (var i = 1; i < arguments.length; i++) { if (!arguments[i]) { continue; } for (var key in arguments[i]) { if (arguments[i].hasOwnProperty(key)) { out[key] = arguments[i][key]; } } } return out; }; // Jarallax functions Jarallax.prototype.initImg = function () { var _this = this; // get image src if(_this.image.src === null) { _this.image.src = _this.css(_this.$item, 'background-image').replace(/^url\(['"]?/g,'').replace(/['"]?\)$/g,''); } return !(!_this.image.src || _this.image.src === 'none'); }; Jarallax.prototype.init = function () { var _this = this, containerStyles = { position : 'absolute', top : 0, left : 0, width : '100%', height : '100%', overflow : 'hidden', pointerEvents : 'none' }, imageStyles = {}; // save default user styles _this.$item.setAttribute('data-jarallax-original-styles', _this.$item.getAttribute('style')); // set relative position and z-index to the parent if (_this.css(_this.$item, 'position') === 'static') { _this.css(_this.$item, { position: 'relative' }); } if (_this.css(_this.$item, 'z-index') === 'auto') { _this.css(_this.$item, { zIndex: 0 }); } // container for parallax image _this.image.$container = document.createElement('div'); _this.css(_this.image.$container, containerStyles); _this.css(_this.image.$container, { visibility : 'hidden', 'z-index' : _this.options.zIndex }); _this.image.$container.setAttribute('id', 'jarallax-container-' + _this.instanceID); _this.$item.appendChild(_this.image.$container); // use img tag if(_this.image.useImgTag) { _this.image.$item = document.createElement('img'); _this.image.$item.setAttribute('src', _this.image.src); imageStyles = _this.extend({ 'max-width' : 'none' }, containerStyles, imageStyles); } // use div with background image else { _this.image.$item = document.createElement('div'); imageStyles = _this.extend({ 'background-position' : '50% 50%', 'background-size' : '100% auto', 'background-repeat' : 'no-repeat no-repeat', 'background-image' : 'url("' + _this.image.src + '")' }, containerStyles, imageStyles); } // check if one of parents have transform style (without this check, scroll transform will be inverted) // discussion - https://github.com/nk-o/jarallax/issues/9 var parentWithTransform = 0; var $itemParents = _this.$item; while ($itemParents !== null && $itemParents !== document && parentWithTransform === 0) { var parent_transform = _this.css($itemParents, '-webkit-transform') || _this.css($itemParents, '-moz-transform') || _this.css($itemParents, 'transform'); if(parent_transform && parent_transform !== 'none') { parentWithTransform = 1; // add transform on parallax container if there is parent with transform _this.css(_this.image.$container, { transform: 'translateX(0) translateY(0)' }); } $itemParents = $itemParents.parentNode; } // absolute position if one of parents have transformations or parallax without scroll if (parentWithTransform || _this.options.type === 'opacity'|| _this.options.type === 'scale' || _this.options.type === 'scale-opacity') { _this.image.position = 'absolute'; } // add position to parallax block imageStyles.position = _this.image.position; // parallax image _this.css(_this.image.$item, imageStyles); _this.image.$container.appendChild(_this.image.$item); // cover image if width and height is ready function initAfterReady () { _this.coverImage(); _this.clipContainer(); _this.onScroll(true); // call onInit event if(_this.options.onInit) { _this.options.onInit.call(_this); } // timeout to fix IE blinking setTimeout(function () { if(_this.$item) { // remove default user background _this.css(_this.$item, { 'background-image' : 'none', 'background-attachment' : 'scroll', 'background-size' : 'auto' }); } }, 0); } if(_this.image.width && _this.image.height) { // init if width and height already exists initAfterReady(); } else { // load image and get width and height _this.getImageSize(_this.image.src, function (width, height) { _this.image.width = width; _this.image.height = height; initAfterReady(); }); } jarallaxList.push(_this); }; Jarallax.prototype.destroy = function () { var _this = this; // remove from instances list for(var k = 0, len = jarallaxList.length; k < len; k++) { if(jarallaxList[k].instanceID === _this.instanceID) { jarallaxList.splice(k, 1); break; } } // return styles on container as before jarallax init var originalStylesTag = _this.$item.getAttribute('data-jarallax-original-styles'); _this.$item.removeAttribute('data-jarallax-original-styles'); // null occurs if there is no style tag before jarallax init if(originalStylesTag === 'null') { _this.$item.removeAttribute('style'); } else { _this.$item.setAttribute('style', originalStylesTag); } // remove additional dom elements if(_this.$clipStyles) { _this.$clipStyles.parentNode.removeChild(_this.$clipStyles); } _this.image.$container.parentNode.removeChild(_this.image.$container); // call onDestroy event if(_this.options.onDestroy) { _this.options.onDestroy.call(_this); } // delete jarallax from item delete _this.$item.jarallax; // delete all variables for(var n in _this) { delete _this[n]; } }; Jarallax.prototype.getImageSize = function (src, callback) { if(!src || !callback) { return; } var tempImg = new Image(); tempImg.onload = function () { callback(tempImg.width, tempImg.height); }; tempImg.src = src; }; // it will remove some image overlapping // overlapping occur due to an image position fixed inside absolute position element (webkit based browsers works without any fix) Jarallax.prototype.clipContainer = function () { // clip is not working properly on real IE9 and less if(isIElt10) { return; } var _this = this, rect = _this.image.$container.getBoundingClientRect(), width = rect.width, height = rect.height; if(!_this.$clipStyles) { _this.$clipStyles = document.createElement('style'); _this.$clipStyles.setAttribute('type', 'text/css'); _this.$clipStyles.setAttribute('id', '#jarallax-clip-' + _this.instanceID); var head = document.head || document.getElementsByTagName('head')[0]; head.appendChild(_this.$clipStyles); } var styles = [ '#jarallax-container-' + _this.instanceID + ' {', ' clip: rect(0 ' + width + 'px ' + height + 'px 0);', ' clip: rect(0, ' + width + 'px, ' + height + 'px, 0);', '}' ].join('\n'); // add clip styles inline (this method need for support IE8 and less browsers) if (_this.$clipStyles.styleSheet){ _this.$clipStyles.styleSheet.cssText = styles; } else { _this.$clipStyles.innerHTML = styles; } }; Jarallax.prototype.coverImage = function () { var _this = this; if(!_this.image.width || !_this.image.height) { return; } var rect = _this.image.$container.getBoundingClientRect(), contW = rect.width, contH = rect.height, contL = rect.left, imgW = _this.image.width, imgH = _this.image.height, speed = _this.options.speed, isScroll = _this.options.type === 'scroll' || _this.options.type === 'scroll-opacity', scrollDist = 0, resultW = 0, resultH = contH, resultML = 0, resultMT = 0; // scroll parallax if(isScroll) { // scroll distance and height for image if (speed < 0) { scrollDist = speed * Math.max(contH, wndH); } else { scrollDist = speed * (contH + wndH); } // size for scroll parallax if (speed > 1) { resultH = Math.abs(scrollDist - wndH); } else if (speed < 0) { resultH = scrollDist / speed + Math.abs(scrollDist); } else { resultH += Math.abs(wndH - contH) * (1 - speed); } scrollDist /= 2; } // calculate width relative to height and image size resultW = resultH * imgW / imgH; if(resultW < contW) { resultW = contW; resultH = resultW * imgH / imgW; } // center parallax image if(isScroll) { resultML = contL + (contW - resultW) / 2; resultMT = (wndH - resultH) / 2; } else { resultML = (contW - resultW) / 2; resultMT = (contH - resultH) / 2; } // fix if parallax block in absolute position if(_this.image.position === 'absolute') { resultML -= contL; } // store scroll distance _this.parallaxScrollDistance = scrollDist; // apply result to item _this.css(_this.image.$item, { width: resultW + 'px', height: resultH + 'px', marginLeft: resultML + 'px', marginTop: resultMT + 'px' }); // call onCoverImage event if(_this.options.onCoverImage) { _this.options.onCoverImage.call(_this); } }; Jarallax.prototype.isVisible = function () { return this.isElementInViewport || false; }; Jarallax.prototype.onScroll = function (force) { var _this = this; if(!_this.image.width || !_this.image.height) { return; } var rect = _this.$item.getBoundingClientRect(), contT = rect.top, contH = rect.height, styles = { visibility : 'visible', backgroundPosition : '50% 50%' }; // check if in viewport var viewportRect = rect; if(_this.options.elementInViewport) { viewportRect = _this.options.elementInViewport.getBoundingClientRect(); } _this.isElementInViewport = viewportRect.bottom >= 0 && viewportRect.right >= 0 && viewportRect.top <= wndH && viewportRect.left <= wndW; // stop calculations if item is not in viewport if (force ? false : !_this.isElementInViewport) { return; } // calculate parallax helping variables var beforeTop = Math.max(0, contT), beforeTopEnd = Math.max(0, contH + contT), afterTop = Math.max(0, -contT), beforeBottom = Math.max(0, contT + contH - wndH), beforeBottomEnd = Math.max(0, contH - (contT + contH - wndH)), afterBottom = Math.max(0, -contT + wndH - contH), fromViewportCenter = 1 - 2 * (wndH - contT) / (wndH + contH); // calculate on how percent of section is visible var visiblePercent = 1; if(contH < wndH) { visiblePercent = 1 - (afterTop || beforeBottom) / contH; } else { if(beforeTopEnd <= wndH) { visiblePercent = beforeTopEnd / wndH; } else if (beforeBottomEnd <= wndH) { visiblePercent = beforeBottomEnd / wndH; } } // opacity if(_this.options.type === 'opacity' || _this.options.type === 'scale-opacity' || _this.options.type === 'scroll-opacity') { styles.transform = ''; // empty to add translateZ(0) where it is possible styles.opacity = visiblePercent; } // scale if(_this.options.type === 'scale' || _this.options.type === 'scale-opacity') { var scale = 1; if(_this.options.speed < 0) { scale -= _this.options.speed * visiblePercent; } else { scale += _this.options.speed * (1 - visiblePercent); } styles.transform = 'scale(' + scale + ')'; } // scroll if(_this.options.type === 'scroll' || _this.options.type === 'scroll-opacity') { var positionY = _this.parallaxScrollDistance * fromViewportCenter; // fix if parallax block in absolute position if(_this.image.position === 'absolute') { positionY -= contT; } styles.transform = 'translateY(' + positionY + 'px)'; } _this.css(_this.image.$item, styles); // call onScroll event if(_this.options.onScroll) { _this.options.onScroll.call(_this, { section: rect, beforeTop: beforeTop, beforeTopEnd: beforeTopEnd, afterTop: afterTop, beforeBottom: beforeBottom, beforeBottomEnd: beforeBottomEnd, afterBottom: afterBottom, visiblePercent: visiblePercent, fromViewportCenter: fromViewportCenter }); } }; // init events function addEventListener (el, eventName, handler) { if (el.addEventListener) { el.addEventListener(eventName, handler); } else { el.attachEvent('on' + eventName, function (){ handler.call(el); }); } } function update (e) { window.requestAnimationFrame(function () { if(e.type !== 'scroll') { updateWndVars(); } for(var k = 0, len = jarallaxList.length; k < len; k++) { // cover image and clip needed only when parallax container was changed if(e.type !== 'scroll') { jarallaxList[k].coverImage(); jarallaxList[k].clipContainer(); } jarallaxList[k].onScroll(); } }); } addEventListener(window, 'scroll', update); addEventListener(window, 'resize', update); addEventListener(window, 'orientationchange', update); addEventListener(window, 'load', update); // global definition var plugin = function (items) { // check for dom element // thanks: http://stackoverflow.com/questions/384286/javascript-isdom-how-do-you-check-if-a-javascript-object-is-a-dom-object if(typeof HTMLElement === "object" ? items instanceof HTMLElement : items && typeof items === "object" && items !== null && items.nodeType === 1 && typeof items.nodeName==="string") { items = [items]; } var options = arguments[1], args = Array.prototype.slice.call(arguments, 2), len = items.length, k = 0, ret; for (k; k < len; k++) { if (typeof options === 'object' || typeof options === 'undefined') { if(!items[k].jarallax) { items[k].jarallax = new Jarallax(items[k], options); } } else if(items[k].jarallax) { ret = items[k].jarallax[options].apply(items[k].jarallax, args); } if (typeof ret !== 'undefined') { return ret; } } return items; }; plugin.constructor = Jarallax; // no conflict var oldPlugin = window.jarallax; window.jarallax = plugin; window.jarallax.noConflict = function () { window.jarallax = oldPlugin; return this; }; // jQuery support if(typeof jQuery !== 'undefined') { var jQueryPlugin = function () { var args = arguments || []; Array.prototype.unshift.call(args, this); var res = plugin.apply(window, args); return typeof res !== 'object' ? res : this; }; jQueryPlugin.constructor = Jarallax; // no conflict var oldJqPlugin = jQuery.fn.jarallax; jQuery.fn.jarallax = jQueryPlugin; jQuery.fn.jarallax.noConflict = function () { jQuery.fn.jarallax = oldJqPlugin; return this; }; } // data-jarallax initialization addEventListener(window, 'DOMContentLoaded', function () { plugin(document.querySelectorAll('[data-jarallax], [data-jarallax-video]')); }); }(window));