PageSourceSearch

https://thebatt.com/wp-content/themes/snoflex/includes/parallax/js/skrollr.js?ver=7.1.2

js thebatt.com collected 2026-10-02 04:03:20 UTC 45,547 bytes, 1,685 lines download raw bytes

1/*!
2 * skrollr core
3 *
4 * Alexander Prinzhorn - https://github.com/Prinzhorn/skrollr
5 *
6 * Free to use under terms of MIT license
7 */
8(function(window, document, undefined) {
9	'use strict';
10
11	/*
12	 * Global api.
13	 */
14	var skrollr = window.skrollr = {
15		get: function() {
16			return _instance;
17		},
18		//Main entry point.
19		init: function(options) {
20			return _instance || new Skrollr(options);
21		},
22		VERSION: '0.6.21'
23	};
24
25	//Minify optimization.
26	var hasProp = Object.prototype.hasOwnProperty;
27	var Math = window.Math;
28	var getStyle = window.getComputedStyle;
29
30	//They will be filled when skrollr gets initialized.
31	var documentElement;
32
33//	var body;  // line edited by SNO 
34	var body = '.parallaxcontainer';
35	
36	var EVENT_TOUCHSTART = 'touchstart';
37	var EVENT_TOUCHMOVE = 'touchmove';
38	var EVENT_TOUCHCANCEL = 'touchcancel';
39	var EVENT_TOUCHEND = 'touchend';
40
41	var SKROLLABLE_CLASS = 'skrollable';
42	var SKROLLABLE_BEFORE_CLASS = SKROLLABLE_CLASS + '-before';
43	var SKROLLABLE_BETWEEN_CLASS = SKROLLABLE_CLASS + '-between';
44	var SKROLLABLE_AFTER_CLASS = SKROLLABLE_CLASS + '-after';
45
46	var SKROLLR_CLASS = 'skrollr';
47	var NO_SKROLLR_CLASS = 'no-' + SKROLLR_CLASS;
48	var SKROLLR_DESKTOP_CLASS = SKROLLR_CLASS + '-desktop';
49	var SKROLLR_MOBILE_CLASS = SKROLLR_CLASS + '-mobile';
50
51	var DEFAULT_EASING = 'linear';
52	var DEFAULT_DURATION = 1000;//ms
53	var DEFAULT_MOBILE_DECELERATION = 0.004;//pixel/ms²
54
55	var DEFAULT_SMOOTH_SCROLLING_DURATION = 200;//ms
56
57	var ANCHOR_START = 'start';
58	var ANCHOR_END = 'end';
59	var ANCHOR_CENTER = 'center';
60	var ANCHOR_BOTTOM = 'bottom';
61
62	//The property which will be added to the DOM element to hold the ID of the skrollable.
63	var SKROLLABLE_ID_DOM_PROPERTY = '___skrollable_id';
64
65	var rxTouchIgnoreTags = /^(?:input|textarea|button|select)$/i;
66
67	var rxTrim = /^\s+|\s+$/g;
68
69	//Find all data-attributes. data-[_constant]-[offset]-[anchor]-[anchor].
70	var rxKeyframeAttribute = /^data(?:-(_\w+))?(?:-?(-?\d*\.?\d+p?))?(?:-?(start|end|top|center|bottom))?(?:-?(top|center|bottom))?$/;
71
72	var rxPropValue = /\s*([\w\-\[\]]+)\s*:\s*(.+?)\s*(?:;|$)/gi;
73
74	//Easing function names follow the property in square brackets.
75	var rxPropEasing = /^([a-z\-]+)\[(\w+)\]$/;
76
77	var rxCamelCase = /-([a-z])/g;
78	var rxCamelCaseFn = function(str, letter) {
79		return letter.toUpperCase();
80	};
81
82	//Numeric values with optional sign.
83	var rxNumericValue = /[\-+]?[\d]*\.?[\d]+/g;
84
85	//Used to replace occurences of {?} with a number.
86	var rxInterpolateString = /\{\?\}/g;
87
88	//Finds rgb(a) colors, which don't use the percentage notation.
89	var rxRGBAIntegerColor = /rgba?\(\s*-?\d+\s*,\s*-?\d+\s*,\s*-?\d+/g;
90
91	//Finds all gradients.
92	var rxGradient = /[a-z\-]+-gradient/g;
93
94	//Vendor prefix. Will be set once skrollr gets initialized.
95	var theCSSPrefix = '';
96	var theDashedCSSPrefix = '';
97
98	//Will be called once (when skrollr gets initialized).
99	var detectCSSPrefix = function() {
100		//Only relevant prefixes. May be extended.
101		//Could be dangerous if there will ever be a CSS property which actually starts with "ms". Don't hope so.
102		var rxPrefixes = /^(?:O|Moz|webkit|ms)|(?:-(?:o|moz|webkit|ms)-)/;
103
104		//Detect prefix for current browser by finding the first property using a prefix.
105		if(!getStyle) {
106			return;
107		}
108
109		var style = getStyle(body, null);
110
111		for(var k in style) {
112			//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.
113			theCSSPrefix = (k.match(rxPrefixes) || (+k == k && style[k].match(rxPrefixes)));
114
115			if(theCSSPrefix) {
116				break;
117			}
118		}
119
120		//Did we even detect a prefix?
121		if(!theCSSPrefix) {
122			theCSSPrefix = theDashedCSSPrefix = '';
123
124			return;
125		}
126
127		theCSSPrefix = theCSSPrefix[0];
128
129		//We could have detected either a dashed prefix or this camelCaseish-inconsistent stuff.
130		if(theCSSPrefix.slice(0,1) === '-') {
131			theDashedCSSPrefix = theCSSPrefix;
132
133			//There's no logic behind these. Need a look up.
134			theCSSPrefix = ({
135				'-webkit-': 'webkit',
136				'-moz-': 'Moz',
137				'-ms-': 'ms',
138				'-o-': 'O'
139			})[theCSSPrefix];
140		} else {
141			theDashedCSSPrefix = '-' + theCSSPrefix.toLowerCase() + '-';
142		}
143	};
144
145	var polyfillRAF = function() {
146		var requestAnimFrame = window.requestAnimationFrame || window[theCSSPrefix.toLowerCase() + 'RequestAnimationFrame'];
147
148		var lastTime = _now();
149
150		if(_isMobile || !requestAnimFrame) {
151			requestAnimFrame = function(callback) {
152				//How long did it take to render?
153				var deltaTime = _now() - lastTime;
154				var delay = Math.max(0, 1000 / 60 - deltaTime);
155
156				return window.setTimeout(function() {
157					lastTime = _now();
158					callback();
159				}, delay);
160			};
161		}
162
163		return requestAnimFrame;
164	};
165
166	var polyfillCAF = function() {
167		var cancelAnimFrame = window.cancelAnimationFrame || window[theCSSPrefix.toLowerCase() + 'CancelAnimationFrame'];
168
169		if(_isMobile || !cancelAnimFrame) {
170			cancelAnimFrame = function(timeout) {
171				return window.clearTimeout(timeout);
172			};
173		}
174
175		return cancelAnimFrame;
176	};
177
178	//Built-in easing functions.
179	var easings = {
180		begin: function() {
181			return 0;
182		},
183		end: function() {
184			return 1;
185		},
186		linear: function(p) {
187			return p;
188		},
189		quadratic: function(p) {
190			return p * p;
191		},
192		cubic: function(p) {
193			return p * p * p;
194		},
195		swing: function(p) {
196			return (-Math.cos(p * Math.PI) / 2) + 0.5;
197		},
198		sqrt: function(p) {
199			return Math.sqrt(p);
200		},
201		outCubic: function(p) {
202			return (Math.pow((p - 1), 3) + 1);
203		},
204		//see https://www.desmos.com/calculator/tbr20s8vd2 for how I did this
205		bounce: function(p) {
206			var a;
207
208			if(p <= 0.5083) {
209				a = 3;
210			} else if(p <= 0.8489) {
211				a = 9;
212			} else if(p <= 0.96208) {
213				a = 27;
214			} else if(p <= 0.99981) {
215				a = 91;
216			} else {
217				return 1;
218			}
219
220			return 1 - Math.abs(3 * Math.cos(p * a * 1.028) / a);
221		}
222	};
223
224	/**
225	 * Constructor.
226	 */
227	function Skrollr(options) {
228		documentElement = document.documentElement;
229		body = document.body;  
230		detectCSSPrefix();
231
232		_instance = this;
233
234		options = options || {};
235
236		_constants = options.constants || {};
237
238		//We allow defining custom easings or overwrite existing.
239		if(options.easing) {
240			for(var e in options.easing) {
241				easings[e] = options.easing[e];
242			}
243		}
244
245		_edgeStrategy = options.edgeStrategy || 'set';
246
247		_listeners = {
248			//Function to be called right before rendering.
249			beforerender: options.beforerender,
250
251			//Function to be called right after finishing rendering.
252			render: options.render
253		};
254
255		//forceHeight is true by default
256		_forceHeight = options.forceHeight !== false;
257
258		if(_forceHeight) {
259			_scale = options.scale || 1;
260		}
261
262		_mobileDeceleration = options.mobileDeceleration || DEFAULT_MOBILE_DECELERATION;
263
264		_smoothScrollingEnabled = options.smoothScrolling !== false;
265		_smoothScrollingDuration = options.smoothScrollingDuration || DEFAULT_SMOOTH_SCROLLING_DURATION;
266
267		//Dummy object. Will be overwritten in the _render method when smooth scrolling is calculated.
268		_smoothScrolling = {
269			targetTop: _instance.getScrollTop()
270		};
271
272		//A custom check function may be passed.
273		_isMobile = ((options.mobileCheck || function() {
274			return (/Android|iPhone|iPad|iPod|BlackBerry/i).test(navigator.userAgent || navigator.vendor || window.opera);
275		})());
276
277		if(_isMobile) {
278			_skrollrBody = document.getElementById('skrollr-body');
279
280			//Detect 3d transform if there's a skrollr-body (only needed for #skrollr-body).
281			if(_skrollrBody) {
282				_detect3DTransforms();
283			}
284
285			_initMobile();
286			_updateClass(documentElement, [SKROLLR_CLASS, SKROLLR_MOBILE_CLASS], [NO_SKROLLR_CLASS]);
287		} else {
288			_updateClass(documentElement, [SKROLLR_CLASS, SKROLLR_DESKTOP_CLASS], [NO_SKROLLR_CLASS]);
289		}
290
291		//Triggers parsing of elements and a first reflow.
292		_instance.refresh();
293
294		_addEvent(window, 'resize orientationchange', function() {
295			var width = documentElement.clientWidth;
296			var height = documentElement.clientHeight;
297
298			//Only reflow if the size actually changed (#271).
299			if(height !== _lastViewportHeight || width !== _lastViewportWidth) {
300				_lastViewportHeight = height;
301				_lastViewportWidth = width;
302
303				_requestReflow = true;
304			}
305		});
306
307		var requestAnimFrame = polyfillRAF();
308
309		//Let's go.
310		(function animloop(){
311			_render();
312			_animFrame = requestAnimFrame(animloop);
313		}());
314
315		return _instance;
316	}
317
318	/**
319	 * (Re)parses some or all elements.
320	 */
321	Skrollr.prototype.refresh = function(elements) {
322		var elementIndex;
323		var elementsLength;
324		var ignoreID = false;
325
326		//Completely reparse anything without argument.
327		if(elements === undefined) {
328			//Ignore that some elements may already have a skrollable ID.
329			ignoreID = true;
330
331			_skrollables = [];
332			_skrollableIdCounter = 0;
333
334			elements = document.getElementsByTagName('*');
335		} else {
336			//We accept a single element or an array of elements.
337			elements = [].concat(elements);
338		}
339
340		elementIndex = 0;
341		elementsLength = elements.length;
342
343		for(; elementIndex < elementsLength; elementIndex++) {
344			var el = elements[elementIndex];
345			var anchorTarget = el;
346			var keyFrames = [];
347
348			//If this particular element should be smooth scrolled.
349			var smoothScrollThis = _smoothScrollingEnabled;
350
351			//The edge strategy for this particular element.
352			var edgeStrategy = _edgeStrategy;
353
354			if(!el.attributes) {
355				continue;
356			}
357
358			//Iterate over all attributes and search for key frame attributes.
359			var attributeIndex = 0;
360			var attributesLength = el.attributes.length;
361
362			for (; attributeIndex < attributesLength; attributeIndex++) {
363				var attr = el.attributes[attributeIndex];
364
365				if(attr.name === 'data-anchor-target') {
366					anchorTarget = document.querySelector(attr.value);
367
368					if(anchorTarget === null) {
369						throw 'Unable to find anchor target "' + attr.value + '"';
370					}
371
372					continue;
373				}
374
375				//Global smooth scrolling can be overridden by the element attribute.
376				if(attr.name === 'data-smooth-scrolling') {
377					smoothScrollThis = attr.value !== 'off';
378
379					continue;
380				}
381
382				//Global edge strategy can be overridden by the element attribute.
383				if(attr.name === 'data-edge-strategy') {
384					edgeStrategy = attr.value;
385
386					continue;
387				}
388
389				var match = attr.name.match(rxKeyframeAttribute);
390
391				if(match === null) {
392					continue;
393				}
394
395				var kf = {
396					props: attr.value,
397					//Point back to the element as well.
398					element: el
399				};
400
401				keyFrames.push(kf);
402
403				var constant = match[1];
404
405				if(constant) {
406					//Strip the underscore prefix.
407					kf.constant = constant.substr(1);
408				}
409
410				//Get the key frame offset.
411				var offset = match[2];
412
413				//Is it a percentage offset?
414				if(/p$/.test(offset)) {
415					kf.isPercentage = true;
416					kf.offset = (offset.slice(0, -1) | 0) / 100;
417				} else {
418					kf.offset = (offset | 0);
419				}
420
421				var anchor1 = match[3];
422
423				//If second anchor is not set, the first will be taken for both.
424				var anchor2 = match[4] || anchor1;
425
426				//"absolute" (or "classic") mode, where numbers mean absolute scroll offset.
427				if(!anchor1 || anchor1 === ANCHOR_START || anchor1 === ANCHOR_END) {
428					kf.mode = 'absolute';
429
430					//data-end needs to be calculated after all key frames are known.
431					if(anchor1 === ANCHOR_END) {
432						kf.isEnd = true;
433					} else if(!kf.isPercentage) {
434						//For data-start we can already set the key frame w/o calculations.
435						//#59: "scale" options should only affect absolute mode.
436						kf.offset = kf.offset * _scale;
437					}
438				}
439				//"relative" mode, where numbers are relative to anchors.
440				else {
441					kf.mode = 'relative';
442					kf.anchors = [anchor1, anchor2];
443				}
444			}
445
446			//Does this element have key frames?
447			if(!keyFrames.length) {
448				continue;
449			}
450
451			//Will hold the original style and class attributes before we controlled the element (see #80).
452			var styleAttr, classAttr;
453
454			var id;
455
456			if(!ignoreID && SKROLLABLE_ID_DOM_PROPERTY in el) {
457				//We already have this element under control. Grab the corresponding skrollable id.
458				id = el[SKROLLABLE_ID_DOM_PROPERTY];
459				styleAttr = _skrollables[id].styleAttr;
460				classAttr = _skrollables[id].classAttr;
461			} else {
462				//It's an unknown element. Asign it a new skrollable id.
463				id = (el[SKROLLABLE_ID_DOM_PROPERTY] = _skrollableIdCounter++);
464				styleAttr = el.style.cssText;
465				classAttr = _getClass(el);
466			}
467
468			_skrollables[id] = {
469				element: el,
470				styleAttr: styleAttr,
471				classAttr: classAttr,
472				anchorTarget: anchorTarget,
473				keyFrames: keyFrames,
474				smoothScrolling: smoothScrollThis,
475				edgeStrategy: edgeStrategy
476			};
477
478			_updateClass(el, [SKROLLABLE_CLASS], []);
479		}
480
481		//Reflow for the first time.
482		_reflow();
483
484		//Now that we got all key frame numbers right, actually parse the properties.
485		elementIndex = 0;
486		elementsLength = elements.length;
487
488		for(; elementIndex < elementsLength; elementIndex++) {
489			var sk = _skrollables[elements[elementIndex][SKROLLABLE_ID_DOM_PROPERTY]];
490
491			if(sk === undefined) {
492				continue;
493			}
494
495			//Parse the property string to objects
496			_parseProps(sk);
497
498			//Fill key frames with missing properties from left and right
499			_fillProps(sk);
500		}
501
502		return _instance;
503	};
504
505	/**
506	 * Transform "relative" mode to "absolute" mode.
507	 * That is, calculate anchor position and offset of element.
508	 */
509	Skrollr.prototype.relativeToAbsolute = function(element, viewportAnchor, elementAnchor) {
510		var viewportHeight = documentElement.clientHeight;
511		var box = element.getBoundingClientRect();
512		var absolute = box.top;
513
514		//#100: IE doesn't supply "height" with getBoundingClientRect.
515		var boxHeight = box.bottom - box.top;
516
517		if(viewportAnchor === ANCHOR_BOTTOM) {
518			absolute -= viewportHeight;
519		} else if(viewportAnchor === ANCHOR_CENTER) {
520			absolute -= viewportHeight / 2;
521		}
522
523		if(elementAnchor === ANCHOR_BOTTOM) {
524			absolute += boxHeight;
525		} else if(elementAnchor === ANCHOR_CENTER) {
526			absolute += boxHeight / 2;
527		}
528
529		//Compensate scrolling since getBoundingClientRect is relative to viewport.
530		absolute += _instance.getScrollTop();
531
532		return (absolute + 0.5) | 0;
533	};
534
535	/**
536	 * Animates scroll top to new position.
537	 */
538	Skrollr.prototype.animateTo = function(top, options) {
539		options = options || {};
540
541		var now = _now();
542		var scrollTop = _instance.getScrollTop();
543
544		//Setting this to a new value will automatically cause the current animation to stop, if any.
545		_scrollAnimation = {
546			startTop: scrollTop,
547			topDiff: top - scrollTop,
548			targetTop: top,
549			duration: options.duration || DEFAULT_DURATION,
550			startTime: now,
551			endTime: now + (options.duration || DEFAULT_DURATION),
552			easing: easings[options.easing || DEFAULT_EASING],
553			done: options.done
554		};
555
556		//Don't queue the animation if there's nothing to animate.
557		if(!_scrollAnimation.topDiff) {
558			if(_scrollAnimation.done) {
559				_scrollAnimation.done.call(_instance, false);
560			}
561
562			_scrollAnimation = undefined;
563		}
564
565		return _instance;
566	};
567
568	/**
569	 * Stops animateTo animation.
570	 */
571	Skrollr.prototype.stopAnimateTo = function() {
572		if(_scrollAnimation && _scrollAnimation.done) {
573			_scrollAnimation.done.call(_instance, true);
574		}
575
576		_scrollAnimation = undefined;
577	};
578
579	/**
580	 * Returns if an animation caused by animateTo is currently running.
581	 */
582	Skrollr.prototype.isAnimatingTo = function() {
583		return !!_scrollAnimation;
584	};
585
586	Skrollr.prototype.setScrollTop = function(top, force) {
587		_forceRender = (force === true);
588
589		if(_isMobile) {
590			_mobileOffset = Math.min(Math.max(top, 0), _maxKeyFrame);
591		} else {
592			window.scrollTo(0, top);
593		}
594
595		return _instance;
596	};
597
598	Skrollr.prototype.getScrollTop = function() {
599		if(_isMobile) {
600			return _mobileOffset;
601		} else {
602			return window.pageYOffset || documentElement.scrollTop || body.scrollTop || 0;
603		}
604	};
605
606	Skrollr.prototype.getMaxScrollTop = function() {
607		return _maxKeyFrame;
608	};
609
610	Skrollr.prototype.on = function(name, fn) {
611		_listeners[name] = fn;
612
613		return _instance;
614	};
615
616	Skrollr.prototype.off = function(name) {
617		delete _listeners[name];
618
619		return _instance;
620	};
621
622	Skrollr.prototype.destroy = function() {
623		var cancelAnimFrame = polyfillCAF();
624		cancelAnimFrame(_animFrame);
625		_removeAllEvents();
626
627		_updateClass(documentElement, [NO_SKROLLR_CLASS], [SKROLLR_CLASS, SKROLLR_DESKTOP_CLASS, SKROLLR_MOBILE_CLASS]);
628
629		var skrollableIndex = 0;
630		var skrollablesLength = _skrollables.length;
631
632		for(; skrollableIndex < skrollablesLength; skrollableIndex++) {
633			_reset(_skrollables[skrollableIndex].element);
634		}
635
636		documentElement.style.overflow = body.style.overflow = 'auto';
637		documentElement.style.height = body.style.height = 'auto';
638
639		if(_skrollrBody) {
640			skrollr.setStyle(_skrollrBody, 'transform', 'none');
641		}
642
643		_instance = undefined;
644		_skrollrBody = undefined;
645		_listeners = undefined;
646		_forceHeight = undefined;
647		_maxKeyFrame = 0;
648		_scale = 1;
649		_constants = undefined;
650		_mobileDeceleration = undefined;
651		_direction = 'down';
652		_lastTop = -1;
653		_lastViewportWidth = 0;
654		_lastViewportHeight = 0;
655		_requestReflow = false;
656		_scrollAnimation = undefined;
657		_smoothScrollingEnabled = undefined;
658		_smoothScrollingDuration = undefined;
659		_smoothScrolling = undefined;
660		_forceRender = undefined;
661		_skrollableIdCounter = 0;
662		_edgeStrategy = undefined;
663		_isMobile = false;
664		_mobileOffset = 0;
665		_translateZ = undefined;
666	};
667
668	/*
669		Private methods.
670	*/
671
672	var _initMobile = function() {
673		var initialElement;
674		var initialTouchY;
675		var initialTouchX;
676		var currentElement;
677		var currentTouchY;
678		var currentTouchX;
679		var lastTouchY;
680		var deltaY;
681
682		var initialTouchTime;
683		var currentTouchTime;
684		var lastTouchTime;
685		var deltaTime;
686
687		_addEvent(documentElement, [EVENT_TOUCHSTART, EVENT_TOUCHMOVE, EVENT_TOUCHCANCEL, EVENT_TOUCHEND].join(' '), function(e) {
688			var touch = e.changedTouches[0];
689
690			currentElement = e.target;
691
692			//We don't want text nodes.
693			while(currentElement.nodeType === 3) {
694				currentElement = currentElement.parentNode;
695			}
696
697			currentTouchY = touch.clientY;
698			currentTouchX = touch.clientX;
699			currentTouchTime = e.timeStamp;
700
701			if(!rxTouchIgnoreTags.test(currentElement.tagName)) {
702				e.preventDefault();
703			}
704
705			switch(e.type) {
706				case EVENT_TOUCHSTART:
707					//The last element we tapped on.
708					if(initialElement) {
709						initialElement.blur();
710					}
711
712					_instance.stopAnimateTo();
713
714					initialElement = currentElement;
715
716					initialTouchY = lastTouchY = currentTouchY;
717					initialTouchX = currentTouchX;
718					initialTouchTime = currentTouchTime;
719
720					break;
721				case EVENT_TOUCHMOVE:
722					//Prevent default event on touchIgnore elements in case they don't have focus yet.
723					if(rxTouchIgnoreTags.test(currentElement.tagName) && document.activeElement !== currentElement) {
724						e.preventDefault();
725					}
726
727					deltaY = currentTouchY - lastTouchY;
728					deltaTime = currentTouchTime - lastTouchTime;
729
730					_instance.setScrollTop(_mobileOffset - deltaY, true);
731
732					lastTouchY = currentTouchY;
733					lastTouchTime = currentTouchTime;
734					break;
735				default:
736				case EVENT_TOUCHCANCEL:
737				case EVENT_TOUCHEND:
738					var distanceY = initialTouchY - currentTouchY;
739					var distanceX = initialTouchX - currentTouchX;
740					var distance2 = distanceX * distanceX + distanceY * distanceY;
741
742					//Check if it was more like a tap (moved less than 7px).
743					if(distance2 < 49) {
744						if(!rxTouchIgnoreTags.test(initialElement.tagName)) {
745							initialElement.focus();
746
747							//It was a tap, click the element.
748							var clickEvent = document.createEvent('MouseEvents');
749							clickEvent.initMouseEvent('click', true, true, e.view, 1, touch.screenX, touch.screenY, touch.clientX, touch.clientY, e.ctrlKey, e.altKey, e.shiftKey, e.metaKey, 0, null);
750							initialElement.dispatchEvent(clickEvent);
751						}
752
753						return;
754					}
755
756					initialElement = undefined;
757
758					var speed = deltaY / deltaTime;
759
760					//Cap speed at 3 pixel/ms.
761					speed = Math.max(Math.min(speed, 3), -3);
762
763					var duration = Math.abs(speed / _mobileDeceleration);
764					var targetOffset = speed * duration + 0.5 * _mobileDeceleration * duration * duration;
765					var targetTop = _instance.getScrollTop() - targetOffset;
766
767					//Relative duration change for when scrolling above bounds.
768					var targetRatio = 0;
769
770					//Change duration proportionally when scrolling would leave bounds.
771					if(targetTop > _maxKeyFrame) {
772						targetRatio = (_maxKeyFrame - targetTop) / targetOffset;
773
774						targetTop = _maxKeyFrame;
775					} else if(targetTop < 0) {
776						targetRatio = -targetTop / targetOffset;
777
778						targetTop = 0;
779					}
780
781					duration = duration * (1 - targetRatio);
782
783					_instance.animateTo((targetTop + 0.5) | 0, {easing: 'outCubic', duration: duration});
784					break;
785			}
786		});
787
788		//Just in case there has already been some native scrolling, reset it.
789		window.scrollTo(0, 0);
790		
791		// removed the line below in testing for the bug that caused no scrolling
792		//documentElement.style.overflow = body.style.overflow = 'hidden';
793	};
794
795	/**
796	 * Updates key frames which depend on others / need to be updated on resize.
797	 * That is "end" in "absolute" mode and all key frames in "relative" mode.
798	 * Also handles constants, because they may change on resize.
799	 */
800	var _updateDependentKeyFrames = function() {
801		var viewportHeight = documentElement.clientHeight;
802		var processedConstants = _processConstants();
803		var skrollable;
804		var element;
805		var anchorTarget;
806		var keyFrames;
807		var keyFrameIndex;
808		var keyFramesLength;
809		var kf;
810		var skrollableIndex;
811		var skrollablesLength;
812		var offset;
813		var constantValue;
814
815		//First process all relative-mode elements and find the max key frame.
816		skrollableIndex = 0;
817		skrollablesLength = _skrollables.length;
818
819		for(; skrollableIndex < skrollablesLength; skrollableIndex++) {
820			skrollable = _skrollables[skrollableIndex];
821			element = skrollable.element;
822			anchorTarget = skrollable.anchorTarget;
823			keyFrames = skrollable.keyFrames;
824
825			keyFrameIndex = 0;
826			keyFramesLength = keyFrames.length;
827
828			for(; keyFrameIndex < keyFramesLength; keyFrameIndex++) {
829				kf = keyFrames[keyFrameIndex];
830
831				offset = kf.offset;
832				constantValue = processedConstants[kf.constant] || 0;
833
834				kf.frame = offset;
835
836				if(kf.isPercentage) {
837					//Convert the offset to percentage of the viewport height.
838					offset = offset * viewportHeight;
839
840					//Absolute + percentage mode.
841					kf.frame = offset;
842				}
843
844				if(kf.mode === 'relative') {
845					_reset(element);
846
847					kf.frame = _instance.relativeToAbsolute(anchorTarget, kf.anchors[0], kf.anchors[1]) - offset;
848
849					_reset(element, true);
850				}
851
852				kf.frame += constantValue;
853
854				//Only search for max key frame when forceHeight is enabled.
855				if(_forceHeight) {
856					//Find the max key frame, but don't use one of the data-end ones for comparison.
857					if(!kf.isEnd && kf.frame > _maxKeyFrame) {
858						_maxKeyFrame = kf.frame;
859					}
860				}
861			}
862		}
863
864		//#133: The document can be larger than the maxKeyFrame we found.
865		_maxKeyFrame = Math.max(_maxKeyFrame, _getDocumentHeight());
866
867		//Now process all data-end keyframes.
868		skrollableIndex = 0;
869		skrollablesLength = _skrollables.length;
870
871		for(; skrollableIndex < skrollablesLength; skrollableIndex++) {
872			skrollable = _skrollables[skrollableIndex];
873			keyFrames = skrollable.keyFrames;
874
875			keyFrameIndex = 0;
876			keyFramesLength = keyFrames.length;
877
878			for(; keyFrameIndex < keyFramesLength; keyFrameIndex++) {
879				kf = keyFrames[keyFrameIndex];
880
881				constantValue = processedConstants[kf.constant] || 0;
882
883				if(kf.isEnd) {
884					kf.frame = _maxKeyFrame - kf.offset + constantValue;
885				}
886			}
887
888			skrollable.keyFrames.sort(_keyFrameComparator);
889		}
890	};
891
892	/**
893	 * Calculates and sets the style properties for the element at the given frame.
894	 * @param fakeFrame The frame to render at when smooth scrolling is enabled.
895	 * @param actualFrame The actual frame we are at.
896	 */
897	var _calcSteps = function(fakeFrame, actualFrame) {
898		//Iterate over all skrollables.
899		var skrollableIndex = 0;
900		var skrollablesLength = _skrollables.length;
901
902		for(; skrollableIndex < skrollablesLength; skrollableIndex++) {
903			var skrollable = _skrollables[skrollableIndex];
904			var element = skrollable.element;
905			var frame = skrollable.smoothScrolling ? fakeFrame : actualFrame;
906			var frames = skrollable.keyFrames;
907			var firstFrame = frames[0].frame;
908			var lastFrame = frames[frames.length - 1].frame;
909			var beforeFirst = frame < firstFrame;
910			var afterLast = frame > lastFrame;
911			var firstOrLastFrame = frames[beforeFirst ? 0 : frames.length - 1];
912			var key;
913			var value;
914
915			//If we are before/after the first/last frame, set the styles according to the given edge strategy.
916			if(beforeFirst || afterLast) {
917				//Check if we already handled this edge case last time.
918				//Note: using setScrollTop it's possible that we jumped from one edge to the other.
919				if(beforeFirst && skrollable.edge === -1 || afterLast && skrollable.edge === 1) {
920					continue;
921				}
922
923				//Add the skrollr-before or -after class.
924				_updateClass(element, [beforeFirst ? SKROLLABLE_BEFORE_CLASS : SKROLLABLE_AFTER_CLASS], [SKROLLABLE_BEFORE_CLASS, SKROLLABLE_BETWEEN_CLASS, SKROLLABLE_AFTER_CLASS]);
925
926				//Remember that we handled the edge case (before/after the first/last keyframe).
927				skrollable.edge = beforeFirst ? -1 : 1;
928
929				switch(skrollable.edgeStrategy) {
930					case 'reset':
931						_reset(element);
932						continue;
933					case 'ease':
934						//Handle this case like it would be exactly at first/last keyframe and just pass it on.
935						frame = firstOrLastFrame.frame;
936						break;
937					default:
938					case 'set':
939						var props = firstOrLastFrame.props;
940
941						for(key in props) {
942							if(hasProp.call(props, key)) {
943								value = _interpolateString(props[key].value);
944
945								skrollr.setStyle(element, key, value);
946							}
947						}
948
949						continue;
950				}
951			} else {
952				//Did we handle an edge last time?
953				if(skrollable.edge !== 0) {
954					_updateClass(element, [SKROLLABLE_CLASS, SKROLLABLE_BETWEEN_CLASS], [SKROLLABLE_BEFORE_CLASS, SKROLLABLE_AFTER_CLASS]);
955					skrollable.edge = 0;
956				}
957			}
958
959			//Find out between which two key frames we are right now.
960			var keyFrameIndex = 0;
961			var framesLength = frames.length - 1;
962
963			for(; keyFrameIndex < framesLength; keyFrameIndex++) {
964				if(frame >= frames[keyFrameIndex].frame && frame <= frames[keyFrameIndex + 1].frame) {
965					var left = frames[keyFrameIndex];
966					var right = frames[keyFrameIndex + 1];
967
968					for(key in left.props) {
969						if(hasProp.call(left.props, key)) {
970							var progress = (frame - left.frame) / (right.frame - left.frame);
971
972							//Transform the current progress using the given easing function.
973							progress = left.props[key].easing(progress);
974
975							//Interpolate between the two values
976							value = _calcInterpolation(left.props[key].value, right.props[key].value, progress);
977
978							value = _interpolateString(value);
979
980							skrollr.setStyle(element, key, value);
981						}
982					}
983
984					break;
985				}
986			}
987		}
988	};
989
990	/**
991	 * Renders all elements.
992	 */
993	var _render = function() {
994		if(_requestReflow) {
995			_requestReflow = false;
996			_reflow();
997		}
998
999		//We may render something else than the actual scrollbar position.
1000		var renderTop = _instance.getScrollTop();
1001
1002		//If there's an animation, which ends in current render call, call the callback after rendering.
1003		var afterAnimationCallback;
1004		var now = _now();
1005		var progress;
1006
1007		//Before actually rendering handle the scroll animation, if any.
1008		if(_scrollAnimation) {
1009			//It's over
1010			if(now >= _scrollAnimation.endTime) {
1011				renderTop = _scrollAnimation.targetTop;
1012				afterAnimationCallback = _scrollAnimation.done;
1013				_scrollAnimation = undefined;
1014			} else {
1015				//Map the current progress to the new progress using given easing function.
1016				progress = _scrollAnimation.easing((now - _scrollAnimation.startTime) / _scrollAnimation.duration);
1017
1018				renderTop = (_scrollAnimation.startTop + progress * _scrollAnimation.topDiff) | 0;
1019			}
1020
1021			_instance.setScrollTop(renderTop, true);
1022		}
1023		//Smooth scrolling only if there's no animation running and if we're not forcing the rendering.
1024		else if(!_forceRender) {
1025			var smoothScrollingDiff = _smoothScrolling.targetTop - renderTop;
1026
1027			//The user scrolled, start new smooth scrolling.
1028			if(smoothScrollingDiff) {
1029				_smoothScrolling = {
1030					startTop: _lastTop,
1031					topDiff: renderTop - _lastTop,
1032					targetTop: renderTop,
1033					startTime: _lastRenderCall,
1034					endTime: _lastRenderCall + _smoothScrollingDuration
1035				};
1036			}
1037
1038			//Interpolate the internal scroll position (not the actual scrollbar).
1039			if(now <= _smoothScrolling.endTime) {
1040				//Map the current progress to the new progress using easing function.
1041				progress = easings.sqrt((now - _smoothScrolling.startTime) / _smoothScrollingDuration);
1042
1043				renderTop = (_smoothScrolling.startTop + progress * _smoothScrolling.topDiff) | 0;
1044			}
1045		}
1046
1047		//That's were we actually "scroll" on mobile.
1048		if(_isMobile && _skrollrBody) {
1049			//Set the transform ("scroll it").
1050			skrollr.setStyle(_skrollrBody, 'transform', 'translate(0, ' + -(_mobileOffset) + 'px) ' + _translateZ);
1051		}
1052
1053		//Did the scroll position even change?
1054		if(_forceRender || _lastTop !== renderTop) {
1055			//Remember in which direction are we scrolling?
1056			_direction = (renderTop > _lastTop) ? 'down' : (renderTop < _lastTop ? 'up' : _direction);
1057
1058			_forceRender = false;
1059
1060			var listenerParams = {
1061				curTop: renderTop,
1062				lastTop: _lastTop,
1063				maxTop: _maxKeyFrame,
1064				direction: _direction
1065			};
1066
1067			//Tell the listener we are about to render.
1068			var continueRendering = _listeners.beforerender && _listeners.beforerender.call(_instance, listenerParams);
1069
1070			//The beforerender listener function is able the cancel rendering.
1071			if(continueRendering !== false) {
1072				//Now actually interpolate all the styles.
1073				_calcSteps(renderTop, _instance.getScrollTop());
1074
1075				//Remember when we last rendered.
1076				_lastTop = renderTop;
1077
1078				if(_listeners.render) {
1079					_listeners.render.call(_instance, listenerParams);
1080				}
1081			}
1082
1083			if(afterAnimationCallback) {
1084				afterAnimationCallback.call(_instance, false);
1085			}
1086		}
1087
1088		_lastRenderCall = now;
1089	};
1090
1091	/**
1092	 * Parses the properties for each key frame of the given skrollable.
1093	 */
1094	var _parseProps = function(skrollable) {
1095		//Iterate over all key frames
1096		var keyFrameIndex = 0;
1097		var keyFramesLength = skrollable.keyFrames.length;
1098
1099		for(; keyFrameIndex < keyFramesLength; keyFrameIndex++) {
1100			var frame = skrollable.keyFrames[keyFrameIndex];
1101			var easing;
1102			var value;
1103			var prop;
1104			var props = {};
1105
1106			var match;
1107
1108			while((match = rxPropValue.exec(frame.props)) !== null) {
1109				prop = match[1];
1110				value = match[2];
1111
1112				easing = prop.match(rxPropEasing);
1113
1114				//Is there an easing specified for this prop?
1115				if(easing !== null) {
1116					prop = easing[1];
1117					easing = easing[2];
1118				} else {
1119					easing = DEFAULT_EASING;
1120				}
1121
1122				//Exclamation point at first position forces the value to be taken literal.
1123				value = value.indexOf('!') ? _parseProp(value) : [value.slice(1)];
1124
1125				//Save the prop for this key frame with his value and easing function
1126				props[prop] = {
1127					value: value,
1128					easing: easings[easing]
1129				};
1130			}
1131
1132			frame.props = props;
1133		}
1134	};
1135
1136	/**
1137	 * Parses a value extracting numeric values and generating a format string
1138	 * for later interpolation of the new values in old string.
1139	 *
1140	 * @param val The CSS value to be parsed.
1141	 * @return Something like ["rgba(?%,?%, ?%,?)", 100, 50, 0, .7]
1142	 * where the first element is the format string later used
1143	 * and all following elements are the numeric value.
1144	 */
1145	var _parseProp = function(val) {
1146		var numbers = [];
1147
1148		//One special case, where floats don't work.
1149		//We replace all occurences of rgba colors
1150		//which don't use percentage notation with the percentage notation.
1151		rxRGBAIntegerColor.lastIndex = 0;
1152		val = val.replace(rxRGBAIntegerColor, function(rgba) {
1153			return rgba.replace(rxNumericValue, function(n) {
1154				return n / 255 * 100 + '%';
1155			});
1156		});
1157
1158		//Handle prefixing of "gradient" values.
1159		//For now only the prefixed value will be set. Unprefixed isn't supported anyway.
1160		if(theDashedCSSPrefix) {
1161			rxGradient.lastIndex = 0;
1162			val = val.replace(rxGradient, function(s) {
1163				return theDashedCSSPrefix + s;
1164			});
1165		}
1166
1167		//Now parse ANY number inside this string and create a format string.
1168		val = val.replace(rxNumericValue, function(n) {
1169			numbers.push(+n);
1170			return '{?}';
1171		});
1172
1173		//Add the formatstring as first value.
1174		numbers.unshift(val);
1175
1176		return numbers;
1177	};
1178
1179	/**
1180	 * Fills the key frames with missing left and right hand properties.
1181	 * If key frame 1 has property X and key frame 2 is missing X,
1182	 * but key frame 3 has X again, then we need to assign X to key frame 2 too.
1183	 *
1184	 * @param sk A skrollable.
1185	 */
1186	var _fillProps = function(sk) {
1187		//Will collect the properties key frame by key frame
1188		var propList = {};
1189		var keyFrameIndex;
1190		var keyFramesLength;
1191
1192		//Iterate over all key frames from left to right
1193		keyFrameIndex = 0;
1194		keyFramesLength = sk.keyFrames.length;
1195
1196		for(; keyFrameIndex < keyFramesLength; keyFrameIndex++) {
1197			_fillPropForFrame(sk.keyFrames[keyFrameIndex], propList);
1198		}
1199
1200		//Now do the same from right to fill the last gaps
1201
1202		propList = {};
1203
1204		//Iterate over all key frames from right to left
1205		keyFrameIndex = sk.keyFrames.length - 1;
1206
1207		for(; keyFrameIndex >= 0; keyFrameIndex--) {
1208			_fillPropForFrame(sk.keyFrames[keyFrameIndex], propList);
1209		}
1210	};
1211
1212	var _fillPropForFrame = function(frame, propList) {
1213		var key;
1214
1215		//For each key frame iterate over all right hand properties and assign them,
1216		//but only if the current key frame doesn't have the property by itself
1217		for(key in propList) {
1218			//The current frame misses this property, so assign it.
1219			if(!hasProp.call(frame.props, key)) {
1220				frame.props[key] = propList[key];
1221			}
1222		}
1223
1224		//Iterate over all props of the current frame and collect them
1225		for(key in frame.props) {
1226			propList[key] = frame.props[key];
1227		}
1228	};
1229
1230	/**
1231	 * Calculates the new values for two given values array.
1232	 */
1233	var _calcInterpolation = function(val1, val2, progress) {
1234		var valueIndex;
1235		var val1Length = val1.length;
1236
1237		//They both need to have the same length
1238		if(val1Length !== val2.length) {
1239			throw 'Can\'t interpolate between "' + val1[0] + '" and "' + val2[0] + '"';
1240		}
1241
1242		//Add the format string as first element.
1243		var interpolated = [val1[0]];
1244
1245		valueIndex = 1;
1246
1247		for(; valueIndex < val1Length; valueIndex++) {
1248			//That's the line where the two numbers are actually interpolated.
1249			interpolated[valueIndex] = val1[valueIndex] + ((val2[valueIndex] - val1[valueIndex]) * progress);
1250		}
1251
1252		return interpolated;
1253	};
1254
1255	/**
1256	 * Interpolates the numeric values into the format string.
1257	 */
1258	var _interpolateString = function(val) {
1259		var valueIndex = 1;
1260
1261		rxInterpolateString.lastIndex = 0;
1262
1263		return val[0].replace(rxInterpolateString, function() {
1264			return val[valueIndex++];
1265		});
1266	};
1267
1268	/**
1269	 * Resets the class and style attribute to what it was before skrollr manipulated the element.
1270	 * Also remembers the values it had before reseting, in order to undo the reset.
1271	 */
1272	var _reset = function(elements, undo) {
1273		//We accept a single element or an array of elements.
1274		elements = [].concat(elements);
1275
1276		var skrollable;
1277		var element;
1278		var elementsIndex = 0;
1279		var elementsLength = elements.length;
1280
1281		for(; elementsIndex < elementsLength; elementsIndex++) {
1282			element = elements[elementsIndex];
1283			skrollable = _skrollables[element[SKROLLABLE_ID_DOM_PROPERTY]];
1284
1285			//Couldn't find the skrollable for this DOM element.
1286			if(!skrollable) {
1287				continue;
1288			}
1289
1290			if(undo) {
1291				//Reset class and style to the "dirty" (set by skrollr) values.
1292				element.style.cssText = skrollable.dirtyStyleAttr;
1293				_updateClass(element, skrollable.dirtyClassAttr);
1294			} else {
1295				//Remember the "dirty" (set by skrollr) class and style.
1296				skrollable.dirtyStyleAttr = element.style.cssText;
1297				skrollable.dirtyClassAttr = _getClass(element);
1298
1299				//Reset class and style to what it originally was.
1300				element.style.cssText = skrollable.styleAttr;
1301				_updateClass(element, skrollable.classAttr);
1302			}
1303		}
1304	};
1305
1306	/**
1307	 * Detects support for 3d transforms by applying it to the skrollr-body.
1308	 */
1309	var _detect3DTransforms = function() {
1310		_translateZ = 'translateZ(0)';
1311		skrollr.setStyle(_skrollrBody, 'transform', _translateZ);
1312
1313		var computedStyle = getStyle(_skrollrBody);
1314		var computedTransform = computedStyle.getPropertyValue('transform');
1315		var computedTransformWithPrefix = computedStyle.getPropertyValue(theDashedCSSPrefix + 'transform');
1316		var has3D = (computedTransform && computedTransform !== 'none') || (computedTransformWithPrefix && computedTransformWithPrefix !== 'none');
1317
1318		if(!has3D) {
1319			_translateZ = '';
1320		}
1321	};
1322
1323	/**
1324	 * Set the CSS property on the given element. Sets prefixed properties as well.
1325	 */
1326	skrollr.setStyle = function(el, prop, val) {
1327		var style = el.style;
1328
1329		//Camel case.
1330		prop = prop.replace(rxCamelCase, rxCamelCaseFn).replace('-', '');
1331
1332		//Make sure z-index gets a <integer>.
1333		//This is the only <integer> case we need to handle.
1334		if(prop === 'zIndex') {
1335			if(isNaN(val)) {
1336				//If it's not a number, don't touch it.
1337				//It could for example be "auto" (#351).
1338				style[prop] = val;
1339			} else {
1340				//Floor the number.
1341				style[prop] = '' + (val | 0);
1342			}
1343		}
1344		//#64: "float" can't be set across browsers. Needs to use "cssFloat" for all except IE.
1345		else if(prop === 'float') {
1346			style.styleFloat = style.cssFloat = val;
1347		}
1348		else {
1349			//Need try-catch for old IE.
1350			try {
1351				//Set prefixed property if there's a prefix.
1352				if(theCSSPrefix) {
1353					style[theCSSPrefix + prop.slice(0,1).toUpperCase() + prop.slice(1)] = val;
1354				}
1355
1356				//Set unprefixed.
1357				style[prop] = val;
1358			} catch(ignore) {}
1359		}
1360	};
1361
1362	/**
1363	 * Cross browser event handling.
1364	 */
1365	var _addEvent = skrollr.addEvent = function(element, names, callback) {
1366		var intermediate = function(e) {
1367			//Normalize IE event stuff.
1368			e = e || window.event;
1369
1370			if(!e.target) {
1371				e.target = e.srcElement;
1372			}
1373
1374			if(!e.preventDefault) {
1375				e.preventDefault = function() {
1376					e.returnValue = false;
1377				};
1378			}
1379
1380			return callback.call(this, e);
1381		};
1382
1383		names = names.split(' ');
1384
1385		var name;
1386		var nameCounter = 0;
1387		var namesLength = names.length;
1388
1389		for(; nameCounter < namesLength; nameCounter++) {
1390			name = names[nameCounter];
1391
1392			if(element.addEventListener) {
1393				element.addEventListener(name, callback, false);
1394			} else {
1395				element.attachEvent('on' + name, intermediate);
1396			}
1397
1398			//Remember the events to be able to flush them later.
1399			_registeredEvents.push({
1400				element: element,
1401				name: name,
1402				listener: callback
1403			});
1404		}
1405	};
1406
1407	var _removeEvent = skrollr.removeEvent = function(element, names, callback) {
1408		names = names.split(' ');
1409
1410		var nameCounter = 0;
1411		var namesLength = names.length;
1412
1413		for(; nameCounter < namesLength; nameCounter++) {
1414			if(element.removeEventListener) {
1415				element.removeEventListener(names[nameCounter], callback, false);
1416			} else {
1417				element.detachEvent('on' + names[nameCounter], callback);
1418			}
1419		}
1420	};
1421
1422	var _removeAllEvents = function() {
1423		var eventData;
1424		var eventCounter = 0;
1425		var eventsLength = _registeredEvents.length;
1426
1427		for(; eventCounter < eventsLength; eventCounter++) {
1428			eventData = _registeredEvents[eventCounter];
1429
1430			_removeEvent(eventData.element, eventData.name, eventData.listener);
1431		}
1432
1433		_registeredEvents = [];
1434	};
1435
1436	var _reflow = function() {
1437		var pos = _instance.getScrollTop();
1438
1439		//Will be recalculated by _updateDependentKeyFrames.
1440		_maxKeyFrame = 0;
1441
1442		if(_forceHeight && !_isMobile) {
1443			//un-"force" the height to not mess with the calculations in _updateDependentKeyFrames (#216).
1444	//		body.style.height = 'auto';
1445		}
1446
1447		_updateDependentKeyFrames();
1448
1449		if(_forceHeight && !_isMobile) {
1450			//"force" the height.
1451		//	body.style.height = (_maxKeyFrame + documentElement.clientHeight) + 'px';
1452		}
1453
1454		//The scroll offset may now be larger than needed (on desktop the browser/os prevents scrolling farther than the bottom).
1455		if(_isMobile) {
1456			_instance.setScrollTop(Math.min(_instance.getScrollTop(), _maxKeyFrame));
1457		} else {
1458			//Remember and reset the scroll pos (#217).
1459			_instance.setScrollTop(pos, true);
1460		}
1461
1462		_forceRender = true;
1463	};
1464
1465	/*
1466	 * Returns a copy of the constants object where all functions and strings have been evaluated.
1467	 */
1468	var _processConstants = function() {
1469		var viewportHeight = documentElement.clientHeight;
1470		var copy = {};
1471		var prop;
1472		var value;
1473
1474		for(prop in _constants) {
1475			value = _constants[prop];
1476
1477			if(typeof value === 'function') {
1478				value = value.call(_instance);
1479			}
1480			//Percentage offset.
1481			else if((/p$/).test(value)) {
1482				value = (value.slice(0, -1) / 100) * viewportHeight;
1483			}
1484
1485			copy[prop] = value;
1486		}
1487
1488		return copy;
1489	};
1490
1491	/*
1492	 * Returns the height of the document.
1493	 */
1494	var _getDocumentHeight = function() {
1495		var skrollrBodyHeight = (_skrollrBody && _skrollrBody.offsetHeight || 0);
1496		var bodyHeight = Math.max(skrollrBodyHeight, body.scrollHeight, body.offsetHeight, documentElement.scrollHeight, documentElement.offsetHeight, documentElement.clientHeight);
1497
1498		return bodyHeight - documentElement.clientHeight;
1499	};
1500
1501	/**
1502	 * Returns a string of space separated classnames for the current element.
1503	 * Works with SVG as well.
1504	 */
1505	var _getClass = function(element) {
1506		var prop = 'className';
1507
1508		//SVG support by using className.baseVal instead of just className.
1509		if(window.SVGElement && element instanceof window.SVGElement) {
1510			element = element[prop];
1511			prop = 'baseVal';
1512		}
1513
1514		return element[prop];
1515	};
1516
1517	/**
1518	 * Adds and removes a CSS classes.
1519	 * Works with SVG as well.
1520	 * add and remove are arrays of strings,
1521	 * or if remove is ommited add is a string and overwrites all classes.
1522	 */
1523	var _updateClass = function(element, add, remove) {
1524		var prop = 'className';
1525
1526		//SVG support by using className.baseVal instead of just className.
1527		if(window.SVGElement && element instanceof window.SVGElement) {
1528			element = element[prop];
1529			prop = 'baseVal';
1530		}
1531
1532		//When remove is ommited, we want to overwrite/set the classes.
1533		if(remove === undefined) {
1534			element[prop] = add;
1535			return;
1536		}
1537
1538		//Cache current classes. We will work on a string before passing back to DOM.
1539		var val = element[prop];
1540
1541		//All classes to be removed.
1542		var classRemoveIndex = 0;
1543		var removeLength = remove.length;
1544
1545		for(; classRemoveIndex < removeLength; classRemoveIndex++) {
1546			val = _untrim(val).replace(_untrim(remove[classRemoveIndex]), ' ');
1547		}
1548
1549		val = _trim(val);
1550
1551		//All classes to be added.
1552		var classAddIndex = 0;
1553		var addLength = add.length;
1554
1555		for(; classAddIndex < addLength; classAddIndex++) {
1556			//Only add if el not already has class.
1557			if(_untrim(val).indexOf(_untrim(add[classAddIndex])) === -1) {
1558				val += ' ' + add[classAddIndex];
1559			}
1560		}
1561
1562		element[prop] = _trim(val);
1563	};
1564
1565	var _trim = function(a) {
1566		return a.replace(rxTrim, '');
1567	};
1568
1569	/**
1570	 * Adds a space before and after the string.
1571	 */
1572	var _untrim = function(a) {
1573		return ' ' + a + ' ';
1574	};
1575
1576	var _now = Date.now || function() {
1577		return +new Date();
1578	};
1579
1580	var _keyFrameComparator = function(a, b) {
1581		return a.frame - b.frame;
1582	};
1583
1584	/*
1585	 * Private variables.
1586	 */
1587
1588	//Singleton
1589	var _instance;
1590
1591	/*
1592		A list of all elements which should be animated associated with their the metadata.
1593		Exmaple skrollable with two key frames animating from 100px width to 20px:
1594
1595		skrollable = {
1596			element: <the DOM element>,
1597			styleAttr: <style attribute of the element before skrollr>,
1598			classAttr: <class attribute of the element before skrollr>,
1599			keyFrames: [
1600				{
1601					frame: 100,
1602					props: {
1603						width: {
1604							value: ['{?}px', 100],
1605							easing: <reference to easing function>
1606						}
1607					},
1608					mode: "absolute"
1609				},
1610				{
1611					frame: 200,
1612					props: {
1613						width: {
1614							value: ['{?}px', 20],
1615							easing: <reference to easing function>
1616						}
1617					},
1618					mode: "absolute"
1619				}
1620			]
1621		};
1622	*/
1623	var _skrollables;
1624
1625	var _skrollrBody;
1626
1627	var _listeners;
1628	var _forceHeight;
1629	var _maxKeyFrame = 0;
1630
1631	var _scale = 1;
1632	var _constants;
1633
1634	var _mobileDeceleration;
1635
1636	//Current direction (up/down).
1637	var _direction = 'down';
1638
1639	//The last top offset value. Needed to determine direction.
1640	var _lastTop = -1;
1641
1642	//The last time we called the render method (doesn't mean we rendered!).
1643	var _lastRenderCall = _now();
1644
1645	//For detecting if it actually resized (#271).
1646	var _lastViewportWidth = 0;
1647	var _lastViewportHeight = 0;
1648
1649	var _requestReflow = false;
1650
1651	//Will contain data about a running scrollbar animation, if any.
1652	var _scrollAnimation;
1653
1654	var _smoothScrollingEnabled;
1655
1656	var _smoothScrollingDuration;
1657
1658	//Will contain settins for smooth scrolling if enabled.
1659	var _smoothScrolling;
1660
1661	//Can be set by any operation/event to force rendering even if the scrollbar didn't move.
1662	var _forceRender;
1663
1664	//Each skrollable gets an unique ID incremented for each skrollable.
1665	//The ID is the index in the _skrollables array.
1666	var _skrollableIdCounter = 0;
1667
1668	var _edgeStrategy;
1669
1670
1671	//Mobile specific vars. Will be stripped by UglifyJS when not in use.
1672	var _isMobile = false;
1673
1674	//The virtual scroll offset when using mobile scrolling.
1675	var _mobileOffset = 0;
1676
1677	//If the browser supports 3d transforms, this will be filled with 'translateZ(0)' (empty string otherwise).
1678	var _translateZ;
1679
1680	//Will contain data about registered events by skrollr.
1681	var _registeredEvents = [];
1682
1683	//Animation frame id returned by RequestAnimationFrame (or timeout when RAF is not supported).
1684	var _animFrame;
1685}(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.