| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497 | 
// ------------------------------------------// Rellax.js// Buttery smooth parallax library// Copyright (c) 2016 Moe Amaya (@moeamaya)// MIT license//// Thanks to Paraxify.js and Jaime Cabllero// for parallax concepts// ------------------------------------------(function (root, factory) {  if (typeof define === 'function' && define.amd) {    // AMD. Register as an anonymous module.    define([], factory);  } else if (typeof module === 'object' && module.exports) {    // Node. Does not work with strict CommonJS, but    // only CommonJS-like environments that support module.exports,    // like Node.    module.exports = factory();  } else {    // Browser globals (root is window)    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;    // check what requestAnimationFrame to use, and if    // it's not supported, use the onscroll event    var loop = window.requestAnimationFrame ||      window.webkitRequestAnimationFrame ||      window.mozRequestAnimationFrame ||      window.msRequestAnimationFrame ||      window.oRequestAnimationFrame ||      function(callback){ return setTimeout(callback, 1000 / 60); };    // store the id for later use    var loopId = null;    // Test via a getter in the options object to see if the passive property is accessed    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) {}    // check what cancelAnimation method to use    var clearLoop = window.cancelAnimationFrame || window.mozCancelAnimationFrame || clearTimeout;    // check which transform property to use    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';      })();    // Default Settings    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() {},    };    // User defined options (might have more in the future)    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;      }      // revert defaults if set incorrectly      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();    }    // By default, rellax class    if (!el) {      el = '.rellax';    }    // check if el is a className or a node    var elements = typeof el === 'string' ? document.querySelectorAll(el) : [el];    // Now query selector    if (elements.length > 0) {      self.elems = elements;    }    // The elements don't exist    else {      console.warn("Rellax: The elements you're trying to select don't exist.");      return;    }    // Has a wrapper and it exists    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;        }      }    }    // set a placeholder for the current breakpoint    var currentBreakpoint;    // helper to determine current breakpoint    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';    };    // Get and cache initial position of all elements    var cacheBlocks = function() {      for (var i = 0; i < self.elems.length; i++){        var block = createBlock(self.elems[i]);        blocks.push(block);      }    };    // Let's kick this script off    // Build array for cached element values    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 paused, unpause and set listener for window resizing events      if (pause) {        window.addEventListener('resize', init);        pause = false;        // Start the loop        update();      }    };    // We want to cache the parallax blocks'    // values: base, top, height, speed    // el: is dom object, return: el cache values    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        };      }      // initializing at scrollY = 0 (top of browser), scrollX = 0 (left of browser)      // ensures elements are positioned based on HTML layout.      //      // If the element has the percentage attribute, the posY and posX needs to be      // the current scroll position's value, so that the elements are still positioned based on HTML layout      var wrapperPosY = self.options.wrapper ? self.options.wrapper.scrollTop : (window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop);      // If the option relativeToWrapper is true, use the wrappers offset to top, subtracted from the current page scroll.      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;      // apparently parallax equation everyone uses      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; }      // Optional individual block speed as data attr, otherwise global speed      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;      // Optional individual block movement axis direction as data attr, otherwise gobal movement direction      var verticalScrollAxis = dataVericalScrollAxis ? dataVericalScrollAxis : self.options.verticalScrollAxis;      var horizontalScrollAxis = dataHorizontalScrollAxis ? dataHorizontalScrollAxis : self.options.horizontalScrollAxis;      var bases = updatePosition(percentageX, percentageY, speed, verticalSpeed, horizontalSpeed);      // ~~Store non-translate3d transforms~~      // Store inline styles and extract transforms      var style = el.style.cssText;      var transform = '';      // Check if there's an inline styled transform      var searchResult = /transform\s*:/i.exec(style);      if (searchResult) {        // Get the index of the transform        var index = searchResult.index;        // Trim the style to the transform point and get the following semi-colon index        var trimmedStyle = style.slice(index);        var delimiter = trimmedStyle.indexOf(';');        // Remove "transform" string and save the attribute        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      };    };    // set scroll position (posY, posX)    // side effect method is not ideal, but okay for now    // returns true if the scroll changed, false if nothing happened    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 option relativeToWrapper is true, use relative wrapper value instead.      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) {        // scroll changed, return true        return true;      }      if (oldX != posX && self.options.horizontal) {        // scroll changed, return true        return true;      }      // scroll did not change      return false;    };    // Ahh a pure function, gets new transform value    // based on scrollPosition and speed    // Allow for decimal pixel values    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;    };    // Remove event listeners and loop again    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);      // loop again      loopId = loop(update);    };    // Loop    var update = function() {      if (setPosition() && pause === false) {        animate();        // loop again        loopId = loop(update);      } else {        loopId = null;        // Don't animate until we get a position updating event        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);      }    };    // Transform3d on parallax element    var animate = function() {      var positions;      for (var i = 0; i < self.elems.length; i++){        // Determine relevant movement directions        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));        // Subtracting initialize value, so element stays in same spot as HTML        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;        // The next two "if" blocks go like this:        // Check if a limit is defined (first "min", then "max");        // Check if we need to change the Y or the X        // (Currently working only if just one of the axes is enabled)        // Then, check if the new position is inside the allowed limit        // If so, use new position. If not, set position to limit.        // Check if a min limit is defined        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;          }        }        // Check if directional min limits are defined        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;        }        // Check if a max limit is defined        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;          }        }        // Check if directional max limits are defined        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;        // Move that element        // (Set the new translation and append initial inline transforms.)        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;      }      // Remove resize event listener if not pause, and pause      if (!pause) {        window.removeEventListener('resize', init);        pause = true;      }      // Clear the animation loop to prevent possible memory leak      clearLoop(loopId);      loopId = null;    };    // Init    init();    // Allow to recalculate the initial values whenever we want    self.refresh = init;    return self;  };  return Rellax;}));
 |