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;
- }));
|