PageSourceSearch

https://munksjo.com/wp-content/themes/uncode/library/js/lib/rellax.js?ver=1202972420

js munksjo.com collected 2026-09-24 22:58:07 UTC 17,819 bytes, 497 lines download raw bytes

vendor: 17,819 bytes, lines 1-497
1
2// ------------------------------------------
3// Rellax.js
4// Buttery smooth parallax library
5// Copyright (c) 2016 Moe Amaya (@moeamaya)
6// MIT license
7//
8// Thanks to Paraxify.js and Jaime Cabllero
9// for parallax concepts
10// ------------------------------------------
11
12(function (root, factory) {
13	if (typeof define === 'function' && define.amd) {
14		// AMD. Register as an anonymous module.
15		define([], factory);
16	} else if (typeof module === 'object' && module.exports) {
17		// Node. Does not work with strict CommonJS, but
18		// only CommonJS-like environments that support module.exports,
19		// like Node.
20		module.exports = factory();
21	} else {
22		// Browser globals (root is window)
23		root.Rellax = factory();
24	}
25}(typeof window !== "undefined" ? window : global, function () {
26	var Rellax = function(el, options){
27		"use strict";
28
29		var self = Object.create(Rellax.prototype);
30
31		var posY = 0;
32		var screenY = 0;
33		var posX = 0;
34		var screenX = 0;
35		var blocks = [];
36		var pause = true;
37
38		// check what requestAnimationFrame to use, and if
39		// it's not supported, use the onscroll event
40		var loop = window.requestAnimationFrame ||
41			window.webkitRequestAnimationFrame ||
42			window.mozRequestAnimationFrame ||
43			window.msRequestAnimationFrame ||
44			window.oRequestAnimationFrame ||
45			function(callback){ return setTimeout(callback, 1000 / 60); };
46
47		// store the id for later use
48		var loopId = null;
49
50		// Test via a getter in the options object to see if the passive property is accessed
51		var supportsPassive = false;
52		try {
53			var opts = Object.defineProperty({}, 'passive', {
54				get: function() {
55					supportsPassive = true;
56				}
57			});
58			window.addEventListener("testPassive", null, opts);
59			window.removeEventListener("testPassive", null, opts);
60		} catch (e) {}
61
62		// check what cancelAnimation method to use
63		var clearLoop = window.cancelAnimationFrame || window.mozCancelAnimationFrame || clearTimeout;
64
65		// check which transform property to use
66		var transformProp = window.transformProp || (function(){
67				var testEl = document.createElement('div');
68				if (testEl.style.transform === null) {
69					var vendors = ['Webkit', 'Moz', 'ms'];
70					for (var vendor in vendors) {
71						if (testEl.style[ vendors[vendor] + 'Transform' ] !== undefined) {
72							return vendors[vendor] + 'Transform';
73						}
74					}
75				}
76				return 'transform';
77			})();
78
79		// Default Settings
80		self.options = {
81			speed: -2,
82			verticalSpeed: null,
83			horizontalSpeed: null,
84			breakpoints: [576, 768, 1201],
85			center: false,
86			wrapper: null,
87			relativeToWrapper: false,
88			round: true,
89			vertical: true,
90			horizontal: false,
91			verticalScrollAxis: "y",
92			horizontalScrollAxis: "x",
93			callback: function() {},
94		};
95
96		// User defined options (might have more in the future)
97		if (options){
98			Object.keys(options).forEach(function(key){
99				self.options[key] = options[key];
100			});
101		}
102
103		function validateCustomBreakpoints () {
104			if (self.options.breakpoints.length === 3 && Array.isArray(self.options.breakpoints)) {
105				var isAscending = true;
106				var isNumerical = true;
107				var lastVal;
108				self.options.breakpoints.forEach(function (i) {
109					if (typeof i !== 'number') isNumerical = false;
110					if (lastVal !== null) {
111						if (i < lastVal) isAscending = false;
112					}
113					lastVal = i;
114				});
115				if (isAscending && isNumerical) return;
116			}
117			// revert defaults if set incorrectly
118			self.options.breakpoints = [576, 768, 1201];
119			console.warn("Rellax: You must pass an array of 3 numbers in ascending order to the breakpoints option. Defaults reverted");
120		}
121
122		if (options && options.breakpoints) {
123			validateCustomBreakpoints();
124		}
125
126		// By default, rellax class
127		if (!el) {
128			el = '.rellax';
129		}
130
131		// check if el is a className or a node
132		var elements = typeof el === 'string' ? document.querySelectorAll(el) : [el];
133
134		// Now query selector
135		if (elements.length > 0) {
136			self.elems = elements;
137		}
138
139		// The elements don't exist
140		else {
141			console.warn("Rellax: The elements you're trying to select don't exist.");
142			return;
143		}
144
145		// Has a wrapper and it exists
146		if (self.options.wrapper) {
147			if (!self.options.wrapper.nodeType) {
148				var wrapper = document.querySelector(self.options.wrapper);
149
150				if (wrapper) {
151					self.options.wrapper = wrapper;
152				} else {
153					console.warn("Rellax: The wrapper you're trying to use doesn't exist.");
154					return;
155				}
156			}
157		}
158
159		// set a placeholder for the current breakpoint
160		var currentBreakpoint;
161
162		// helper to determine current breakpoint
163		var getCurrentBreakpoint = function (w) {
164			var bp = self.options.breakpoints;
165			if (w < bp[0]) return 'xs';
166			if (w >= bp[0] && w < bp[1]) return 'sm';
167			if (w >= bp[1] && w < bp[2]) return 'md';
168			return 'lg';
169		};
170
171		// Get and cache initial position of all elements
172		var cacheBlocks = function() {
173			for (var i = 0; i < self.elems.length; i++){
174				var block = createBlock(self.elems[i]);
175				blocks.push(block);
176			}
177		};
178
179
180		// Let's kick this script off
181		// Build array for cached element values
182		var init = function() {
183			for (var i = 0; i < blocks.length; i++){
184				self.elems[i].style.cssText = blocks[i].style;
185			}
186
187			blocks = [];
188
189			screenY = window.innerHeight;
190			screenX = window.innerWidth;
191			currentBreakpoint = getCurrentBreakpoint(screenX);
192
193			setPosition();
194
195			cacheBlocks();
196
197			animate();
198
199			// If paused, unpause and set listener for window resizing events
200			if (pause) {
201				window.addEventListener('resize', init);
202				pause = false;
203				// Start the loop
204				update();
205			}
206		};
207
208		// We want to cache the parallax blocks'
209		// values: base, top, height, speed
210		// el: is dom object, return: el cache values
211		var createBlock = function(el) {
212			var dataPercentage = el.getAttribute( 'data-rellax-percentage' );
213			var dataSpeed = el.getAttribute( 'data-rellax-speed' );
214			var dataXsSpeed = el.getAttribute( 'data-rellax-xs-speed' );
215			var dataMobileSpeed = el.getAttribute( 'data-rellax-mobile-speed' );
216			var dataTabletSpeed = el.getAttribute( 'data-rellax-tablet-speed' );
217			var dataDesktopSpeed = el.getAttribute( 'data-rellax-desktop-speed' );
218			var dataVerticalSpeed = el.getAttribute('data-rellax-vertical-speed');
219			var dataHorizontalSpeed = el.getAttribute('data-rellax-horizontal-speed');
220			var dataVericalScrollAxis = el.getAttribute('data-rellax-vertical-scroll-axis');
221			var dataHorizontalScrollAxis = el.getAttribute('data-rellax-horizontal-scroll-axis');
222			var dataZindex = el.getAttribute( 'data-rellax-zindex' ) || 0;
223			var dataMin = el.getAttribute( 'data-rellax-min' );
224			var dataMax = el.getAttribute( 'data-rellax-max' );
225			var dataMinX = el.getAttribute('data-rellax-min-x');
226			var dataMaxX = el.getAttribute('data-rellax-max-x');
227			var dataMinY = el.getAttribute('data-rellax-min-y');
228			var dataMaxY = el.getAttribute('data-rellax-max-y');
229			var mapBreakpoints;
230			var breakpoints = true;
231
232			if (!dataXsSpeed && !dataMobileSpeed && !dataTabletSpeed && !dataDesktopSpeed) {
233				breakpoints = false;
234			} else {
235				mapBreakpoints = {
236					'xs': dataXsSpeed,
237					'sm': dataMobileSpeed,
238					'md': dataTabletSpeed,
239					'lg': dataDesktopSpeed
240				};
241			}
242
243			// initializing at scrollY = 0 (top of browser), scrollX = 0 (left of browser)
244			// ensures elements are positioned based on HTML layout.
245			//
246			// If the element has the percentage attribute, the posY and posX needs to be
247			// the current scroll position's value, so that the elements are still positioned based on HTML layout
248			var wrapperPosY = self.options.wrapper ? self.options.wrapper.scrollTop : (window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop);
249			// If the option relativeToWrapper is true, use the wrappers offset to top, subtracted from the current page scroll.
250			if (self.options.relativeToWrapper) {
251				var scrollPosY = (window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop);
252				wrapperPosY = scrollPosY - self.options.wrapper.offsetTop;
253			}
254			var posY = self.options.vertical ? ( dataPercentage || self.options.center ? wrapperPosY : 0 ) : 0;
255			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;
256
257			var blockTop = posY + el.getBoundingClientRect().top;
258			var blockHeight = el.clientHeight || el.offsetHeight || el.scrollHeight;
259
260			var blockLeft = posX + el.getBoundingClientRect().left;
261			var blockWidth = el.clientWidth || el.offsetWidth || el.scrollWidth;
262
263			// apparently parallax equation everyone uses
264			var percentageY = dataPercentage ? dataPercentage : (posY - blockTop + screenY) / (blockHeight + screenY);
265			var percentageX = dataPercentage ? dataPercentage : (posX - blockLeft + screenX) / (blockWidth + screenX);
266			if(self.options.center){ percentageX = 0.5; percentageY = 0.5; }
267
268			// Optional individual block speed as data attr, otherwise global speed
269			var speed = (breakpoints && mapBreakpoints[currentBreakpoint] !== null) ? Number(mapBreakpoints[currentBreakpoint]) : (dataSpeed ? dataSpeed : self.options.speed);
270			var verticalSpeed = dataVerticalSpeed ? dataVerticalSpeed : self.options.verticalSpeed;
271			var horizontalSpeed = dataHorizontalSpeed ? dataHorizontalSpeed : self.options.horizontalSpeed;
272
273			// Optional individual block movement axis direction as data attr, otherwise global movement direction
274			var verticalScrollAxis = dataVericalScrollAxis ? dataVericalScrollAxis : self.options.verticalScrollAxis;
275			var horizontalScrollAxis = dataHorizontalScrollAxis ? dataHorizontalScrollAxis : self.options.horizontalScrollAxis;
276
277			var bases = updatePosition(percentageX, percentageY, speed, verticalSpeed, horizontalSpeed);
278
279			// ~~Store non-translate3d transforms~~
280			// Store inline styles and extract transforms
281			var style = el.style.cssText;
282			var transform = '';
283
284			// Check if there's an inline styled transform
285			var searchResult = /transform\s*:/i.exec(style);
286			if (searchResult) {
287				// Get the index of the transform
288				var index = searchResult.index;
289
290				// Trim the style to the transform point and get the following semi-colon index
291				var trimmedStyle = style.slice(index);
292				var delimiter = trimmedStyle.indexOf(';');
293
294				// Remove "transform" string and save the attribute
295				if (delimiter) {
296					transform = " " + trimmedStyle.slice(11, delimiter).replace(/\s/g,'');
297				} else {
298					transform = " " + trimmedStyle.slice(11).replace(/\s/g,'');
299				}
300			}
301
302			return {
303				baseX: bases.x,
304				baseY: bases.y,
305				top: blockTop,
306				left: blockLeft,
307				height: blockHeight,
308				width: blockWidth,
309				speed: speed,
310				verticalSpeed: verticalSpeed,
311				horizontalSpeed: horizontalSpeed,
312				verticalScrollAxis: verticalScrollAxis,
313				horizontalScrollAxis: horizontalScrollAxis,
314				style: style,
315				transform: transform,
316				zindex: dataZindex,
317				min: dataMin,
318				max: dataMax,
319				minX: dataMinX,
320				maxX: dataMaxX,
321				minY: dataMinY,
322				maxY: dataMaxY
323			};
324		};
325
326		// set scroll position (posY, posX)
327		// side effect method is not ideal, but okay for now
328		// returns true if the scroll changed, false if nothing happened
329		var setPosition = function() {
330			var oldY = posY;
331			var oldX = posX;
332
333			posY = self.options.wrapper ? self.options.wrapper.scrollTop : (document.documentElement || document.body.parentNode || document.body).scrollTop || window.pageYOffset;
334			posX = self.options.wrapper ? self.options.wrapper.scrollLeft : (document.documentElement || document.body.parentNode || document.body).scrollLeft || window.pageXOffset;
335			// If option relativeToWrapper is true, use relative wrapper value instead.
336			if (self.options.relativeToWrapper) {
337				var scrollPosY = (document.documentElement || document.body.parentNode || document.body).scrollTop || window.pageYOffset;
338				posY = scrollPosY - self.options.wrapper.offsetTop;
339			}
340
341
342			if (oldY != posY && self.options.vertical) {
343				// scroll changed, return true
344				return true;
345			}
346
347			if (oldX != posX && self.options.horizontal) {
348				// scroll changed, return true
349				return true;
350			}
351
352			// scroll did not change
353			return false;
354		};
355
356		// Ahh a pure function, gets new transform value
357		// based on scrollPosition and speed
358		// Allow for decimal pixel values
359		var updatePosition = function(percentageX, percentageY, speed, verticalSpeed, horizontalSpeed) {
360			var result = {};
361			var valueX = ((horizontalSpeed ? horizontalSpeed : speed) * (100 * (1 - percentageX)));
362			var valueY = ((verticalSpeed ? verticalSpeed : speed) * (100 * (1 - percentageY)));
363
364			result.x = self.options.round ? Math.round(valueX) : Math.round(valueX * 100) / 100;
365			result.y = self.options.round ? Math.round(valueY) : Math.round(valueY * 100) / 100;
366
367			return result;
368		};
369
370		// Remove event listeners and loop again
371		var deferredUpdate = function() {
372			window.removeEventListener('resize', deferredUpdate);
373			window.removeEventListener('orientationchange', deferredUpdate);
374			(self.options.wrapper ? self.options.wrapper : window).removeEventListener('scroll', deferredUpdate);
375			(self.options.wrapper ? self.options.wrapper : document).removeEventListener('touchmove', deferredUpdate);
376
377			// loop again
378			loopId = loop(update);
379		};
380
381		// Loop
382		var update = function() {
383			if (setPosition() && pause === false) {
384				animate();
385
386				// loop again
387				loopId = loop(update);
388			} else {
389				loopId = null;
390
391				// Don't animate until we get a position updating event
392				window.addEventListener('resize', deferredUpdate);
393				window.addEventListener('orientationchange', deferredUpdate);
394				(self.options.wrapper ? self.options.wrapper : window).addEventListener('scroll', deferredUpdate, supportsPassive ? { passive: true } : false);
395				(self.options.wrapper ? self.options.wrapper : document).addEventListener('touchmove', deferredUpdate, supportsPassive ? { passive: true } : false);
396			}
397		};
398
399		// Transform3d on parallax element
400		var animate = function() {
401			var positions;
402			for (var i = 0; i < self.elems.length; i++){
403				// Determine relevant movement directions
404				var verticalScrollAxis = blocks[i].verticalScrollAxis.toLowerCase();
405				var horizontalScrollAxis = blocks[i].horizontalScrollAxis.toLowerCase();
406				var verticalScrollX = verticalScrollAxis.indexOf("x") != -1 ? posY : 0;
407				var verticalScrollY = verticalScrollAxis.indexOf("y") != -1 ? posY : 0;
408				var horizontalScrollX = horizontalScrollAxis.indexOf("x") != -1 ? posX : 0;
409				var horizontalScrollY = horizontalScrollAxis.indexOf("y") != -1 ? posX : 0;
410
411				var percentageY = ((verticalScrollY + horizontalScrollY - blocks[i].top + screenY) / (blocks[i].height + screenY));
412				var percentageX = ((verticalScrollX + horizontalScrollX - blocks[i].left + screenX) / (blocks[i].width + screenX));
413
414				// Subtracting initialize value, so element stays in same spot as HTML
415				positions = updatePosition(percentageX, percentageY, blocks[i].speed, blocks[i].verticalSpeed, blocks[i].horizontalSpeed);
416				var positionY = positions.y - blocks[i].baseY;
417				var positionX = positions.x - blocks[i].baseX;
418
419				// The next two "if" blocks go like this:
420				// Check if a limit is defined (first "min", then "max");
421				// Check if we need to change the Y or the X
422				// (Currently working only if just one of the axes is enabled)
423				// Then, check if the new position is inside the allowed limit
424				// If so, use new position. If not, set position to limit.
425
426				// Check if a min limit is defined
427				if (blocks[i].min !== null) {
428					if (self.options.vertical && !self.options.horizontal) {
429						positionY = positionY <= blocks[i].min ? blocks[i].min : positionY;
430					}
431					if (self.options.horizontal && !self.options.vertical) {
432						positionX = positionX <= blocks[i].min ? blocks[i].min : positionX;
433					}
434				}
435
436				// Check if directional min limits are defined
437				if (blocks[i].minY != null) {
438						positionY = positionY <= blocks[i].minY ? blocks[i].minY : positionY;
439				}
440				if (blocks[i].minX != null) {
441						positionX = positionX <= blocks[i].minX ? blocks[i].minX : positionX;
442				}
443
444				// Check if a max limit is defined
445				if (blocks[i].max !== null) {
446					if (self.options.vertical && !self.options.horizontal) {
447						positionY = positionY >= blocks[i].max ? blocks[i].max : positionY;
448					}
449					if (self.options.horizontal && !self.options.vertical) {
450						positionX = positionX >= blocks[i].max ? blocks[i].max : positionX;
451					}
452				}
453
454				// Check if directional max limits are defined
455				if (blocks[i].maxY != null) {
456						positionY = positionY >= blocks[i].maxY ? blocks[i].maxY : positionY;
457				}
458				if (blocks[i].maxX != null) {
459						positionX = positionX >= blocks[i].maxX ? blocks[i].maxX : positionX;
460				}
461
462				var zindex = blocks[i].zindex;
463
464				// Move that element
465				// (Set the new translation and append initial inline transforms.)
466				var translate = 'translate3d(' + (self.options.horizontal ? positionX : '0') + 'px,' + (self.options.vertical ? positionY : '0') + 'px,' + zindex + 'px) ' + blocks[i].transform;
467				self.elems[i].style[transformProp] = translate;
468			}
469			self.options.callback(positions);
470		};
471
472		self.destroy = function() {
473			for (var i = 0; i < self.elems.length; i++){
474				self.elems[i].style.cssText = blocks[i].style;
475			}
476
477			// Remove resize event listener if not pause, and pause
478			if (!pause) {
479				window.removeEventListener('resize', init);
480				pause = true;
481			}
482
483			// Clear the animation loop to prevent possible memory leak
484			clearLoop(loopId);
485			loopId = null;
486		};
487
488		// Init
489		init();
490
491		// Allow to recalculate the initial values whenever we want
492		self.refresh = init;
493
494		return self;
495	};
496	return Rellax;
497}));

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.