PageSourceSearch

https://www.cisi.org/cisiweb2/js/common/skrollr.js?v=636802949420000000

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