skrollr-rails 0.6.4 → 0.6.5

Sign up to get free protection for your applications and to get access to all the features.
@@ -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));