skrollr-rails 0.5.14 → 0.6.4

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