skrollr-rails 0.6.4 → 0.6.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -4,1383 +4,1384 @@
4
4
  * Alexander Prinzhorn - https://github.com/Prinzhorn/skrollr
5
5
  *
6
6
  * Free to use under terms of MIT license
7
- */ (function(window, document, undefined) {
8
- 'use strict';
7
+ */
8
+ (function(window, document, undefined) {
9
+ 'use strict';
10
+
11
+ /*
12
+ * Global api.
13
+ */
14
+ var skrollr = window.skrollr = {
15
+ get: function() {
16
+ return _instance;
17
+ },
18
+ //Main entry point.
19
+ init: function(options) {
20
+ return _instance || new Skrollr(options);
21
+ },
22
+ VERSION: '0.6.5'
23
+ };
9
24
 
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
- }());
25
+ //Minify optimization.
26
+ var hasProp = Object.prototype.hasOwnProperty;
27
+ var Math = window.Math;
28
+ var getStyle = window.getComputedStyle;
282
29
 
283
- return _instance;
284
- }
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 MOBILE_DECELERATION = 0.0006;//pixel/ms²
52
+
53
+ var SMOOTH_SCROLLING_DURATION = 200;
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 rxTrim = /^\s+|\s+$/g;
64
+
65
+ //Find all data-attributes. data-[_constant]-[offset]-[anchor]-[anchor].
66
+ var rxKeyframeAttribute = /^data(?:-(_\w+))?(?:-?(-?\d+))?(?:-?(start|end|top|center|bottom))?(?:-?(top|center|bottom))?$/;
67
+
68
+ var rxPropValue = /\s*([\w\-\[\]]+)\s*:\s*(.+?)\s*(?:;|$)/gi;
69
+
70
+ //Easing function names follow the property in square brackets.
71
+ var rxPropEasing = /^([a-z\-]+)\[(\w+)\]$/;
72
+
73
+ var rxCamelCase = /-([a-z])/g;
74
+ var rxCamelCaseFn = function(str, letter) {
75
+ return letter.toUpperCase();
76
+ };
77
+
78
+ //Numeric values with optional sign.
79
+ var rxNumericValue = /[\-+]?[\d]*\.?[\d]+/g;
80
+
81
+ //Used to replace occurences of {?} with a number.
82
+ var rxInterpolateString = /\{\?\}/g;
83
+
84
+ //Finds rgb(a) colors, which don't use the percentage notation.
85
+ var rxRGBAIntegerColor = /rgba?\(\s*-?\d+\s*,\s*-?\d+\s*,\s*-?\d+/g;
86
+
87
+ //Finds all gradients.
88
+ var rxGradient = /[a-z\-]+-gradient/g;
89
+
90
+ //Vendor prefix. Will be set once skrollr gets initialized.
91
+ var theCSSPrefix = '';
92
+ var theDashedCSSPrefix = '';
93
+
94
+ //Will be called once (when skrollr gets initialized).
95
+ var detectCSSPrefix = function() {
96
+ //Only relevant prefixes. May be extended.
97
+ //Could be dangerous if there will ever be a CSS property which actually starts with "ms". Don't hope so.
98
+ var rxPrefixes = /^(?:O|Moz|webkit|ms)|(?:-(?:o|moz|webkit|ms)-)/;
99
+
100
+ //Detect prefix for current browser by finding the first property using a prefix.
101
+ if(!getStyle) {
102
+ return;
103
+ }
104
+
105
+ var style = getStyle(body, null);
106
+
107
+ for(var k in style) {
108
+ //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.
109
+ theCSSPrefix = (k.match(rxPrefixes) || (+k == k && style[k].match(rxPrefixes)));
110
+
111
+ if(theCSSPrefix) {
112
+ break;
113
+ }
114
+ }
115
+
116
+ //Did we even detect a prefix?
117
+ if(!theCSSPrefix) {
118
+ theCSSPrefix = theDashedCSSPrefix = '';
119
+
120
+ return;
121
+ }
122
+
123
+ theCSSPrefix = theCSSPrefix[0];
124
+
125
+ //We could have detected either a dashed prefix or this camelCaseish-inconsistent stuff.
126
+ if(theCSSPrefix.slice(0,1) === '-') {
127
+ theDashedCSSPrefix = theCSSPrefix;
128
+
129
+ //There's no logic behind these. Need a look up.
130
+ theCSSPrefix = ({
131
+ '-webkit-': 'webkit',
132
+ '-moz-': 'Moz',
133
+ '-ms-': 'ms',
134
+ '-o-': 'O'
135
+ })[theCSSPrefix];
136
+ } else {
137
+ theDashedCSSPrefix = '-' + theCSSPrefix.toLowerCase() + '-';
138
+ }
139
+ };
140
+
141
+ var polyfillRAF = function() {
142
+ var requestAnimFrame = window.requestAnimationFrame || window[theCSSPrefix.toLowerCase() + 'RequestAnimationFrame'];
143
+
144
+ var lastTime = _now();
145
+
146
+ if(_isMobile || !requestAnimFrame) {
147
+ requestAnimFrame = function(callback) {
148
+ //How long did it take to render?
149
+ var deltaTime = _now() - lastTime;
150
+ var delay = Math.max(0, 33 - deltaTime);
151
+
152
+ window.setTimeout(function() {
153
+ lastTime = _now();
154
+ callback();
155
+ }, delay);
156
+ };
157
+ }
158
+
159
+ return requestAnimFrame;
160
+ };
161
+
162
+ //Built-in easing functions.
163
+ var easings = {
164
+ begin: function() {
165
+ return 0;
166
+ },
167
+ end: function() {
168
+ return 1;
169
+ },
170
+ linear: function(p) {
171
+ return p;
172
+ },
173
+ quadratic: function(p) {
174
+ return p * p;
175
+ },
176
+ cubic: function(p) {
177
+ return p * p * p;
178
+ },
179
+ swing: function(p) {
180
+ return (-Math.cos(p * Math.PI) / 2) + 0.5;
181
+ },
182
+ sqrt: function(p) {
183
+ return Math.sqrt(p);
184
+ },
185
+ outCubic: function(p) {
186
+ return (Math.pow((p - 1), 3) + 1);
187
+ },
188
+ //see https://www.desmos.com/calculator/tbr20s8vd2 for how I did this
189
+ bounce: function(p) {
190
+ var a;
191
+
192
+ if(p <= 0.5083) {
193
+ a = 3;
194
+ } else if(p <= 0.8489) {
195
+ a = 9;
196
+ } else if(p <= 0.96208) {
197
+ a = 27;
198
+ } else if(p <= 0.99981) {
199
+ a = 91;
200
+ } else {
201
+ return 1;
202
+ }
203
+
204
+ return 1 - Math.abs(3 * Math.cos(p * a * 1.028) / a);
205
+ }
206
+ };
207
+
208
+ /**
209
+ * Constructor.
210
+ */
211
+ function Skrollr(options) {
212
+ documentElement = document.documentElement;
213
+ body = document.body;
214
+
215
+ detectCSSPrefix();
216
+
217
+ _instance = this;
218
+
219
+ options = options || {};
220
+
221
+ _constants = options.constants || {};
222
+
223
+ //We allow defining custom easings or overwrite existing.
224
+ if(options.easing) {
225
+ for(var e in options.easing) {
226
+ easings[e] = options.easing[e];
227
+ }
228
+ }
229
+
230
+ _edgeStrategy = options.edgeStrategy || 'ease';
231
+
232
+ _listeners = {
233
+ //Function to be called right before rendering.
234
+ beforerender: options.beforerender,
235
+
236
+ //Function to be called right after finishing rendering.
237
+ render: options.render
238
+ };
239
+
240
+ //forceHeight is true by default
241
+ _forceHeight = options.forceHeight !== false;
242
+
243
+ if(_forceHeight) {
244
+ _scale = options.scale || 1;
245
+ }
246
+
247
+ _smoothScrollingEnabled = options.smoothScrolling !== false;
248
+
249
+ //Dummy object. Will be overwritten in the _render method when smooth scrolling is calculated.
250
+ _smoothScrolling = {
251
+ targetTop: _instance.getScrollTop()
252
+ };
253
+
254
+ //A custom check function may be passed.
255
+ _isMobile = ((options.mobileCheck || function() {
256
+ return (/Android|iPhone|iPad|iPod|BlackBerry|Windows Phone/i).test(navigator.userAgent || navigator.vendor || window.opera);
257
+ })());
258
+
259
+ if(_isMobile) {
260
+ _skrollrBody = document.getElementById('skrollr-body');
261
+
262
+ //Detect 3d transform if there's a skrollr-body (only needed for #skrollr-body).
263
+ if(_skrollrBody) {
264
+ _detect3DTransforms();
265
+ }
266
+
267
+ _initMobile();
268
+ _updateClass(documentElement, [SKROLLR_CLASS, SKROLLR_MOBILE_CLASS], [NO_SKROLLR_CLASS]);
269
+ } else {
270
+ _updateClass(documentElement, [SKROLLR_CLASS, SKROLLR_DESKTOP_CLASS], [NO_SKROLLR_CLASS]);
271
+ }
272
+
273
+ //Triggers parsing of elements and a first reflow.
274
+ _instance.refresh();
275
+
276
+ _addEvent(window, 'resize orientationchange', _reflow);
277
+
278
+ var requestAnimFrame = polyfillRAF();
279
+
280
+ //Let's go.
281
+ (function animloop(){
282
+ _render();
283
+ requestAnimFrame(animloop);
284
+ }());
285
+
286
+ return _instance;
287
+ }
288
+
289
+ /**
290
+ * (Re)parses some or all elements.
291
+ */
292
+ Skrollr.prototype.refresh = function(elements) {
293
+ var elementIndex;
294
+ var elementsLength;
295
+ var ignoreID = false;
296
+
297
+ //Completely reparse anything without argument.
298
+ if(elements === undefined) {
299
+ //Ignore that some elements may already have a skrollable ID.
300
+ ignoreID = true;
301
+
302
+ _skrollables = [];
303
+ _skrollableIdCounter = 0;
304
+
305
+ elements = document.getElementsByTagName('*');
306
+ } else {
307
+ //We accept a single element or an array of elements.
308
+ elements = [].concat(elements);
309
+ }
310
+
311
+ elementIndex = 0;
312
+ elementsLength = elements.length;
313
+
314
+ for(; elementIndex < elementsLength; elementIndex++) {
315
+ var el = elements[elementIndex];
316
+ var anchorTarget = el;
317
+ var keyFrames = [];
285
318
 
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;
319
+ //If this particular element should be smooth scrolled.
320
+ var smoothScrollThis = _smoothScrollingEnabled;
293
321
 
294
- //Completely reparse anything without argument.
295
- if (elements === undefined) {
296
- //Ignore that some elements may already have a skrollable ID.
297
- ignoreID = true;
322
+ //The edge strategy for this particular element.
323
+ var edgeStrategy = _edgeStrategy;
298
324
 
299
- _skrollables = [];
300
- _skrollableIdCounter = 0;
325
+ if(!el.attributes) {
326
+ continue;
327
+ }
301
328
 
302
- elements = document.getElementsByTagName('*');
303
- } else {
304
- //We accept a single element or an array of elements.
305
- elements = [].concat(elements);
306
- }
329
+ //Iterate over all attributes and search for key frame attributes.
330
+ var attributeIndex = 0;
331
+ var attributesLength = el.attributes.length;
307
332
 
308
- elementIndex = 0;
309
- elementsLength = elements.length;
333
+ for (; attributeIndex < attributesLength; attributeIndex++) {
334
+ var attr = el.attributes[attributeIndex];
310
335
 
311
- for (; elementIndex < elementsLength; elementIndex++) {
312
- var el = elements[elementIndex];
313
- var anchorTarget = el;
314
- var keyFrames = [];
336
+ if(attr.name === 'data-anchor-target') {
337
+ anchorTarget = document.querySelector(attr.value);
315
338
 
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
- /*
339
+ if(anchorTarget === null) {
340
+ throw 'Unable to find anchor target "' + attr.value + '"';
341
+ }
342
+
343
+ continue;
344
+ }
345
+
346
+ //Global smooth scrolling can be overridden by the element attribute.
347
+ if(attr.name === 'data-smooth-scrolling') {
348
+ smoothScrollThis = attr.value !== 'off';
349
+
350
+ continue;
351
+ }
352
+
353
+ //Global edge strategy can be overridden by the element attribute.
354
+ if(attr.name === 'data-edge-strategy') {
355
+ edgeStrategy = attr.value;
356
+
357
+ continue;
358
+ }
359
+
360
+ var match = attr.name.match(rxKeyframeAttribute);
361
+
362
+ if(match === null) {
363
+ continue;
364
+ }
365
+
366
+ var constant = match[1];
367
+
368
+ //If there is a constant, get it's value or fall back to 0.
369
+ constant = constant && _constants[constant.substr(1)] || 0;
370
+
371
+ //Parse key frame offset. If undefined will be casted to 0.
372
+ var offset = (match[2] | 0) + constant;
373
+ var anchor1 = match[3];
374
+ //If second anchor is not set, the first will be taken for both.
375
+ var anchor2 = match[4] || anchor1;
376
+
377
+ var kf = {
378
+ offset: offset,
379
+ props: attr.value,
380
+ //Point back to the element as well.
381
+ element: el
382
+ };
383
+
384
+ keyFrames.push(kf);
385
+
386
+ //"absolute" (or "classic") mode, where numbers mean absolute scroll offset.
387
+ if(!anchor1 || anchor1 === ANCHOR_START || anchor1 === ANCHOR_END) {
388
+ kf.mode = 'absolute';
389
+
390
+ //data-end needs to be calculated after all key frames are know.
391
+ if(anchor1 === ANCHOR_END) {
392
+ kf.isEnd = true;
393
+ } else {
394
+ //For data-start we can already set the key frame w/o calculations.
395
+ //#59: "scale" options should only affect absolute mode.
396
+ kf.frame = offset * _scale;
397
+
398
+ delete kf.offset;
399
+ }
400
+ }
401
+ //"relative" mode, where numbers are relative to anchors.
402
+ else {
403
+ kf.mode = 'relative';
404
+ kf.anchors = [anchor1, anchor2];
405
+ }
406
+ }
407
+
408
+ //Does this element have key frames?
409
+ if(!keyFrames.length) {
410
+ continue;
411
+ }
412
+
413
+ //Will hold the original style and class attributes before we controlled the element (see #80).
414
+ var styleAttr, classAttr;
415
+
416
+ var id;
417
+
418
+ if(!ignoreID && SKROLLABLE_ID_DOM_PROPERTY in el) {
419
+ //We already have this element under control. Grab the corresponding skrollable id.
420
+ id = el[SKROLLABLE_ID_DOM_PROPERTY];
421
+ styleAttr = _skrollables[id].styleAttr;
422
+ classAttr = _skrollables[id].classAttr;
423
+ } else {
424
+ //It's an unknown element. Asign it a new skrollable id.
425
+ id = (el[SKROLLABLE_ID_DOM_PROPERTY] = _skrollableIdCounter++);
426
+ styleAttr = el.style.cssText;
427
+ classAttr = _getClass(el);
428
+ }
429
+
430
+ _skrollables[id] = {
431
+ element: el,
432
+ styleAttr: styleAttr,
433
+ classAttr: classAttr,
434
+ anchorTarget: anchorTarget,
435
+ keyFrames: keyFrames,
436
+ smoothScrolling: smoothScrollThis,
437
+ edgeStrategy: edgeStrategy
438
+ };
439
+
440
+ _updateClass(el, [SKROLLABLE_CLASS], []);
441
+ }
442
+
443
+ //Reflow for the first time.
444
+ _reflow();
445
+
446
+ //Now that we got all key frame numbers right, actually parse the properties.
447
+ elementIndex = 0;
448
+ elementsLength = elements.length;
449
+
450
+ for(; elementIndex < elementsLength; elementIndex++) {
451
+ var sk = _skrollables[elements[elementIndex][SKROLLABLE_ID_DOM_PROPERTY]];
452
+
453
+ if(sk === undefined) {
454
+ continue;
455
+ }
456
+
457
+ //Parse the property string to objects
458
+ _parseProps(sk);
459
+
460
+ //Fill key frames with missing properties from left and right
461
+ _fillProps(sk);
462
+ }
463
+
464
+ return _instance;
465
+ };
466
+
467
+ /**
468
+ * Transform "relative" mode to "absolute" mode.
469
+ * That is, calculate anchor position and offset of element.
470
+ */
471
+ Skrollr.prototype.relativeToAbsolute = function(element, viewportAnchor, elementAnchor) {
472
+ var viewportHeight = documentElement.clientHeight;
473
+ var box = element.getBoundingClientRect();
474
+ var absolute = box.top;
475
+
476
+ //#100: IE doesn't supply "height" with getBoundingClientRect.
477
+ var boxHeight = box.bottom - box.top;
478
+
479
+ if(viewportAnchor === ANCHOR_BOTTOM) {
480
+ absolute -= viewportHeight;
481
+ } else if(viewportAnchor === ANCHOR_CENTER) {
482
+ absolute -= viewportHeight / 2;
483
+ }
484
+
485
+ if(elementAnchor === ANCHOR_BOTTOM) {
486
+ absolute += boxHeight;
487
+ } else if(elementAnchor === ANCHOR_CENTER) {
488
+ absolute += boxHeight / 2;
489
+ }
490
+
491
+ //Compensate scrolling since getBoundingClientRect is relative to viewport.
492
+ absolute += _instance.getScrollTop();
493
+
494
+ return (absolute + 0.5) | 0;
495
+ };
496
+
497
+ /**
498
+ * Animates scroll top to new position.
499
+ */
500
+ Skrollr.prototype.animateTo = function(top, options) {
501
+ options = options || {};
502
+
503
+ var now = _now();
504
+ var scrollTop = _instance.getScrollTop();
505
+
506
+ //Setting this to a new value will automatically cause the current animation to stop, if any.
507
+ _scrollAnimation = {
508
+ startTop: scrollTop,
509
+ topDiff: top - scrollTop,
510
+ targetTop: top,
511
+ duration: options.duration || DEFAULT_DURATION,
512
+ startTime: now,
513
+ endTime: now + (options.duration || DEFAULT_DURATION),
514
+ easing: easings[options.easing || DEFAULT_EASING],
515
+ done: options.done
516
+ };
517
+
518
+ //Don't queue the animation if there's nothing to animate.
519
+ if(!_scrollAnimation.topDiff) {
520
+ if(_scrollAnimation.done) {
521
+ _scrollAnimation.done.call(_instance, false);
522
+ }
523
+
524
+ _scrollAnimation = undefined;
525
+ }
526
+
527
+ return _instance;
528
+ };
529
+
530
+ /**
531
+ * Stops animateTo animation.
532
+ */
533
+ Skrollr.prototype.stopAnimateTo = function() {
534
+ if(_scrollAnimation && _scrollAnimation.done) {
535
+ _scrollAnimation.done.call(_instance, true);
536
+ }
537
+
538
+ _scrollAnimation = undefined;
539
+ };
540
+
541
+ /**
542
+ * Returns if an animation caused by animateTo is currently running.
543
+ */
544
+ Skrollr.prototype.isAnimatingTo = function() {
545
+ return !!_scrollAnimation;
546
+ };
547
+
548
+ Skrollr.prototype.setScrollTop = function(top, force) {
549
+ //Don't do smooth scrolling (last top === new top).
550
+ if(force === true) {
551
+ _lastTop = top;
552
+ _forceRender = true;
553
+ }
554
+
555
+ if(_isMobile) {
556
+ _mobileOffset = Math.min(Math.max(top, 0), _maxKeyFrame);
557
+
558
+ //That's were we actually "scroll" on mobile.
559
+ if(_skrollrBody) {
560
+ //Set the transform ("scroll it").
561
+ _setStyle(_skrollrBody, 'transform', 'translate(0, ' + -(_mobileOffset) + 'px) ' + _translateZ);
562
+ }
563
+ } else {
564
+ window.scrollTo(0, top);
565
+ }
566
+
567
+ return _instance;
568
+ };
569
+
570
+ Skrollr.prototype.getScrollTop = function() {
571
+ if(_isMobile) {
572
+ return _mobileOffset;
573
+ } else {
574
+ return window.pageYOffset || documentElement.scrollTop || body.scrollTop || 0;
575
+ }
576
+ };
577
+
578
+ Skrollr.prototype.on = function(name, fn) {
579
+ _listeners[name] = fn;
580
+
581
+ return _instance;
582
+ };
583
+
584
+ Skrollr.prototype.off = function(name) {
585
+ delete _listeners[name];
586
+
587
+ return _instance;
588
+ };
589
+
590
+ /*
588
591
  Private methods.
589
592
  */
590
593
 
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
- /*
594
+ var _initMobile = function() {
595
+ var initialElement;
596
+ var initialTouchY;
597
+ var initialTouchX;
598
+ var currentTouchY;
599
+ var currentTouchX;
600
+ var lastTouchY;
601
+ var deltaY;
602
+
603
+ var initialTouchTime;
604
+ var currentTouchTime;
605
+ var lastTouchTime;
606
+ var deltaTime;
607
+
608
+ _addEvent(documentElement, [EVENT_TOUCHSTART, EVENT_TOUCHMOVE, EVENT_TOUCHCANCEL, EVENT_TOUCHEND].join(' '), function(e) {
609
+ e.preventDefault();
610
+
611
+ var touch = e.changedTouches[0];
612
+
613
+ currentTouchY = touch.clientY;
614
+ currentTouchX = touch.clientX;
615
+ currentTouchTime = e.timeStamp;
616
+
617
+ switch(e.type) {
618
+ case EVENT_TOUCHSTART:
619
+ //The last element we tapped on.
620
+ if(initialElement) {
621
+ initialElement.blur();
622
+ }
623
+
624
+ initialElement = e.target;
625
+ initialTouchY = lastTouchY = currentTouchY;
626
+ initialTouchX = currentTouchX;
627
+ initialTouchTime = currentTouchTime;
628
+
629
+ break;
630
+ case EVENT_TOUCHMOVE:
631
+ deltaY = currentTouchY - lastTouchY;
632
+ deltaTime = currentTouchTime - lastTouchTime;
633
+
634
+ _instance.setScrollTop(_mobileOffset - deltaY);
635
+
636
+ lastTouchY = currentTouchY;
637
+ lastTouchTime = currentTouchTime;
638
+ break;
639
+ default:
640
+ case EVENT_TOUCHCANCEL:
641
+ case EVENT_TOUCHEND:
642
+ var distanceY = initialTouchY - currentTouchY;
643
+ var distanceX = initialTouchX - currentTouchX;
644
+ var distance2 = distanceX * distanceX + distanceY * distanceY;
645
+
646
+ //Check if it was more like a tap (moved less than 7px).
647
+ if(distance2 < 49) {
648
+ //It was a tap, click the element.
649
+ initialElement.focus();
650
+ initialElement.click();
651
+
652
+ return;
653
+ }
654
+
655
+ initialElement = undefined;
656
+
657
+ var speed = deltaY / deltaTime;
658
+
659
+ //Cap speed at 3 pixel/ms.
660
+ speed = Math.max(Math.min(speed, 3), -3);
661
+
662
+ var duration = Math.abs(speed / MOBILE_DECELERATION);
663
+ var targetOffset = speed * duration + 0.5 * MOBILE_DECELERATION * duration * duration;
664
+ var targetTop = _instance.getScrollTop() - targetOffset;
665
+
666
+ //Relative duration change for when scrolling above bounds.
667
+ var targetRatio = 0;
668
+
669
+ //Change duration proportionally when scrolling would leave bounds.
670
+ if(targetTop > _maxKeyFrame) {
671
+ targetRatio = (_maxKeyFrame - targetTop) / targetOffset;
672
+
673
+ targetTop = _maxKeyFrame;
674
+ } else if(targetTop < 0) {
675
+ targetRatio = -targetTop / targetOffset;
676
+
677
+ targetTop = 0;
678
+ }
679
+
680
+ duration = duration * (1 - targetRatio);
681
+
682
+ _instance.animateTo(targetTop, {easing: 'outCubic', duration: duration});
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
+ }
1206
+ else {
1207
+ //Need try-catch for old IE.
1208
+ try {
1209
+ //Set prefixed property if there's a prefix.
1210
+ if(theCSSPrefix) {
1211
+ style[theCSSPrefix + prop.slice(0,1).toUpperCase() + prop.slice(1)] = val;
1212
+ }
1213
+
1214
+ //Set unprefixed.
1215
+ style[prop] = val;
1216
+ } catch(ignore) {}
1217
+ }
1218
+ };
1219
+
1220
+ /**
1221
+ * Cross browser event handling.
1222
+ */
1223
+ var _addEvent = skrollr.addEvent = function(element, names, callback) {
1224
+ var intermediate = function(e) {
1225
+ //Normalize IE event stuff.
1226
+ e = e || window.event;
1227
+
1228
+ if(!e.target) {
1229
+ e.target = e.srcElement;
1230
+ }
1231
+
1232
+ if(!e.preventDefault) {
1233
+ e.preventDefault = function() {
1234
+ e.returnValue = false;
1235
+ };
1236
+ }
1237
+
1238
+ return callback.call(this, e);
1239
+ };
1240
+
1241
+ names = names.split(' ');
1242
+
1243
+ var nameCounter = 0;
1244
+ var namesLength = names.length;
1245
+
1246
+ for(; nameCounter < namesLength; nameCounter++) {
1247
+ if(element.addEventListener) {
1248
+ element.addEventListener(names[nameCounter], callback, false);
1249
+ } else {
1250
+ element.attachEvent('on' + names[nameCounter], intermediate);
1251
+ }
1252
+ }
1253
+ };
1254
+
1255
+ var _reflow = function() {
1256
+ var pos = _instance.getScrollTop();
1257
+
1258
+ //Will be recalculated by _updateDependentKeyFrames.
1259
+ _maxKeyFrame = 0;
1260
+
1261
+ if(_forceHeight && !_isMobile) {
1262
+ //un-"force" the height to not mess with the calculations in _updateDependentKeyFrames (#216).
1263
+ body.style.height = 'auto';
1264
+ }
1265
+
1266
+ _updateDependentKeyFrames();
1267
+
1268
+ if(_forceHeight && !_isMobile) {
1269
+ //"force" the height.
1270
+ body.style.height = (_maxKeyFrame + documentElement.clientHeight) + 'px';
1271
+ }
1272
+
1273
+ //The scroll offset may now be larger than needed (on desktop the browser/os prevents scrolling farther than the bottom).
1274
+ if(_isMobile) {
1275
+ _instance.setScrollTop(Math.min(_instance.getScrollTop(), _maxKeyFrame));
1276
+ } else {
1277
+ //Remember and reset the scroll pos (#217).
1278
+ _instance.setScrollTop(pos, true);
1279
+ }
1280
+
1281
+ _forceRender = true;
1282
+ };
1283
+
1284
+ /*
1285
+ * Returns the height of the document.
1286
+ */
1287
+ var _getDocumentHeight = function() {
1288
+ var skrollrBodyHeight = (_skrollrBody && _skrollrBody.offsetHeight || 0);
1289
+ var bodyHeight = Math.max(skrollrBodyHeight, body.scrollHeight, body.offsetHeight, documentElement.scrollHeight, documentElement.offsetHeight, documentElement.clientHeight);
1290
+
1291
+ return bodyHeight - documentElement.clientHeight;
1292
+ };
1293
+
1294
+ /**
1295
+ * Returns a string of space separated classnames for the current element.
1296
+ * Works with SVG as well.
1297
+ */
1298
+ var _getClass = function(element) {
1299
+ var prop = 'className';
1300
+
1301
+ //SVG support by using className.baseVal instead of just className.
1302
+ if(window.SVGElement && element instanceof window.SVGElement) {
1303
+ element = element[prop];
1304
+ prop = 'baseVal';
1305
+ }
1306
+
1307
+ return element[prop];
1308
+ };
1309
+
1310
+ /**
1311
+ * Adds and removes a CSS classes.
1312
+ * Works with SVG as well.
1313
+ * add and remove are arrays of strings,
1314
+ * or if remove is ommited add is a string and overwrites all classes.
1315
+ */
1316
+ var _updateClass = function(element, add, remove) {
1317
+ var prop = 'className';
1318
+
1319
+ //SVG support by using className.baseVal instead of just className.
1320
+ if(window.SVGElement && element instanceof window.SVGElement) {
1321
+ element = element[prop];
1322
+ prop = 'baseVal';
1323
+ }
1324
+
1325
+ //When remove is ommited, we want to overwrite/set the classes.
1326
+ if(remove === undefined) {
1327
+ element[prop] = add;
1328
+ return;
1329
+ }
1330
+
1331
+ //Cache current classes. We will work on a string before passing back to DOM.
1332
+ var val = element[prop];
1333
+
1334
+ //All classes to be removed.
1335
+ var classRemoveIndex = 0;
1336
+ var removeLength = remove.length;
1337
+
1338
+ for(; classRemoveIndex < removeLength; classRemoveIndex++) {
1339
+ val = _untrim(val).replace(_untrim(remove[classRemoveIndex]), ' ');
1340
+ }
1341
+
1342
+ val = _trim(val);
1343
+
1344
+ //All classes to be added.
1345
+ var classAddIndex = 0;
1346
+ var addLength = add.length;
1347
+
1348
+ for(; classAddIndex < addLength; classAddIndex++) {
1349
+ //Only add if el not already has class.
1350
+ if(_untrim(val).indexOf(_untrim(add[classAddIndex])) === -1) {
1351
+ val += ' ' + add[classAddIndex];
1352
+ }
1353
+ }
1354
+
1355
+ element[prop] = _trim(val);
1356
+ };
1357
+
1358
+ var _trim = function(a) {
1359
+ return a.replace(rxTrim, '');
1360
+ };
1361
+
1362
+ /**
1363
+ * Adds a space before and after the string.
1364
+ */
1365
+ var _untrim = function(a) {
1366
+ return ' ' + a + ' ';
1367
+ };
1368
+
1369
+ var _now = Date.now || function() {
1370
+ return +new Date();
1371
+ };
1372
+
1373
+ var _keyFrameComparator = function(a, b) {
1374
+ return a.frame - b.frame;
1375
+ };
1376
+
1377
+ /*
1378
+ * Private variables.
1379
+ */
1380
+
1381
+ //Singleton
1382
+ var _instance;
1383
+
1384
+ /*
1384
1385
  A list of all elements which should be animated associated with their the metadata.
1385
1386
  Exmaple skrollable with two key frames animating from 100px width to 20px:
1386
1387
 
@@ -1412,50 +1413,50 @@
1412
1413
  ]
1413
1414
  };
1414
1415
  */
1415
- var _skrollables;
1416
+ var _skrollables;
1416
1417
 
1417
- var _skrollrBody;
1418
+ var _skrollrBody;
1418
1419
 
1419
- var _listeners;
1420
- var _forceHeight;
1421
- var _maxKeyFrame = 0;
1420
+ var _listeners;
1421
+ var _forceHeight;
1422
+ var _maxKeyFrame = 0;
1422
1423
 
1423
- var _scale = 1;
1424
- var _constants;
1424
+ var _scale = 1;
1425
+ var _constants;
1425
1426
 
1426
- //Current direction (up/down).
1427
- var _direction = 'down';
1427
+ //Current direction (up/down).
1428
+ var _direction = 'down';
1428
1429
 
1429
- //The last top offset value. Needed to determine direction.
1430
- var _lastTop = -1;
1430
+ //The last top offset value. Needed to determine direction.
1431
+ var _lastTop = -1;
1431
1432
 
1432
- //The last time we called the render method (doesn't mean we rendered!).
1433
- var _lastRenderCall = _now();
1433
+ //The last time we called the render method (doesn't mean we rendered!).
1434
+ var _lastRenderCall = _now();
1434
1435
 
1435
- //Will contain data about a running scrollbar animation, if any.
1436
- var _scrollAnimation;
1436
+ //Will contain data about a running scrollbar animation, if any.
1437
+ var _scrollAnimation;
1437
1438
 
1438
- var _smoothScrollingEnabled;
1439
+ var _smoothScrollingEnabled;
1439
1440
 
1440
- //Will contain settins for smooth scrolling if enabled.
1441
- var _smoothScrolling;
1441
+ //Will contain settins for smooth scrolling if enabled.
1442
+ var _smoothScrolling;
1442
1443
 
1443
- //Can be set by any operation/event to force rendering even if the scrollbar didn't move.
1444
- var _forceRender;
1444
+ //Can be set by any operation/event to force rendering even if the scrollbar didn't move.
1445
+ var _forceRender;
1445
1446
 
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;
1447
+ //Each skrollable gets an unique ID incremented for each skrollable.
1448
+ //The ID is the index in the _skrollables array.
1449
+ var _skrollableIdCounter = 0;
1449
1450
 
1450
- var _edgeStrategy;
1451
+ var _edgeStrategy;
1451
1452
 
1452
1453
 
1453
- //Mobile specific vars. Will be stripped by UglifyJS when not in use.
1454
- var _isMobile = false;
1454
+ //Mobile specific vars. Will be stripped by UglifyJS when not in use.
1455
+ var _isMobile = false;
1455
1456
 
1456
- //The virtual scroll offset when using mobile scrolling.
1457
- var _mobileOffset = 0;
1457
+ //The virtual scroll offset when using mobile scrolling.
1458
+ var _mobileOffset = 0;
1458
1459
 
1459
- //If the browser supports 3d transforms, this will be filled with 'translateZ(0)' (empty string otherwise).
1460
- var _translateZ;
1461
- }(window, document));
1460
+ //If the browser supports 3d transforms, this will be filled with 'translateZ(0)' (empty string otherwise).
1461
+ var _translateZ;
1462
+ }(window, document));