/*! * Name : Just Another Parallax [Jarallax] * Version : 1.7.3 * 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' }; // 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 && 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); // 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; } // 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));