PageSourceSearch

https://www.ggxrd.com/ac/js/skrollr.js

js ggxrd.com collected 2026-10-02 16:48:14 UTC 19,672 bytes, 755 lines download raw bytes

1/*! skrollr v0.3.11 https://github.com/Prinzhorn/skrollr | free to use under terms of MIT license */
2(function(window, document, undefined) {
3	'use strict';
4
5	//Used as a dummy function for event listeners.
6	var NOOP = function() {};
7
8	//Minify optimization.
9	var hasProp = Object.prototype.hasOwnProperty;
10	var documentElement = document.documentElement;
11	var body = document.body;
12
13	var HIDDEN_CLASS = 'hidden';
14	var SKROLLABLE_CLASS = 'skrollable';
15	var DEFAULT_EASING = 'linear';
16	var DEFAULT_DURATION = 1000;
17
18	var requestAnimFrame =
19		window.requestAnimationFrame ||
20		window.webkitRequestAnimationFrame ||
21		window.mozRequestAnimationFrame ||
22		window.oRequestAnimationFrame ||
23		window.msRequestAnimationFrame ||
24		function(fn) {
25			//Just 30 fps, because that's enough for those legacy browsers
26			window.setTimeout(fn, 1000 / 30);
27		};
28
29	var rxTrim = /^\s*(.*)\s$/;
30
31	//Find data-<number>, as well as data-end and data-end-<number>
32	var rxKeyframeAttribute = /^data(-end)?-?(\d+)?$/;
33
34	var rxPropSplit = /:|;/g;
35
36	//Easing function names follow the property in square brackets.
37	var rxPropEasing = /^([a-z\-]+)\[(\w+)\]$/;
38
39	var rxCamelCase = /-([a-z])/g;
40
41	//Numeric values with optional sign.
42	var rxNumericValue = /(:?\+|-)?[\d.]+/g;
43
44	//Finds rgb(a) colors, which don't use the percentage notation.
45	var rxRGBAIntegerColor = /rgba?\(\s*-?\d+\s*,\s*-?\d+\s*,\s*-?\d+/g;
46
47	//Finds all gradients.
48	var rxGradient = /[a-z\-]+-gradient/g;
49
50	//Only relevant prefixes. May be extended.
51	//Could be dangerous if there will ever be a CSS property which actually starts with "ms". Don't hope so.
52	var rxPrefixes = /^O|Moz|webkit|ms/;
53
54	var theCSSPrefix;
55	var theDashedCSSPrefix;
56
57	//Detect prefix for current browser by finding the first property using a prefix.
58	if(window.getComputedStyle) {
59		var style = window.getComputedStyle(body, null);
60
61		for(var k in style) {
62			//We check the key and if the key is a number, we check the value as well, because safari's getComputedStyle returns some weird array-like thingy.
63			theCSSPrefix = (k.match(rxPrefixes) || (+k == k && style[k].match(rxPrefixes)));
64
65			if(theCSSPrefix) {
66				break;
67			}
68		}
69	}
70
71	//Empty string if no prefix detected
72	theCSSPrefix = (theCSSPrefix || [''])[0];
73
74	//Will be "--" if no prefix detected. No problem, browser will ignore "--transform" and stuff.
75	theDashedCSSPrefix = '-' + theCSSPrefix.toLowerCase() + '-';
76
77	//Cleanup.
78	rxPrefixes = undefined;
79
80	//Built-in easing functions.
81	var easings = {
82		begin: function() {
83			return 0;
84		},
85		end: function() {
86			return 1;
87		},
88		linear: function(p) {
89			return p;
90		},
91		quadratic: function(p) {
92			return p * p;
93		},
94		cubic: function(p) {
95			return p * p * p;
96		},
97		swing: function(p) {
98			return (-Math.cos(p * Math.PI) / 2) + 0.5;
99		},
100		//see https://www.desmos.com/calculator/tbr20s8vd2 for how I did this
101		bounce: function(p) {
102			var a;
103
104			switch(true) {
105				case (p <= 0.5083):
106					a = 3; break;
107				case (p <= 0.8489):
108					a = 9; break;
109				case (p <= 0.96208):
110					a = 27; break;
111				case (p <= 0.99981):
112					a = 91; break;
113				default:
114					return 1;
115			}
116
117			return 1 - Math.abs(3 * Math.cos(p * a * 1.028) / a);
118		}
119	};
120
121	/**
122	 * Constructor.
123	 */
124	function Skrollr(options) {
125		_instance = this;
126
127		options = options || {};
128
129		//We allow defining custom easings or overwrite existing
130		if(options.easing) {
131			for(var e in options.easing) {
132				easings[e] = options.easing[e];
133			}
134		}
135
136		_listeners = {
137			//Function to be called right before rendering.
138			beforerender: options.beforerender || NOOP,
139
140			//Function to be called right after finishing rendering.
141			render: options.render || NOOP
142		};
143
144		//true is default, thus undefined is true as well.
145		_forceHeight = options.forceHeight !== false;
146
147		if(_forceHeight) {
148			_scale = options.scale || 1;
149		}
150
151		var allElements = document.getElementsByTagName('*');
152
153		//Iterate over all elements in document.
154		for(var i = 0; i < allElements.length; i++) {
155			var el = allElements[i];
156			var keyFrames = [];
157
158			if(!el.attributes) {
159				continue;
160			}
161
162			//Iterate over all attributes and search for key frame attributes.
163			for (var k = 0; k < el.attributes.length; k++) {
164				var attr = el.attributes[k];
165				var match = attr.name.match(rxKeyframeAttribute);
166
167				if(match !== null) {
168					var frame = (match[2] | 0) * _scale;
169
170					var kf = {
171						frame: frame,
172						props: attr.value
173					};
174
175					keyFrames.push(kf);
176
177					//special handling for data-end.
178					if(match[1] === '-end') {
179						kf.dataEnd = kf.frame;
180						_dataEndKeyFrames.push(kf);
181					}
182
183					if(frame > _maxKeyFrame) {
184						_maxKeyFrame = frame;
185					}
186				}
187			}
188
189			//Does this element have key frames?
190			if(keyFrames.length) {
191				_skrollables.push({
192					element: el,
193					keyFrames: keyFrames
194				});
195
196				_addClass(el, SKROLLABLE_CLASS);
197			}
198		}
199
200		var updateDataEnd = function() {
201			//Set all data-end key frames to max key frame
202			for(var i = 0; i < _dataEndKeyFrames.length; i++) {
203				var kf = _dataEndKeyFrames[i];
204				kf.frame = _maxKeyFrame - kf.dataEnd;
205			}
206		};
207
208		var onResize;
209
210		if(_forceHeight) {
211			//Add a dummy element in order to get a large enough scrollbar.
212			var dummy = document.createElement('div');
213			var dummyStyle = dummy.style;
214
215			dummyStyle.width = '1px';
216			dummyStyle.position = 'absolute';
217			dummyStyle.right = dummyStyle.top = dummyStyle.zIndex = '0';
218
219			body.appendChild(dummy);
220
221			//Update height of dummy div when window size is changed.
222			onResize = function() {
223				dummyStyle.height = (_maxKeyFrame + documentElement.clientHeight) + 'px';
224				updateDataEnd();
225			};
226		} else {
227			onResize = function() {
228				_maxKeyFrame = body.scrollHeight - documentElement.clientHeight;
229				updateDataEnd();
230				_forceRender = true;
231			};
232		}
233
234		_addEvent('resize', onResize);
235		onResize();
236
237		//Now that we got all key frame numbers right, actually parse the properties.
238		for(var i = 0; i < _skrollables.length; i++) {
239			var sk = _skrollables[i];
240
241			//Make sure they are in order
242			sk.keyFrames.sort(_keyFrameComparator);
243
244			//Parse the property string to objects
245			_parseProps(sk);
246
247			//Fill key frames with missing properties from left and right
248			_fillProps(sk);
249		}
250
251		//Clean up
252		allElements = options = undefined;
253
254		//Let's go
255		_render();
256
257		return _instance;
258	}
259
260	/**
261	 * Animates scroll top to new position.
262	 */
263	Skrollr.prototype.animateTo = function(top, options) {
264		options = options || {};
265
266		var now = _now();
267
268		//Setting this to a new value will automatically prevent the current animation to stop, if any.
269		_scrollAnimation = {
270			startTop: _instance.getScrollTop(),
271			topDiff: top - _instance.getScrollTop(),
272			targetTop: top,
273			duration: options.duration || DEFAULT_DURATION,
274			startTime: now,
275			endTime: now + (options.duration || DEFAULT_DURATION),
276			easing: easings[options.easing || DEFAULT_EASING],
277			done: options.done || NOOP
278		};
279
280		//Don't queue the animation if there's nothing to animate.
281		if(!_scrollAnimation.topDiff) {
282			_scrollAnimation.done.call(_instance);
283			_scrollAnimation = undefined;
284		}
285	};
286
287	Skrollr.prototype.setScrollTop = function(top) {
288		window.scroll(0, top);
289	};
290
291	Skrollr.prototype.getScrollTop = function(top) {
292		return window.pageYOffset || documentElement.scrollTop || body.scrollTop || 0;
293	};
294
295	Skrollr.prototype.on = function(name, fn) {
296		_listeners[name] = fn || NOOP;
297	};
298
299	Skrollr.prototype.off = function(name) {
300		_listeners[name] = NOOP;
301	};
302
303	/*
304		Private methods.
305	*/
306
307	/**
308	 * Calculate and sets the style properties for the element at the given frame
309	 */
310	var _calcSteps = function(skrollable, frame) {
311		var frames = skrollable.keyFrames;
312
313		//We are before the first frame, don't do anything
314		if(frame < frames[0].frame) {
315			_addClass(skrollable.element, HIDDEN_CLASS);
316		}
317		//We are after or at the last frame, the element gets all props from last key frame
318		else if(frame >= frames[frames.length - 1].frame) {
319			_removeClass(skrollable.element, HIDDEN_CLASS);
320
321			var last = frames[frames.length - 1];
322			var value;
323
324			for(var key in last.props) {
325				if(hasProp.call(last.props, key)) {
326					value = _interpolateString(last.props[key].value);
327
328					_setStyle(skrollable.element, key, value);
329				}
330			}
331		}
332		//We are between two frames
333		else {
334			_removeClass(skrollable.element, HIDDEN_CLASS);
335
336			//Find out between which two key frames we are right now
337			for(var i = 0; i < frames.length - 1; i++) {
338				if(frame >= frames[i].frame && frame <= frames[i + 1].frame) {
339					var left = frames[i];
340					var right = frames[i + 1];
341
342					for(var key in left.props) {
343						if(hasProp.call(left.props, key)) {
344							var progress = (frame - left.frame) / (right.frame - left.frame);
345
346							//Transform the current progress using the given easing function.
347							progress = left.props[key].easing(progress);
348
349							//Interpolate between the two values
350							var value = _calcInterpolation(left.props[key].value, right.props[key].value, progress);
351
352							value = _interpolateString(value);
353
354							_setStyle(skrollable.element, key, value);
355						}
356					}
357
358					break;
359				}
360			}
361		}
362	};
363
364	/**
365	 * Renders all elements
366	 */
367	var _render = function() {
368		//If there's an animation, which ends in current render call, call the callback after rendering;
369		var afterAnimationCallback = NOOP;
370
371		//Before actually rendering handle the scroll animation, if any.
372		if(_scrollAnimation) {
373			var now = _now();
374
375			//It's over
376			if(now >= _scrollAnimation.endTime) {
377				_instance.setScrollTop(_scrollAnimation.targetTop);
378				afterAnimationCallback = _scrollAnimation.done;
379				_scrollAnimation = undefined;
380			} else {
381				//Map the current progress to the new progress using given easing function.
382				var progress = _scrollAnimation.easing((now - _scrollAnimation.startTime) / _scrollAnimation.duration);
383
384				_instance.setScrollTop((_scrollAnimation.startTop + progress * _scrollAnimation.topDiff) | 0);
385			}
386		}
387
388		_curTop = _instance.getScrollTop();
389
390		//In OSX it's possible to have a negative scrolltop, so, we set it to zero.
391		if(_curTop < 0) {
392			_curTop = 0;
393		}
394
395		//Did the scroll position even change?
396		if(_forceRender || _lastTop !== _curTop) {
397			//Remember in which direction are we scrolling?
398			_direction = (_curTop >= _lastTop) ? 'down' : 'up';
399
400			_forceRender = false;
401
402			var listenerParams = {
403				curTop: _curTop,
404				lastTop: _lastTop,
405				maxTop: _maxKeyFrame,
406				direction: _direction
407			};
408
409			//Tell the listener we are about to render.
410			var continueRendering = _listeners.beforerender.call(_instance, listenerParams);
411
412			//The beforerender listener function is able the cancel rendering.
413			if(continueRendering !== false) {
414				for(var i = 0; i < _skrollables.length; i++) {
415					_calcSteps(_skrollables[i], _curTop);
416				}
417
418				//Remember when we last rendered.
419				_lastTop = _curTop;
420
421				_listeners.render.call(_instance, listenerParams);
422			}
423
424			afterAnimationCallback.call(_instance);
425		}
426
427		requestAnimFrame(function() {
428			_render();
429		});
430	};
431
432	/**
433	 * Parses the properties for each key frame of the given skrollable.
434	 */
435	var _parseProps = function(skrollable) {
436		//Iterate over all key frames
437		for(var i = 0; i < skrollable.keyFrames.length; i++) {
438			var frame = skrollable.keyFrames[i];
439
440			//Get all properties and values in an array
441			var allProps = frame.props.split(rxPropSplit);
442
443			var prop;
444			var value;
445			var easing;
446
447			frame.props = {};
448
449			//Iterate over all props and values (+2 because [prop,value,prop,value,...])
450			for(var k = 0; k < allProps.length - 1; k += 2) {
451				prop = allProps[k];
452				value = allProps[k + 1];
453				easing = prop.match(rxPropEasing);
454
455				//Is there an easing specified for this prop?
456				if(easing !== null) {
457					prop = easing[1];
458					easing = easing[2];
459				} else {
460					easing = DEFAULT_EASING;
461
462				}
463
464				//Exclamation point at first position forces the value to be taken literal.
465				value = value.indexOf('!') ? _parseProp(value) : [value.slice(1)];
466
467				//Save the prop for this key frame with his value and easing function
468				frame.props[prop] = {
469					value: value,
470					easing: easings[easing]
471				};
472			}
473		}
474	};
475
476	/**
477	 * Parses a value extracting numeric values and generating a format string
478	 * for later interpolation of the new values in old string.
479	 *
480	 * @param val The CSS value to be parsed.
481	 * @return Something like ["rgba(?%,?%, ?%,?)", 100, 50, 0, .7]
482	 * where the first element is the format string later used
483	 * and all following elements are the numeric value.
484	 */
485	var _parseProp = function(val) {
486		var numbers = [];
487
488		//One special case, where floats don't work.
489		//We replace all occurences of rgba colors
490		//which don't use percentage notation with the percentage notation.
491		rxRGBAIntegerColor.lastIndex = 0;
492		val = val.replace(rxRGBAIntegerColor, function(rgba) {
493			return rgba.replace(rxNumericValue, function(n) {
494				return n / 255 * 100 + '%';
495			});
496		});
497
498		//Handle prefixing of "gradient" values.
499		//For now only the prefixed value will be set. Unprefixed isn't supported anyway.
500		rxGradient.lastIndex = 0;
501		val = val.replace(rxGradient, function(s) {
502			return theDashedCSSPrefix + s;
503		});
504
505
506		//Now parse ANY number inside this string and create a format string.
507		val = val.replace(rxNumericValue, function(n) {
508			numbers.push(+n);
509			return '?';
510		});
511
512		//Add the formatstring as first value.
513		numbers.unshift(val);
514
515		return numbers;
516	};
517
518	/**
519	 * Fills the key frames with missing left and right hand properties.
520	 * If key frame 1 has property X and key frame 2 is missing X,
521	 * but key frame 3 has X again, then we need to assign X to key frame 2 too.
522	 *
523	 * @param sk A skrollable.
524	 */
525	var _fillProps = function(sk) {
526		//Will collect the properties key frame by key frame
527		var propList = {};
528
529		//Iterate over all key frames from left to right
530		for(var i = 0; i < sk.keyFrames.length; i++) {
531			_fillPropForFrame(sk.keyFrames[i], propList);
532		}
533
534		//Now do the same from right to fill the last gaps
535
536		propList = {};
537
538		//Iterate over all key frames from right to left
539		for(var i = sk.keyFrames.length - 1; i >= 0; i--) {
540			_fillPropForFrame(sk.keyFrames[i], propList);
541		}
542	};
543
544	var _fillPropForFrame = function(frame, propList) {
545		//For each key frame iterate over all right hand properties and assign them,
546		//but only if the current key frame doesn't have the property by itself
547		for(var key in propList) {
548			//The current frame misses this property, so assign it.
549			if(!hasProp.call(frame.props, key)) {
550				frame.props[key] = propList[key];
551			}
552		}
553
554		//Iterate over all props of the current frame and collect them
555		for(var key in frame.props) {
556			propList[key] = frame.props[key];
557		}
558	};
559
560	/**
561	 * Calculates the new values for two given values array.
562	 */
563	var _calcInterpolation = function(val1, val2, progress) {
564		//They both need to have the same length
565		if(val1.length !== val2.length) {
566			throw 'Can\'t interpolate between "' + val1[0] + '" and "' + val2[0] + '"';
567		}
568
569		//Add the format string as first element.
570		var interpolated = [val1[0]];
571
572		for(var i = 1; i < val1.length; i++) {
573			//That's the line where the two numbers are actually interpolated.
574			interpolated[i] = val1[i] + ((val2[i] - val1[i]) * progress);
575		}
576
577		return interpolated;
578	};
579
580	/**
581	 * Interpolates the numeric values into the format string.
582	 */
583	var _interpolateString = function(val) {
584		var i = 1;
585
586		return val[0].replace(/\?/g, function() {
587			return val[i++];
588		});
589	};
590
591	/**
592	 * Set the CSS property on the given element. Sets prefixed properties as well.
593	 */
594	var _setStyle = function(el, prop, val) {
595		var style = el.style;
596
597		//Camel case.
598		prop = prop.replace(rxCamelCase, function(str, p1) {
599			return p1.toUpperCase();
600		}).replace('-', '');
601
602		//Make sure z-index gets a <integer>.
603		//This is the only <integer> case we need to handle.
604		if(prop === 'zIndex') {
605			//Floor
606			style[prop] = '' + (val | 0);
607		}
608		else {
609			//Need try-catch for old IE.
610			try {
611				//Set prefixed property.
612				style[theCSSPrefix + prop.slice(0,1).toUpperCase() + prop.slice(1)] = val;
613
614				//Set unprefixed.
615				style[prop] = val;
616			} catch(ignore) {}
617		}
618
619		//Plugin entry point.
620		if(_plugins.setStyle) {
621			for(var i = 0; i < _plugins.setStyle.length; i++) {
622				_plugins.setStyle[0].call(this, el, prop, val);
623			}
624		}
625	};
626
627	/**
628	 * Adds a CSS class.
629	 */
630	var _addClass = function(el, name) {
631		if(_untrim(el.className).indexOf(_untrim(name)) === -1) {
632			el.className = _trim((el.className + ' ' + name));
633		}
634	};
635
636	/**
637	 * Cross browser event handling.
638	 */
639	var _addEvent = function(name, fn) {
640		if(window.addEventListener) {
641			window.addEventListener(name, fn, false);
642		} else {
643			window.attachEvent('on' + name, fn);
644		}
645	};
646
647	/**
648	 * Removes a CSS class.
649	 */
650	var _removeClass = function(el, name) {
651		el.className = _trim((_untrim(el.className)).replace(_untrim(name), ' '));
652	};
653
654	var _trim = function(a) {
655		return a.replace(rxTrim, '$1');
656	};
657
658	/**
659	 * Adds a space before and after the string.
660	 */
661	var _untrim = function(a) {
662		return ' ' + a + ' ';
663	};
664
665	var _now = function() {
666		return +new Date();
667	};
668
669	var _keyFrameComparator = function(a, b) {
670		return a.frame - b.frame;
671	};
672
673	/*
674	 * Private variables.
675	 */
676	//Singleton
677	var _instance;
678
679	//Will contain all plugin-functions.
680	var _plugins = {};
681
682	/*
683		A list of all elements which should be animated associated with their the metadata.
684		Exmaple skrollable with two key frames animating from 100px width to 20px:
685
686		skrollable = {
687			element: <the DOM element>,
688			keyFrames: [
689				{
690					frame: 100,
691					props: {
692						width: {
693							value: ['?px', 100],
694							easing: <reference to easing function>
695						}
696					}
697				},
698				{
699					frame: 200,
700					props: {
701						width: {
702							value: ['?px', 20],
703							easing: <reference to easing function>
704						}
705					}
706				}
707			]
708		};
709	*/
710	var _skrollables = [];
711
712	//Will contain references to all "data-end" key frames.
713	var _dataEndKeyFrames = [];
714
715	var _listeners;
716	var _forceHeight;
717	var _maxKeyFrame = 0;
718
719	var _scale = 1;
720
721	//Current direction (up/down).
722	var _direction = 'down';
723
724	//The last top offset value. Needed to determine direction.
725	var _lastTop = -1;
726
727	//The current top offset, needed for async rendering.
728	var _curTop = 0;
729
730	//Will contain data about a running scrollbar animation, if any.
731	var _scrollAnimation;
732
733	//Can be set by any operation/event to force rendering even if the scrollbar didn't move.
734	var _forceRender;
735
736	/*
737	 * Global api.
738	 */
739	window.skrollr = {
740		//Main entry point.
741		init: function(options) {
742			return _instance || new Skrollr(options);
743		},
744		//Plugin api.
745		plugin: function(entryPoint, fn) {
746			//Each entry point may contain multiple plugin-functions.
747			if(_plugins[entryPoint]) {
748				_plugins[entryPoint].push(fn);
749			} else {
750				_plugins[entryPoint] = [fn];
751			}
752		},
753		VERSION: '0.3.11'
754	};
755}(window, document));

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.