PageSourceSearch

https://www.lakesidebank.com/wp-content/themes/lakeside-bank/js/skrollr.js?ver=1674528446

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