/*! * Name : Just Another Parallax [Jarallax] * Version : 1.7.1 * 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; } }()); } var supportTransform = (function () { if (!window.getComputedStyle) { return false; } var el = document.createElement('p'), has3d, transforms = { 'webkitTransform':'-webkit-transform', 'OTransform':'-o-transform', 'msTransform':'-ms-transform', 'MozTransform':'-moz-transform', 'transform':'transform' }; // Add it to the body to get the computed style. (document.body || document.documentElement).insertBefore(el, null); for (var t in transforms) { if (typeof el.style[t] !== 'undefined') { el.style[t] = "translate3d(1px,1px,1px)"; has3d = window.getComputedStyle(el).getPropertyValue(transforms[t]); } } (document.body || document.documentElement).removeChild(el); return typeof has3d !== 'undefined' && has3d.length > 0 && has3d !== "none"; }()); var isAndroid = navigator.userAgent.toLowerCase().indexOf('android') > -1; var isIOs = /iPad|iPhone|iPod/.test(navigator.userAgent) && !window.MSStream; var isOperaOld = !!window.opera; var isEdge = /Edge\/\d+/.test(navigator.userAgent); var isIE11 = /Trident.*rv[ :]*11\./.test(navigator.userAgent); var isIE10 = !!Function('/*@cc_on return document.documentMode===10@*/')(); var isIElt10 = document.all && !window.atob; 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, enableTransform : true, 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(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 || isOperaOld || isIE11 || isIE10 || isEdge }; 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) { styles.WebkitTransform = styles.MozTransform = 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 = { position : 'fixed' }; // 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 && supportTransform && _this.options.enableTransform) { _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); } // fix for IE9 and less if(isIElt10) { imageStyles.backgroundAttachment = 'fixed'; } // 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 _this.parentWithTransform = 0; var $itemParents = _this.$item; while ($itemParents !== null && $itemParents !== document && _this.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') { _this.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; } // 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); // save default user styles _this.$item.setAttribute('data-jarallax-original-styles', _this.$item.getAttribute('style')); // 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 scrollDist = speed * (contH + wndH) / 2; if(speed < 0 || speed > 1) { scrollDist = speed * Math.max(contH, wndH) / 2; } // size for scroll parallax if(speed < 0 || speed > 1) { resultH = Math.max(contH, wndH) + Math.abs(scrollDist) * 2; } else { resultH += Math.abs(wndH - contH) * (1 - speed); } } // calculate width relative to height and image size resultW = resultH * imgW / imgH; if(resultW < contW) { resultW = contW; resultH = resultW * imgH / imgW; } // when disabled transformations, height should be >= window height _this.bgPosVerticalCenter = 0; if(isScroll && resultH < wndH && (!supportTransform || !_this.options.enableTransform)) { _this.bgPosVerticalCenter = (wndH - resultH) / 2; resultH = wndH; } // 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 parents with transform style if(supportTransform && _this.options.enableTransform && _this.parentWithTransform) { 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 = { position : 'absolute', 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 = 'translate3d(0, 0, 0)'; 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 + ') translate3d(0, 0, 0)'; } // scroll if(_this.options.type === 'scroll' || _this.options.type === 'scroll-opacity') { var positionY = _this.parallaxScrollDistance * fromViewportCenter; if(supportTransform && _this.options.enableTransform) { // fix if parents with transform style if(_this.parentWithTransform) { positionY -= contT; } styles.transform = 'translate3d(0, ' + positionY + 'px, 0)'; } else if (_this.image.useImgTag) { styles.top = positionY + 'px'; } else { // vertical centering if(_this.bgPosVerticalCenter) { positionY += _this.bgPosVerticalCenter; } styles.backgroundPosition = '50% ' + positionY + 'px'; } // fixed position is not work properly for IE9 and less // solution - use absolute position and emulate fixed by using container offset styles.position = isIElt10 ? 'absolute' : 'fixed'; } _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));