|
@@ -0,0 +1,497 @@
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+(function (root, factory) {
|
|
|
+ if (typeof define === 'function' && define.amd) {
|
|
|
+
|
|
|
+ define([], factory);
|
|
|
+ } else if (typeof module === 'object' && module.exports) {
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+ module.exports = factory();
|
|
|
+ } else {
|
|
|
+
|
|
|
+ root.Rellax = factory();
|
|
|
+ }
|
|
|
+}(typeof window !== "undefined" ? window : global, function () {
|
|
|
+ var Rellax = function(el, options){
|
|
|
+ "use strict";
|
|
|
+
|
|
|
+ var self = Object.create(Rellax.prototype);
|
|
|
+
|
|
|
+ var posY = 0;
|
|
|
+ var screenY = 0;
|
|
|
+ var posX = 0;
|
|
|
+ var screenX = 0;
|
|
|
+ var blocks = [];
|
|
|
+ var pause = true;
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+ var loop = window.requestAnimationFrame ||
|
|
|
+ window.webkitRequestAnimationFrame ||
|
|
|
+ window.mozRequestAnimationFrame ||
|
|
|
+ window.msRequestAnimationFrame ||
|
|
|
+ window.oRequestAnimationFrame ||
|
|
|
+ function(callback){ return setTimeout(callback, 1000 / 60); };
|
|
|
+
|
|
|
+
|
|
|
+ var loopId = null;
|
|
|
+
|
|
|
+
|
|
|
+ var supportsPassive = false;
|
|
|
+ try {
|
|
|
+ var opts = Object.defineProperty({}, 'passive', {
|
|
|
+ get: function() {
|
|
|
+ supportsPassive = true;
|
|
|
+ }
|
|
|
+ });
|
|
|
+ window.addEventListener("testPassive", null, opts);
|
|
|
+ window.removeEventListener("testPassive", null, opts);
|
|
|
+ } catch (e) {}
|
|
|
+
|
|
|
+
|
|
|
+ var clearLoop = window.cancelAnimationFrame || window.mozCancelAnimationFrame || clearTimeout;
|
|
|
+
|
|
|
+
|
|
|
+ var transformProp = window.transformProp || (function(){
|
|
|
+ var testEl = document.createElement('div');
|
|
|
+ if (testEl.style.transform === null) {
|
|
|
+ var vendors = ['Webkit', 'Moz', 'ms'];
|
|
|
+ for (var vendor in vendors) {
|
|
|
+ if (testEl.style[ vendors[vendor] + 'Transform' ] !== undefined) {
|
|
|
+ return vendors[vendor] + 'Transform';
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return 'transform';
|
|
|
+ })();
|
|
|
+
|
|
|
+
|
|
|
+ self.options = {
|
|
|
+ speed: -2,
|
|
|
+ verticalSpeed: null,
|
|
|
+ horizontalSpeed: null,
|
|
|
+ breakpoints: [576, 768, 1201],
|
|
|
+ center: false,
|
|
|
+ wrapper: null,
|
|
|
+ relativeToWrapper: false,
|
|
|
+ round: true,
|
|
|
+ vertical: true,
|
|
|
+ horizontal: false,
|
|
|
+ verticalScrollAxis: "y",
|
|
|
+ horizontalScrollAxis: "x",
|
|
|
+ callback: function() {},
|
|
|
+ };
|
|
|
+
|
|
|
+
|
|
|
+ if (options){
|
|
|
+ Object.keys(options).forEach(function(key){
|
|
|
+ self.options[key] = options[key];
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ function validateCustomBreakpoints () {
|
|
|
+ if (self.options.breakpoints.length === 3 && Array.isArray(self.options.breakpoints)) {
|
|
|
+ var isAscending = true;
|
|
|
+ var isNumerical = true;
|
|
|
+ var lastVal;
|
|
|
+ self.options.breakpoints.forEach(function (i) {
|
|
|
+ if (typeof i !== 'number') isNumerical = false;
|
|
|
+ if (lastVal !== null) {
|
|
|
+ if (i < lastVal) isAscending = false;
|
|
|
+ }
|
|
|
+ lastVal = i;
|
|
|
+ });
|
|
|
+ if (isAscending && isNumerical) return;
|
|
|
+ }
|
|
|
+
|
|
|
+ self.options.breakpoints = [576, 768, 1201];
|
|
|
+ console.warn("Rellax: You must pass an array of 3 numbers in ascending order to the breakpoints option. Defaults reverted");
|
|
|
+ }
|
|
|
+
|
|
|
+ if (options && options.breakpoints) {
|
|
|
+ validateCustomBreakpoints();
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ if (!el) {
|
|
|
+ el = '.rellax';
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ var elements = typeof el === 'string' ? document.querySelectorAll(el) : [el];
|
|
|
+
|
|
|
+
|
|
|
+ if (elements.length > 0) {
|
|
|
+ self.elems = elements;
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ else {
|
|
|
+ console.warn("Rellax: The elements you're trying to select don't exist.");
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ if (self.options.wrapper) {
|
|
|
+ if (!self.options.wrapper.nodeType) {
|
|
|
+ var wrapper = document.querySelector(self.options.wrapper);
|
|
|
+
|
|
|
+ if (wrapper) {
|
|
|
+ self.options.wrapper = wrapper;
|
|
|
+ } else {
|
|
|
+ console.warn("Rellax: The wrapper you're trying to use doesn't exist.");
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ var currentBreakpoint;
|
|
|
+
|
|
|
+
|
|
|
+ var getCurrentBreakpoint = function (w) {
|
|
|
+ var bp = self.options.breakpoints;
|
|
|
+ if (w < bp[0]) return 'xs';
|
|
|
+ if (w >= bp[0] && w < bp[1]) return 'sm';
|
|
|
+ if (w >= bp[1] && w < bp[2]) return 'md';
|
|
|
+ return 'lg';
|
|
|
+ };
|
|
|
+
|
|
|
+
|
|
|
+ var cacheBlocks = function() {
|
|
|
+ for (var i = 0; i < self.elems.length; i++){
|
|
|
+ var block = createBlock(self.elems[i]);
|
|
|
+ blocks.push(block);
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+ var init = function() {
|
|
|
+ for (var i = 0; i < blocks.length; i++){
|
|
|
+ self.elems[i].style.cssText = blocks[i].style;
|
|
|
+ }
|
|
|
+
|
|
|
+ blocks = [];
|
|
|
+
|
|
|
+ screenY = window.innerHeight;
|
|
|
+ screenX = window.innerWidth;
|
|
|
+ currentBreakpoint = getCurrentBreakpoint(screenX);
|
|
|
+
|
|
|
+ setPosition();
|
|
|
+
|
|
|
+ cacheBlocks();
|
|
|
+
|
|
|
+ animate();
|
|
|
+
|
|
|
+
|
|
|
+ if (pause) {
|
|
|
+ window.addEventListener('resize', init);
|
|
|
+ pause = false;
|
|
|
+
|
|
|
+ update();
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+ var createBlock = function(el) {
|
|
|
+ var dataPercentage = el.getAttribute( 'data-rellax-percentage' );
|
|
|
+ var dataSpeed = el.getAttribute( 'data-rellax-speed' );
|
|
|
+ var dataXsSpeed = el.getAttribute( 'data-rellax-xs-speed' );
|
|
|
+ var dataMobileSpeed = el.getAttribute( 'data-rellax-mobile-speed' );
|
|
|
+ var dataTabletSpeed = el.getAttribute( 'data-rellax-tablet-speed' );
|
|
|
+ var dataDesktopSpeed = el.getAttribute( 'data-rellax-desktop-speed' );
|
|
|
+ var dataVerticalSpeed = el.getAttribute('data-rellax-vertical-speed');
|
|
|
+ var dataHorizontalSpeed = el.getAttribute('data-rellax-horizontal-speed');
|
|
|
+ var dataVericalScrollAxis = el.getAttribute('data-rellax-vertical-scroll-axis');
|
|
|
+ var dataHorizontalScrollAxis = el.getAttribute('data-rellax-horizontal-scroll-axis');
|
|
|
+ var dataZindex = el.getAttribute( 'data-rellax-zindex' ) || 0;
|
|
|
+ var dataMin = el.getAttribute( 'data-rellax-min' );
|
|
|
+ var dataMax = el.getAttribute( 'data-rellax-max' );
|
|
|
+ var dataMinX = el.getAttribute('data-rellax-min-x');
|
|
|
+ var dataMaxX = el.getAttribute('data-rellax-max-x');
|
|
|
+ var dataMinY = el.getAttribute('data-rellax-min-y');
|
|
|
+ var dataMaxY = el.getAttribute('data-rellax-max-y');
|
|
|
+ var mapBreakpoints;
|
|
|
+ var breakpoints = true;
|
|
|
+
|
|
|
+ if (!dataXsSpeed && !dataMobileSpeed && !dataTabletSpeed && !dataDesktopSpeed) {
|
|
|
+ breakpoints = false;
|
|
|
+ } else {
|
|
|
+ mapBreakpoints = {
|
|
|
+ 'xs': dataXsSpeed,
|
|
|
+ 'sm': dataMobileSpeed,
|
|
|
+ 'md': dataTabletSpeed,
|
|
|
+ 'lg': dataDesktopSpeed
|
|
|
+ };
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+ var wrapperPosY = self.options.wrapper ? self.options.wrapper.scrollTop : (window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop);
|
|
|
+
|
|
|
+ if (self.options.relativeToWrapper) {
|
|
|
+ var scrollPosY = (window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop);
|
|
|
+ wrapperPosY = scrollPosY - self.options.wrapper.offsetTop;
|
|
|
+ }
|
|
|
+ var posY = self.options.vertical ? ( dataPercentage || self.options.center ? wrapperPosY : 0 ) : 0;
|
|
|
+ var posX = self.options.horizontal ? ( dataPercentage || self.options.center ? self.options.wrapper ? self.options.wrapper.scrollLeft : (window.pageXOffset || document.documentElement.scrollLeft || document.body.scrollLeft) : 0 ) : 0;
|
|
|
+
|
|
|
+ var blockTop = posY + el.getBoundingClientRect().top;
|
|
|
+ var blockHeight = el.clientHeight || el.offsetHeight || el.scrollHeight;
|
|
|
+
|
|
|
+ var blockLeft = posX + el.getBoundingClientRect().left;
|
|
|
+ var blockWidth = el.clientWidth || el.offsetWidth || el.scrollWidth;
|
|
|
+
|
|
|
+
|
|
|
+ var percentageY = dataPercentage ? dataPercentage : (posY - blockTop + screenY) / (blockHeight + screenY);
|
|
|
+ var percentageX = dataPercentage ? dataPercentage : (posX - blockLeft + screenX) / (blockWidth + screenX);
|
|
|
+ if(self.options.center){ percentageX = 0.5; percentageY = 0.5; }
|
|
|
+
|
|
|
+
|
|
|
+ var speed = (breakpoints && mapBreakpoints[currentBreakpoint] !== null) ? Number(mapBreakpoints[currentBreakpoint]) : (dataSpeed ? dataSpeed : self.options.speed);
|
|
|
+ var verticalSpeed = dataVerticalSpeed ? dataVerticalSpeed : self.options.verticalSpeed;
|
|
|
+ var horizontalSpeed = dataHorizontalSpeed ? dataHorizontalSpeed : self.options.horizontalSpeed;
|
|
|
+
|
|
|
+
|
|
|
+ var verticalScrollAxis = dataVericalScrollAxis ? dataVericalScrollAxis : self.options.verticalScrollAxis;
|
|
|
+ var horizontalScrollAxis = dataHorizontalScrollAxis ? dataHorizontalScrollAxis : self.options.horizontalScrollAxis;
|
|
|
+
|
|
|
+ var bases = updatePosition(percentageX, percentageY, speed, verticalSpeed, horizontalSpeed);
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+ var style = el.style.cssText;
|
|
|
+ var transform = '';
|
|
|
+
|
|
|
+
|
|
|
+ var searchResult = /transform\s*:/i.exec(style);
|
|
|
+ if (searchResult) {
|
|
|
+
|
|
|
+ var index = searchResult.index;
|
|
|
+
|
|
|
+
|
|
|
+ var trimmedStyle = style.slice(index);
|
|
|
+ var delimiter = trimmedStyle.indexOf(';');
|
|
|
+
|
|
|
+
|
|
|
+ if (delimiter) {
|
|
|
+ transform = " " + trimmedStyle.slice(11, delimiter).replace(/\s/g,'');
|
|
|
+ } else {
|
|
|
+ transform = " " + trimmedStyle.slice(11).replace(/\s/g,'');
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return {
|
|
|
+ baseX: bases.x,
|
|
|
+ baseY: bases.y,
|
|
|
+ top: blockTop,
|
|
|
+ left: blockLeft,
|
|
|
+ height: blockHeight,
|
|
|
+ width: blockWidth,
|
|
|
+ speed: speed,
|
|
|
+ verticalSpeed: verticalSpeed,
|
|
|
+ horizontalSpeed: horizontalSpeed,
|
|
|
+ verticalScrollAxis: verticalScrollAxis,
|
|
|
+ horizontalScrollAxis: horizontalScrollAxis,
|
|
|
+ style: style,
|
|
|
+ transform: transform,
|
|
|
+ zindex: dataZindex,
|
|
|
+ min: dataMin,
|
|
|
+ max: dataMax,
|
|
|
+ minX: dataMinX,
|
|
|
+ maxX: dataMaxX,
|
|
|
+ minY: dataMinY,
|
|
|
+ maxY: dataMaxY
|
|
|
+ };
|
|
|
+ };
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+ var setPosition = function() {
|
|
|
+ var oldY = posY;
|
|
|
+ var oldX = posX;
|
|
|
+
|
|
|
+ posY = self.options.wrapper ? self.options.wrapper.scrollTop : (document.documentElement || document.body.parentNode || document.body).scrollTop || window.pageYOffset;
|
|
|
+ posX = self.options.wrapper ? self.options.wrapper.scrollLeft : (document.documentElement || document.body.parentNode || document.body).scrollLeft || window.pageXOffset;
|
|
|
+
|
|
|
+ if (self.options.relativeToWrapper) {
|
|
|
+ var scrollPosY = (document.documentElement || document.body.parentNode || document.body).scrollTop || window.pageYOffset;
|
|
|
+ posY = scrollPosY - self.options.wrapper.offsetTop;
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ if (oldY != posY && self.options.vertical) {
|
|
|
+
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (oldX != posX && self.options.horizontal) {
|
|
|
+
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ return false;
|
|
|
+ };
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+ var updatePosition = function(percentageX, percentageY, speed, verticalSpeed, horizontalSpeed) {
|
|
|
+ var result = {};
|
|
|
+ var valueX = ((horizontalSpeed ? horizontalSpeed : speed) * (100 * (1 - percentageX)));
|
|
|
+ var valueY = ((verticalSpeed ? verticalSpeed : speed) * (100 * (1 - percentageY)));
|
|
|
+
|
|
|
+ result.x = self.options.round ? Math.round(valueX) : Math.round(valueX * 100) / 100;
|
|
|
+ result.y = self.options.round ? Math.round(valueY) : Math.round(valueY * 100) / 100;
|
|
|
+
|
|
|
+ return result;
|
|
|
+ };
|
|
|
+
|
|
|
+
|
|
|
+ var deferredUpdate = function() {
|
|
|
+ window.removeEventListener('resize', deferredUpdate);
|
|
|
+ window.removeEventListener('orientationchange', deferredUpdate);
|
|
|
+ (self.options.wrapper ? self.options.wrapper : window).removeEventListener('scroll', deferredUpdate);
|
|
|
+ (self.options.wrapper ? self.options.wrapper : document).removeEventListener('touchmove', deferredUpdate);
|
|
|
+
|
|
|
+
|
|
|
+ loopId = loop(update);
|
|
|
+ };
|
|
|
+
|
|
|
+
|
|
|
+ var update = function() {
|
|
|
+ if (setPosition() && pause === false) {
|
|
|
+ animate();
|
|
|
+
|
|
|
+
|
|
|
+ loopId = loop(update);
|
|
|
+ } else {
|
|
|
+ loopId = null;
|
|
|
+
|
|
|
+
|
|
|
+ window.addEventListener('resize', deferredUpdate);
|
|
|
+ window.addEventListener('orientationchange', deferredUpdate);
|
|
|
+ (self.options.wrapper ? self.options.wrapper : window).addEventListener('scroll', deferredUpdate, supportsPassive ? { passive: true } : false);
|
|
|
+ (self.options.wrapper ? self.options.wrapper : document).addEventListener('touchmove', deferredUpdate, supportsPassive ? { passive: true } : false);
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+
|
|
|
+ var animate = function() {
|
|
|
+ var positions;
|
|
|
+ for (var i = 0; i < self.elems.length; i++){
|
|
|
+
|
|
|
+ var verticalScrollAxis = blocks[i].verticalScrollAxis.toLowerCase();
|
|
|
+ var horizontalScrollAxis = blocks[i].horizontalScrollAxis.toLowerCase();
|
|
|
+ var verticalScrollX = verticalScrollAxis.indexOf("x") != -1 ? posY : 0;
|
|
|
+ var verticalScrollY = verticalScrollAxis.indexOf("y") != -1 ? posY : 0;
|
|
|
+ var horizontalScrollX = horizontalScrollAxis.indexOf("x") != -1 ? posX : 0;
|
|
|
+ var horizontalScrollY = horizontalScrollAxis.indexOf("y") != -1 ? posX : 0;
|
|
|
+
|
|
|
+ var percentageY = ((verticalScrollY + horizontalScrollY - blocks[i].top + screenY) / (blocks[i].height + screenY));
|
|
|
+ var percentageX = ((verticalScrollX + horizontalScrollX - blocks[i].left + screenX) / (blocks[i].width + screenX));
|
|
|
+
|
|
|
+
|
|
|
+ positions = updatePosition(percentageX, percentageY, blocks[i].speed, blocks[i].verticalSpeed, blocks[i].horizontalSpeed);
|
|
|
+ var positionY = positions.y - blocks[i].baseY;
|
|
|
+ var positionX = positions.x - blocks[i].baseX;
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+ if (blocks[i].min !== null) {
|
|
|
+ if (self.options.vertical && !self.options.horizontal) {
|
|
|
+ positionY = positionY <= blocks[i].min ? blocks[i].min : positionY;
|
|
|
+ }
|
|
|
+ if (self.options.horizontal && !self.options.vertical) {
|
|
|
+ positionX = positionX <= blocks[i].min ? blocks[i].min : positionX;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ if (blocks[i].minY != null) {
|
|
|
+ positionY = positionY <= blocks[i].minY ? blocks[i].minY : positionY;
|
|
|
+ }
|
|
|
+ if (blocks[i].minX != null) {
|
|
|
+ positionX = positionX <= blocks[i].minX ? blocks[i].minX : positionX;
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ if (blocks[i].max !== null) {
|
|
|
+ if (self.options.vertical && !self.options.horizontal) {
|
|
|
+ positionY = positionY >= blocks[i].max ? blocks[i].max : positionY;
|
|
|
+ }
|
|
|
+ if (self.options.horizontal && !self.options.vertical) {
|
|
|
+ positionX = positionX >= blocks[i].max ? blocks[i].max : positionX;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ if (blocks[i].maxY != null) {
|
|
|
+ positionY = positionY >= blocks[i].maxY ? blocks[i].maxY : positionY;
|
|
|
+ }
|
|
|
+ if (blocks[i].maxX != null) {
|
|
|
+ positionX = positionX >= blocks[i].maxX ? blocks[i].maxX : positionX;
|
|
|
+ }
|
|
|
+
|
|
|
+ var zindex = blocks[i].zindex;
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+ var translate = 'translate3d(' + (self.options.horizontal ? positionX : '0') + 'px,' + (self.options.vertical ? positionY : '0') + 'px,' + zindex + 'px) ' + blocks[i].transform;
|
|
|
+ self.elems[i].style[transformProp] = translate;
|
|
|
+ }
|
|
|
+ self.options.callback(positions);
|
|
|
+ };
|
|
|
+
|
|
|
+ self.destroy = function() {
|
|
|
+ for (var i = 0; i < self.elems.length; i++){
|
|
|
+ self.elems[i].style.cssText = blocks[i].style;
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ if (!pause) {
|
|
|
+ window.removeEventListener('resize', init);
|
|
|
+ pause = true;
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ clearLoop(loopId);
|
|
|
+ loopId = null;
|
|
|
+ };
|
|
|
+
|
|
|
+
|
|
|
+ init();
|
|
|
+
|
|
|
+
|
|
|
+ self.refresh = init;
|
|
|
+
|
|
|
+ return self;
|
|
|
+ };
|
|
|
+ return Rellax;
|
|
|
+}));
|