skrollr-rails 0.5.13.rc1

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in skrollr-rails.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2013 Nick Reed
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,70 @@
1
+ # Skrollr-Rails
2
+
3
+ [Skrollr](https://github.com/Prinzhorn/skrollr) is a stand-alone parallax scrolling library for mobile and desktop.
4
+
5
+ The **skrollr-rails** gem integrates skrollr with the Rails asset pipeline for ease of use and version control.
6
+
7
+ ## Installation
8
+
9
+ Add this line to your application's Gemfile:
10
+
11
+ gem 'skrollr-rails'
12
+
13
+ And then execute:
14
+
15
+ $ bundle
16
+
17
+ Or install it yourself as:
18
+
19
+ $ gem install skrollr-rails
20
+
21
+ ## Setup
22
+
23
+ This gem provides four javascript files you can include in your `app/assets/javascripts/application.js` file.
24
+
25
+ ##### Skrollr
26
+
27
+ The skrollr core file. That's all you need for modern desktop browsers.
28
+
29
+ ```javascript
30
+ //= require skrollr
31
+ ```
32
+
33
+ ##### Skrollr IE compatibility
34
+
35
+ For IE < 9, include it after the core using conditional comments. The plugin makes IE understand opacity, rgb() and hsl() (the ones with alpha are mapped to them) and it creates a very simple document.querySelector polyfill which only supports ID selectors (using getElementById). Needed if you want to use data-anchor-target.
36
+
37
+ ```javascript
38
+ //= require skrollr
39
+ //= require skrollr.ie
40
+ ```
41
+
42
+ ##### Skrollr mobile
43
+
44
+ Includes iScroll and a bridge script which initializes iScroll and makes it work with skrollr. Include it after the core when you need mobile support.
45
+
46
+ ```javascript
47
+ //= require skrollr
48
+ //= require skrollr.mobile
49
+ ```
50
+
51
+ ##### Skrollr menu plugin
52
+
53
+ This plugin makes hashlinks scroll nicely to their target position.
54
+
55
+ ```javascript
56
+ //= require skrollr
57
+ //= require skrollr.menu
58
+ ```
59
+
60
+ ## Versioning
61
+
62
+ The version number of the skrollr-rails gem corresponds directly with the skrollr library.
63
+
64
+ ## Contributing
65
+
66
+ 1. Fork it
67
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
68
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
69
+ 4. Push to the branch (`git push origin my-new-feature`)
70
+ 5. Submit a new Pull Request
data/Rakefile ADDED
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
@@ -0,0 +1,7 @@
1
+ require "skrollr-rails/version"
2
+
3
+ module Skrollr
4
+ module Rails
5
+ require "skrollr-rails/engine"
6
+ end
7
+ end
@@ -0,0 +1,6 @@
1
+ module Skrollr
2
+ module Rails
3
+ class Engine < ::Rails::Engine
4
+ end
5
+ end
6
+ end
@@ -0,0 +1,5 @@
1
+ module Skrollr
2
+ module Rails
3
+ VERSION = "0.5.13.rc1"
4
+ end
5
+ end
@@ -0,0 +1,17 @@
1
+ # -*- encoding: utf-8 -*-
2
+ require File.expand_path("../lib/skrollr-rails/version", __FILE__)
3
+
4
+ Gem::Specification.new do |s|
5
+ s.name = 'skrollr-rails'
6
+ s.version = Skrollr::Rails::VERSION
7
+ s.authors = ['Nick Reed']
8
+ s.email = ['reednj77@gmail.com']
9
+ s.description = %q{Integrate the skrollr javascript library with the Rails asset pipeline}
10
+ s.summary = %q{Skrollr is a stand-alone parallax scrolling library for mobile and desktop. This gem integrates skrollr with the Rails asset pipeline for ease of use and version control.}
11
+ s.homepage = 'https://github.com/reednj77/skrollr-rails'
12
+
13
+ s.files = `git ls-files`.split("\n")
14
+ s.require_paths = ['lib']
15
+
16
+ s.add_dependency 'rails', '>= 3.1.0'
17
+ end
@@ -0,0 +1,121 @@
1
+ /*!
2
+ * Plugin for skrollr.
3
+ * This plugin brings opacity and hsl colors to IE < 9.
4
+ *
5
+ * https://github.com/Prinzhorn/skrollr
6
+ *
7
+ * free to use under terms of MIT license
8
+ */
9
+ (function(document, skrollr) {
10
+ var rxHSLAColor = /hsla?\(\s*(-?[\d.]+)\s*,\s*(-?[\d.]+)%\s*,\s*(-?[\d.]+)%.*?\)/g;
11
+ var rxRGBAColor = /rgba?\(\s*(-?[\d.]+%?)\s*,\s*(-?[\d.]+%?)\s*,\s*(-?[\d.]+%?).*?\)/g;
12
+ var rxID = /^#[^\s]+$/;
13
+
14
+ var _setStyle = skrollr.setStyle;
15
+
16
+ //Monkeypatch the setStyle function.
17
+ skrollr.setStyle = function(el, prop, val) {
18
+ //Original function call.
19
+ _setStyle.apply(this, arguments);
20
+
21
+ var style = el.style;
22
+ var matched;
23
+
24
+ //IE opacity
25
+ if(prop === 'opacity') {
26
+ style.zoom = 1;
27
+
28
+ //Remove filter attribute in IE
29
+ if(val >= 1 && style.removeAttribute) {
30
+ style.removeAttribute('filter');
31
+ } else {
32
+ style.filter = 'alpha(opacity=' + val * 100 + ')';
33
+ }
34
+
35
+ return;
36
+ }
37
+
38
+ //Fast pre check
39
+ if(val.indexOf('hsl') > -1) {
40
+ matched = false;
41
+
42
+ //Replace hsl(a) with hex if needed (ignoring alpha).
43
+ val = val.replace(rxHSLAColor, function(x, h, s, l) {
44
+ matched = true;
45
+
46
+ return toHex.hsl(parseFloat(h), parseFloat(s), parseFloat(l));
47
+ });
48
+
49
+ if(matched) {
50
+ try {
51
+ style[prop] = val;
52
+ } catch(ignore) {}
53
+
54
+ return;
55
+ }
56
+ }
57
+
58
+ //Fast pre check
59
+ if(val.indexOf('rgb') > -1) {
60
+ matched = false;
61
+
62
+ //Replace rgba with hex if needed (ignoring alpha).
63
+ val = val.replace(rxRGBAColor, function(s, r, g, b) {
64
+ matched = true;
65
+
66
+ r = parseFloat(r, 10);
67
+ g = parseFloat(g, 10);
68
+ b = parseFloat(b, 10);
69
+
70
+ //rgba allows percentage notation.
71
+ if(s.indexOf('%') > -1) {
72
+ r = (r / 100) * 255;
73
+ g = (g / 100) * 255;
74
+ b = (b / 100) * 255;
75
+ }
76
+
77
+ return toHex.rgb(r | 0, g | 0, b | 0);
78
+ });
79
+
80
+ if(matched) {
81
+ try {
82
+ style[prop] = val;
83
+ } catch(ignore) {}
84
+
85
+ return;
86
+ }
87
+ }
88
+ };
89
+
90
+
91
+ /**
92
+ * Converts rgb or hsl color to hex color.
93
+ */
94
+ var toHex = {
95
+ //Credits to aemkei, jed and others
96
+ //Based on https://gist.github.com/1325937 and https://gist.github.com/983535
97
+ hsl: function(a,b,c,y){
98
+ a%=360;
99
+ a=a/60;c=c/100;b=[c+=b*=(c<0.5?c:1-c)/100,c-a%1*b*2,c-=b*=2,c,c+a%1*b,c+b];
100
+
101
+ y = [b[~~a%6],b[(a|16)%6],b[(a|8)%6]];
102
+
103
+ return toHex.rgb(parseInt(y[0] * 255, 10), parseInt(y[1] * 255, 10), parseInt(y[2] * 255, 10));
104
+ },
105
+ //https://gist.github.com/983535
106
+ rgb: function(a,b,c){
107
+ return'#' + ((256+a<<8|b)<<8|c).toString(16).slice(1);
108
+ }
109
+ };
110
+
111
+ /*
112
+ A really bad polyfill. But the main use-case for data-anchor-target are IDs.
113
+ */
114
+ document.querySelector = document.querySelector || function(selector) {
115
+ if(!rxID.test(selector)) {
116
+ throw 'Unsupported selector "' + selector + '". The querySelector polyfill only works for IDs.';
117
+ }
118
+
119
+ return document.getElementById(selector.substr(1));
120
+ };
121
+ }(document, window.skrollr));
@@ -0,0 +1,1203 @@
1
+ /*!
2
+ * skrollr
3
+ *
4
+ * https://github.com/Prinzhorn/skrollr
5
+ *
6
+ * free to use under terms of MIT license
7
+ */
8
+ (function(window, document, undefined) {
9
+ 'use strict';
10
+
11
+ /*
12
+ * Global api.
13
+ */
14
+ var skrollr = window.skrollr = {
15
+ get: function() {
16
+ return _instance;
17
+ },
18
+ //Main entry point.
19
+ init: function(options) {
20
+ return _instance || new Skrollr(options);
21
+ },
22
+ VERSION: '0.5.13'
23
+ };
24
+
25
+ //Minify optimization.
26
+ var hasProp = Object.prototype.hasOwnProperty;
27
+
28
+ //They will be filled when skrollr gets initialized.
29
+ var documentElement;
30
+ var body;
31
+
32
+ var RENDERED_CLASS = 'rendered';
33
+ var UNRENDERED_CLASS = 'un' + RENDERED_CLASS;
34
+ var SKROLLABLE_CLASS = 'skrollable';
35
+ var SKROLLR_CLASS = 'skrollr';
36
+ var NO_SKROLLR_CLASS = 'no-' + SKROLLR_CLASS;
37
+
38
+ var DEFAULT_EASING = 'linear';
39
+ var DEFAULT_DURATION = 1000;
40
+
41
+ var SMOOTH_SCROLLING_DURATION = 200;
42
+
43
+ var ANCHOR_START = 'start';
44
+ var ANCHOR_END = 'end';
45
+ var ANCHOR_TOP = 'top';
46
+ var ANCHOR_CENTER = 'center';
47
+ var ANCHOR_BOTTOM = 'bottom';
48
+
49
+ var SKROLLABLE_HAS_RENDERED_CLASS_PROPERTY = '___has_rendered_class';
50
+
51
+ //The property which will be added to the DOM element to hold the ID of the skrollable.
52
+ var SKROLLABLE_ID_DOM_PROPERTY = '___skrollable_id';
53
+
54
+ var requestAnimFrame = window.requestAnimationFrame;
55
+
56
+ //Request animation frame polyfill.
57
+ //Credits go to Erik Möller (http://my.opera.com/emoller/blog/2011/12/20/requestanimationframe-for-smart-er-animating)
58
+ (function() {
59
+ var vendors = ['ms', 'moz', 'webkit', 'o'];
60
+ var i;
61
+
62
+ for(i = 0; i < vendors.length && !requestAnimFrame; i++) {
63
+ requestAnimFrame = window[vendors[i] + 'RequestAnimationFrame'];
64
+ }
65
+
66
+ var lastTime = 0;
67
+
68
+ if (!requestAnimFrame) {
69
+ requestAnimFrame = function(callback) {
70
+ var currTime = _now();
71
+ var timeToCall = Math.max(0, 30 - (currTime - lastTime));
72
+
73
+ window.setTimeout(function() {
74
+ callback(currTime + timeToCall);
75
+ }, timeToCall);
76
+
77
+ lastTime = currTime + timeToCall;
78
+ };
79
+ }
80
+ }());
81
+
82
+
83
+ var rxTrim = /^\s*(.+)\s*$/m;
84
+
85
+ //Find all data-attributes. data-[_constant]-[offset]-[anchor]-[anchor].
86
+ var rxKeyframeAttribute = /^data(?:-(_\w+))?(?:-?(-?\d+))?(?:-?(start|end|top|center|bottom))?(?:-?(top|center|bottom))?$/;
87
+
88
+ var rxPropValue = /\s*([a-z\-\[\]]+)\s*:\s*(.+?)\s*(?:;|$)/gi;
89
+
90
+ //Easing function names follow the property in square brackets.
91
+ var rxPropEasing = /^([a-z\-]+)\[(\w+)\]$/;
92
+
93
+ var rxCamelCase = /-([a-z])/g;
94
+ var rxCamelCaseFn = function(str, letter) {
95
+ return letter.toUpperCase();
96
+ };
97
+
98
+ //Numeric values with optional sign.
99
+ var rxNumericValue = /[\-+]?[\d]*\.?[\d]+/g;
100
+
101
+ //Used to replace occurences of {?} with a number.
102
+ var rxInterpolateString = /\{\?\}/g;
103
+
104
+ //Finds rgb(a) colors, which don't use the percentage notation.
105
+ var rxRGBAIntegerColor = /rgba?\(\s*-?\d+\s*,\s*-?\d+\s*,\s*-?\d+/g;
106
+
107
+ //Finds all gradients.
108
+ var rxGradient = /[a-z\-]+-gradient/g;
109
+
110
+ //Vendor prefix. Will be set once skrollr gets initialized.
111
+ var theCSSPrefix;
112
+ var theDashedCSSPrefix;
113
+
114
+ //Will be called once (when skrollr gets initialized).
115
+ var detectCSSPrefix = function() {
116
+ //Only relevant prefixes. May be extended.
117
+ //Could be dangerous if there will ever be a CSS property which actually starts with "ms". Don't hope so.
118
+ var rxPrefixes = /^(?:O|Moz|webkit|ms)|(?:-(?:o|moz|webkit|ms)-)/;
119
+
120
+ //Detect prefix for current browser by finding the first property using a prefix.
121
+ if(window.getComputedStyle) {
122
+ var style = window.getComputedStyle(body, null);
123
+
124
+ for(var k in style) {
125
+ //We check the key and if the key is a number, we check the value as well, because safari's getComputedStyle returns some weird array-like thingy.
126
+ theCSSPrefix = (k.match(rxPrefixes) || (+k == k && style[k].match(rxPrefixes)));
127
+
128
+ if(theCSSPrefix) {
129
+ break;
130
+ }
131
+ }
132
+
133
+ //Did we even detect a prefix?
134
+ if(theCSSPrefix) {
135
+ theCSSPrefix = theCSSPrefix[0];
136
+
137
+ //We could have detected either a dashed prefix or this camelCaseish-inconsistent stuff.
138
+ if(theCSSPrefix.slice(0,1) === '-') {
139
+ theDashedCSSPrefix = theCSSPrefix;
140
+
141
+ //There's no logic behind these. Need a look up.
142
+ theCSSPrefix = ({
143
+ '-webkit-': 'webkit',
144
+ '-moz-': 'Moz',
145
+ '-ms-': 'ms',
146
+ '-o-': 'O'
147
+ })[theCSSPrefix];
148
+ } else {
149
+ theDashedCSSPrefix = '-' + theCSSPrefix.toLowerCase() + '-';
150
+ }
151
+ }
152
+ }
153
+ };
154
+
155
+ //Built-in easing functions.
156
+ var easings = {
157
+ begin: function() {
158
+ return 0;
159
+ },
160
+ end: function() {
161
+ return 1;
162
+ },
163
+ linear: function(p) {
164
+ return p;
165
+ },
166
+ quadratic: function(p) {
167
+ return p * p;
168
+ },
169
+ cubic: function(p) {
170
+ return p * p * p;
171
+ },
172
+ swing: function(p) {
173
+ return (-Math.cos(p * Math.PI) / 2) + 0.5;
174
+ },
175
+ sqrt: function(p) {
176
+ return Math.sqrt(p);
177
+ },
178
+ //see https://www.desmos.com/calculator/tbr20s8vd2 for how I did this
179
+ bounce: function(p) {
180
+ var a;
181
+
182
+ if(p <= 0.5083) {
183
+ a = 3;
184
+ } else if(p <= 0.8489) {
185
+ a = 9;
186
+ } else if(p <= 0.96208) {
187
+ a = 27;
188
+ } else if(p <= 0.99981) {
189
+ a = 91;
190
+ } else {
191
+ return 1;
192
+ }
193
+
194
+ return 1 - Math.abs(3 * Math.cos(p * a * 1.028) / a);
195
+ }
196
+ };
197
+
198
+ /**
199
+ * Constructor.
200
+ */
201
+ function Skrollr(options) {
202
+ documentElement = document.documentElement;
203
+ body = document.body;
204
+
205
+ detectCSSPrefix();
206
+
207
+ _instance = this;
208
+
209
+ options = options || {};
210
+
211
+ _constants = options.constants || {};
212
+
213
+ //We allow defining custom easings or overwrite existing
214
+ if(options.easing) {
215
+ for(var e in options.easing) {
216
+ easings[e] = options.easing[e];
217
+ }
218
+ }
219
+
220
+ _listeners = {
221
+ //Function to be called right before rendering.
222
+ beforerender: options.beforerender,
223
+
224
+ //Function to be called right after finishing rendering.
225
+ render: options.render
226
+ };
227
+
228
+ //forceHeight is true by default
229
+ _forceHeight = options.forceHeight !== false;
230
+
231
+ _smoothScrollingEnabled = options.smoothScrolling !== false;
232
+
233
+ //Dummy object. Will be overwritten in the _render method when smooth scrolling is calculated.
234
+ _smoothScrolling = {
235
+ targetTop: _instance.getScrollTop()
236
+ };
237
+
238
+ if(_forceHeight) {
239
+ _scale = options.scale || 1;
240
+ }
241
+
242
+ //Remove "no-skrollr" and add "skrollr" to the HTML element.
243
+ _updateClass(documentElement, [SKROLLR_CLASS], [NO_SKROLLR_CLASS]);
244
+
245
+ if(_forceHeight) {
246
+ //Add a dummy element in order to get a large enough scrollbar.
247
+ //On mobile and later desktop versions a #skrollr-body element takes this role.
248
+
249
+ var dummy = document.getElementById('skrollr-body') || document.createElement('div');
250
+ var dummyStyle = dummy.style;
251
+
252
+ dummyStyle.minWidth = '1px';
253
+ dummyStyle.position = 'absolute';
254
+ dummyStyle.top = dummyStyle.zIndex = '0';
255
+
256
+ //It's the dummy we just created.
257
+ if(!dummy.id) {
258
+ //Give the dummy element a small width and move it to the right to not overlap or interfere with the content.
259
+ //Fixes #76.
260
+ dummyStyle.width = '1px';
261
+ dummyStyle.right = '0';
262
+
263
+ body.appendChild(dummy);
264
+ }
265
+
266
+ //Update height of dummy div when reflowing (e.g. window size is changed).
267
+ (function(oldReflowFn) {
268
+ _reflow = function() {
269
+ oldReflowFn.apply(this, arguments);
270
+
271
+ //"force" the height.
272
+ dummyStyle.height = (_maxKeyFrame + documentElement.clientHeight) + 'px';
273
+ };
274
+ }(_reflow));
275
+ }
276
+
277
+ _instance.refresh();
278
+
279
+ skrollr.addEvent(window, 'resize', _reflow);
280
+
281
+ //Let's go.
282
+ (function animloop(){
283
+ //This is how the cool kids use requestAnimationFrame
284
+ //http://paulirish.com/2011/requestanimationframe-for-smart-animating/
285
+ requestAnimFrame(animloop);
286
+ _render();
287
+ }());
288
+
289
+ return _instance;
290
+ }
291
+
292
+ /**
293
+ * (Re)parses some or all elements.
294
+ */
295
+ Skrollr.prototype.refresh = function(elements) {
296
+ var elementIndex;
297
+ var ignoreID = false;
298
+
299
+ //Completely reparse anything without argument.
300
+ if(elements === undefined) {
301
+ //Ignore that some elements may already have a skrollable ID.
302
+ ignoreID = true;
303
+
304
+ _skrollables = [];
305
+ _skrollableIdCounter = 0;
306
+
307
+ elements = document.getElementsByTagName('*');
308
+ } else {
309
+ //We accept a single element or an array of elements.
310
+ elements = [].concat(elements);
311
+ }
312
+
313
+ for(elementIndex = 0; elementIndex < elements.length; elementIndex++) {
314
+ var el = elements[elementIndex];
315
+ var anchorTarget = el;
316
+ var keyFrames = [];
317
+
318
+ //If this particular element should be smooth scrolled.
319
+ var smoothScrollThis = _smoothScrollingEnabled;
320
+
321
+ if(!el.attributes) {
322
+ continue;
323
+ }
324
+
325
+ //Iterate over all attributes and search for key frame attributes.
326
+ for (var attributeIndex = 0; attributeIndex < el.attributes.length; attributeIndex++) {
327
+ var attr = el.attributes[attributeIndex];
328
+
329
+ if(attr.name === 'data-anchor-target') {
330
+ anchorTarget = document.querySelector(attr.value);
331
+
332
+ if(anchorTarget === null) {
333
+ throw 'Unable to find anchor target "' + attr.value + '"';
334
+ }
335
+
336
+ continue;
337
+ }
338
+
339
+ //Global smooth scrolling can be overridden by the element attribute.
340
+ if(attr.name === 'data-smooth-scrolling') {
341
+ smoothScrollThis = attr.value !== 'off';
342
+
343
+ continue;
344
+ }
345
+
346
+ var match = attr.name.match(rxKeyframeAttribute);
347
+
348
+ if(match !== null) {
349
+ var constant = match[1];
350
+
351
+ //If there is a constant, get it's value or fall back to 0.
352
+ constant = constant && _constants[constant.substr(1)] || 0;
353
+
354
+ //Parse key frame offset. If undefined will be casted to 0.
355
+ var offset = (match[2] | 0) + constant;
356
+ var anchor1 = match[3];
357
+ //If second anchor is not set, the first will be taken for both.
358
+ var anchor2 = match[4] || anchor1;
359
+
360
+ var kf = {
361
+ offset: offset,
362
+ props: attr.value,
363
+ //Point back to the element as well.
364
+ element: el
365
+ };
366
+
367
+ keyFrames.push(kf);
368
+
369
+ //"absolute" (or "classic") mode, where numbers mean absolute scroll offset.
370
+ if(!anchor1 || anchor1 === ANCHOR_START || anchor1 === ANCHOR_END) {
371
+ kf.mode = 'absolute';
372
+
373
+ //data-end needs to be calculated after all key frames are know.
374
+ if(anchor1 === ANCHOR_END) {
375
+ kf.isEnd = true;
376
+ } else {
377
+ //For data-start we can already set the key frame w/o calculations.
378
+ //#59: "scale" options should only affect absolute mode.
379
+ kf.frame = offset * _scale;
380
+
381
+ delete kf.offset;
382
+ }
383
+ }
384
+ //"relative" mode, where numbers are relative to anchors.
385
+ else {
386
+ kf.mode = 'relative';
387
+ kf.anchors = [anchor1, anchor2];
388
+ }
389
+ }
390
+ }
391
+
392
+ //Does this element have key frames?
393
+ if(keyFrames.length) {
394
+ //Will hold the original style and class attributes before we controlled the element (see #80).
395
+ var styleAttr, classAttr;
396
+
397
+ var id;
398
+
399
+ if(!ignoreID && SKROLLABLE_ID_DOM_PROPERTY in el) {
400
+ //We already have this element under control. Grab the corresponding skrollable id.
401
+ id = el[SKROLLABLE_ID_DOM_PROPERTY];
402
+ styleAttr = _skrollables[id].styleAttr;
403
+ classAttr = _skrollables[id].classAttr;
404
+ } else {
405
+ //It's an unknown element. Asign it a new skrollable id.
406
+ id = (el[SKROLLABLE_ID_DOM_PROPERTY] = _skrollableIdCounter++);
407
+ styleAttr = el.style.cssText;
408
+ classAttr = _getClass(el);
409
+ }
410
+
411
+ var skrollable = _skrollables[id] = {
412
+ element: el,
413
+ styleAttr: styleAttr,
414
+ classAttr: classAttr,
415
+ anchorTarget: anchorTarget,
416
+ keyFrames: keyFrames,
417
+ smoothScrolling: smoothScrollThis
418
+ };
419
+
420
+ _updateClass(el, [SKROLLABLE_CLASS, RENDERED_CLASS], [UNRENDERED_CLASS]);
421
+ skrollable[SKROLLABLE_HAS_RENDERED_CLASS_PROPERTY] = true;
422
+ }
423
+ }
424
+
425
+ //Reflow for the first time.
426
+ _reflow();
427
+
428
+ //Now that we got all key frame numbers right, actually parse the properties.
429
+ for(elementIndex = 0; elementIndex < elements.length; elementIndex++) {
430
+ var sk = _skrollables[elements[elementIndex][SKROLLABLE_ID_DOM_PROPERTY]];
431
+
432
+ if(sk === undefined) {
433
+ continue;
434
+ }
435
+
436
+ //Make sure they are in order
437
+ sk.keyFrames.sort(_keyFrameComparator);
438
+
439
+ //Parse the property string to objects
440
+ _parseProps(sk);
441
+
442
+ //Fill key frames with missing properties from left and right
443
+ _fillProps(sk);
444
+ }
445
+
446
+ return _instance;
447
+ };
448
+
449
+ /**
450
+ * Transform "relative" mode to "absolute" mode.
451
+ * That is, calculate anchor position and offset of element.
452
+ */
453
+ Skrollr.prototype.relativeToAbsolute = function(element, viewportAnchor, elementAnchor) {
454
+ var viewportHeight = documentElement.clientHeight;
455
+ var box = element.getBoundingClientRect();
456
+ var absolute = box.top;
457
+
458
+ //#100: IE doesn't supply "height" with getBoundingClientRect.
459
+ var boxHeight = box.bottom - box.top;
460
+
461
+ if(viewportAnchor === ANCHOR_BOTTOM) {
462
+ absolute -= viewportHeight;
463
+ } else if(viewportAnchor === ANCHOR_CENTER) {
464
+ absolute -= viewportHeight / 2;
465
+ }
466
+
467
+ if(elementAnchor === ANCHOR_BOTTOM) {
468
+ absolute += boxHeight;
469
+ } else if(elementAnchor === ANCHOR_CENTER) {
470
+ absolute += boxHeight / 2;
471
+ }
472
+
473
+ //Compensate scrolling since getBoundingClientRect is relative to viewport.
474
+ absolute += _instance.getScrollTop();
475
+
476
+ return (absolute + 0.5) | 0;
477
+ };
478
+
479
+ /**
480
+ * Animates scroll top to new position.
481
+ */
482
+ Skrollr.prototype.animateTo = function(top, options) {
483
+ options = options || {};
484
+
485
+ var now = _now();
486
+ var scrollTop = _instance.getScrollTop();
487
+
488
+ //Setting this to a new value will automatically cause the current animation to stop, if any.
489
+ _scrollAnimation = {
490
+ startTop: scrollTop,
491
+ topDiff: top - scrollTop,
492
+ targetTop: top,
493
+ duration: options.duration || DEFAULT_DURATION,
494
+ startTime: now,
495
+ endTime: now + (options.duration || DEFAULT_DURATION),
496
+ easing: easings[options.easing || DEFAULT_EASING],
497
+ done: options.done
498
+ };
499
+
500
+ //Don't queue the animation if there's nothing to animate.
501
+ if(!_scrollAnimation.topDiff) {
502
+ if(_scrollAnimation.done) {
503
+ _scrollAnimation.done.call(_instance, false);
504
+ }
505
+
506
+ _scrollAnimation = undefined;
507
+ }
508
+
509
+ return _instance;
510
+ };
511
+
512
+ /**
513
+ * Stops animateTo animation.
514
+ */
515
+ Skrollr.prototype.stopAnimateTo = function() {
516
+ if(_scrollAnimation && _scrollAnimation.done) {
517
+ _scrollAnimation.done.call(_instance, true);
518
+ }
519
+
520
+ _scrollAnimation = undefined;
521
+ };
522
+
523
+ /**
524
+ * Returns if an animation caused by animateTo is currently running.
525
+ */
526
+ Skrollr.prototype.isAnimatingTo = function() {
527
+ return !!_scrollAnimation;
528
+ };
529
+
530
+ Skrollr.prototype.setScrollTop = function(top) {
531
+ //skrollr.iscroll is an instance of iscroll available in mobile mode
532
+ if(skrollr.iscroll) {
533
+ //Notice the minus.
534
+ skrollr.iscroll.scrollTo(0, -top);
535
+ } else {
536
+ window.scrollTo(0, top);
537
+ }
538
+
539
+ return _instance;
540
+ };
541
+
542
+ Skrollr.prototype.getScrollTop = function() {
543
+ //skrollr.iscroll is an instance of iscroll available in mobile mode
544
+ if(skrollr.iscroll) {
545
+ return -skrollr.iscroll.y;
546
+ } else {
547
+ return window.pageYOffset || documentElement.scrollTop || body.scrollTop || 0;
548
+ }
549
+ };
550
+
551
+ Skrollr.prototype.on = function(name, fn) {
552
+ _listeners[name] = fn;
553
+
554
+ return _instance;
555
+ };
556
+
557
+ Skrollr.prototype.off = function(name) {
558
+ delete _listeners[name];
559
+
560
+ return _instance;
561
+ };
562
+
563
+ /*
564
+ Private methods.
565
+ */
566
+
567
+ /**
568
+ * Updates key frames which depend on others.
569
+ * That is "end" in "absolute" mode and all key frames in "relative" mode.
570
+ */
571
+ var _updateDependentKeyFrames = function() {
572
+ var skrollable;
573
+ var element;
574
+ var anchorTarget;
575
+ var keyFrames;
576
+ var kf;
577
+ var skrollableIndex;
578
+ var keyFrameIndex;
579
+
580
+ //For relative mode, we need to reset style and class. See #80
581
+ var styleAttr;
582
+ var classAttr;
583
+
584
+ //First process all relative-mode elements and find the max key frame.
585
+ for(skrollableIndex = 0; skrollableIndex < _skrollables.length; skrollableIndex++) {
586
+ skrollable = _skrollables[skrollableIndex];
587
+ element = skrollable.element;
588
+ anchorTarget = skrollable.anchorTarget;
589
+ keyFrames = skrollable.keyFrames;
590
+
591
+ for(keyFrameIndex = 0; keyFrameIndex < keyFrames.length; keyFrameIndex++) {
592
+ kf = keyFrames[keyFrameIndex];
593
+
594
+ if(kf.mode === 'relative') {
595
+ //Save the current style and class (#80)
596
+ styleAttr = element.style.cssText;
597
+ classAttr = _getClass(element);
598
+
599
+ //Reset style and class to original (#80)
600
+ element.style.cssText = skrollable.styleAttr;
601
+ _updateClass(element, skrollable.classAttr);
602
+
603
+ kf.frame = _instance.relativeToAbsolute(anchorTarget, kf.anchors[0], kf.anchors[1]) - kf.offset;
604
+
605
+ //Now set style and class back to what skrollr did to it.
606
+ element.style.cssText = styleAttr;
607
+ _updateClass(element, classAttr);
608
+ }
609
+
610
+ //Only search for max key frame when forceHeight is enabled.
611
+ if(_forceHeight) {
612
+ //Find the max key frame, but don't use one of the data-end ones for comparison.
613
+ if(!kf.isEnd && kf.frame > _maxKeyFrame) {
614
+ _maxKeyFrame = kf.frame;
615
+ }
616
+ }
617
+ }
618
+ }
619
+
620
+ //#133: The document can be larger than the maxKeyFrame we found.
621
+ _maxKeyFrame = Math.max(_maxKeyFrame, _getDocumentHeight());
622
+
623
+ //Now process all data-end keyframes.
624
+ for(skrollableIndex = 0; skrollableIndex < _skrollables.length; skrollableIndex++) {
625
+ skrollable = _skrollables[skrollableIndex];
626
+ keyFrames = skrollable.keyFrames;
627
+
628
+ for(keyFrameIndex = 0; keyFrameIndex < keyFrames.length; keyFrameIndex++) {
629
+ kf = keyFrames[keyFrameIndex];
630
+
631
+ if(kf.isEnd) {
632
+ kf.frame = _maxKeyFrame - kf.offset;
633
+ }
634
+ }
635
+ }
636
+ };
637
+
638
+ /**
639
+ * Calculates and sets the style properties for the element at the given frame.
640
+ * @param fakeFrame The frame to render at when smooth scrolling is enabled.
641
+ * @param actualFrame The actual frame we are at.
642
+ */
643
+ var _calcSteps = function(fakeFrame, actualFrame) {
644
+ //Iterate over all skrollables.
645
+ for(var skrollableIndex = 0; skrollableIndex < _skrollables.length; skrollableIndex++) {
646
+ var skrollable = _skrollables[skrollableIndex];
647
+ var frame = skrollable.smoothScrolling ? fakeFrame : actualFrame;
648
+ var frames = skrollable.keyFrames;
649
+ var firstFrame = frames[0].frame;
650
+ var lastFrame = frames[frames.length - 1].frame;
651
+ var atFirst = frame <= firstFrame;
652
+ var atLast = frame >= lastFrame;
653
+ var key;
654
+ var value;
655
+
656
+ //If we are before/after or exactly at the first/last frame, the element gets all props from this key frame.
657
+ if(atFirst || atLast) {
658
+ var props = frames[atFirst ? 0 : frames.length - 1].props;
659
+
660
+ for(key in props) {
661
+ if(hasProp.call(props, key)) {
662
+ value = _interpolateString(props[key].value);
663
+
664
+ skrollr.setStyle(skrollable.element, key, value);
665
+ }
666
+ }
667
+
668
+ //Add the unrendered class when before or after first/last frame.
669
+ if(skrollable[SKROLLABLE_HAS_RENDERED_CLASS_PROPERTY] && (frame < firstFrame || frame > lastFrame)) {
670
+ _updateClass(skrollable.element, [UNRENDERED_CLASS], [RENDERED_CLASS]);
671
+
672
+ //Does a faster job than sth. like hasClass('string')
673
+ skrollable[SKROLLABLE_HAS_RENDERED_CLASS_PROPERTY] = false;
674
+ }
675
+
676
+ continue;
677
+ }
678
+
679
+ //We are between two frames.
680
+ if(!skrollable[SKROLLABLE_HAS_RENDERED_CLASS_PROPERTY]) {
681
+ _updateClass(skrollable.element, [RENDERED_CLASS], [UNRENDERED_CLASS]);
682
+
683
+ skrollable[SKROLLABLE_HAS_RENDERED_CLASS_PROPERTY] = true;
684
+ }
685
+
686
+ //Find out between which two key frames we are right now.
687
+ for(var keyFrameIndex = 0; keyFrameIndex < frames.length - 1; keyFrameIndex++) {
688
+ if(frame >= frames[keyFrameIndex].frame && frame <= frames[keyFrameIndex + 1].frame) {
689
+ var left = frames[keyFrameIndex];
690
+ var right = frames[keyFrameIndex + 1];
691
+
692
+ for(key in left.props) {
693
+ if(hasProp.call(left.props, key)) {
694
+ var progress = (frame - left.frame) / (right.frame - left.frame);
695
+
696
+ //Transform the current progress using the given easing function.
697
+ progress = left.props[key].easing(progress);
698
+
699
+ //Interpolate between the two values
700
+ value = _calcInterpolation(left.props[key].value, right.props[key].value, progress);
701
+
702
+ value = _interpolateString(value);
703
+
704
+ skrollr.setStyle(skrollable.element, key, value);
705
+ }
706
+ }
707
+
708
+ break;
709
+ }
710
+ }
711
+ }
712
+ };
713
+
714
+ /**
715
+ * Renders all elements
716
+ */
717
+ var _render = function() {
718
+ //We may render something else than the actual scrollbar position.
719
+ var renderTop = _instance.getScrollTop();
720
+
721
+ //If there's an animation, which ends in current render call, call the callback after rendering.
722
+ var afterAnimationCallback;
723
+ var now = _now();
724
+ var progress;
725
+
726
+ //Before actually rendering handle the scroll animation, if any.
727
+ if(_scrollAnimation) {
728
+ //It's over
729
+ if(now >= _scrollAnimation.endTime) {
730
+ renderTop = _scrollAnimation.targetTop;
731
+ afterAnimationCallback = _scrollAnimation.done;
732
+ _scrollAnimation = undefined;
733
+ } else {
734
+ //Map the current progress to the new progress using given easing function.
735
+ progress = _scrollAnimation.easing((now - _scrollAnimation.startTime) / _scrollAnimation.duration);
736
+
737
+ renderTop = (_scrollAnimation.startTop + progress * _scrollAnimation.topDiff) | 0;
738
+ }
739
+
740
+ _instance.setScrollTop(renderTop);
741
+ }
742
+ //Smooth scrolling only if there's no animation running.
743
+ else {
744
+ var smoothScrollingDiff = _smoothScrolling.targetTop - renderTop;
745
+
746
+ //The user scrolled, start new smooth scrolling.
747
+ if(smoothScrollingDiff) {
748
+ _smoothScrolling = {
749
+ startTop: _lastTop,
750
+ topDiff: renderTop - _lastTop,
751
+ targetTop: renderTop,
752
+ startTime: _lastRenderCall,
753
+ endTime: _lastRenderCall + SMOOTH_SCROLLING_DURATION
754
+ };
755
+ }
756
+
757
+ //Interpolate the internal scroll position (not the actual scrollbar).
758
+ if(now <= _smoothScrolling.endTime) {
759
+ //Map the current progress to the new progress using easing function.
760
+ progress = easings.sqrt((now - _smoothScrolling.startTime) / SMOOTH_SCROLLING_DURATION);
761
+
762
+ renderTop = (_smoothScrolling.startTop + progress * _smoothScrolling.topDiff) | 0;
763
+ }
764
+ }
765
+
766
+ //In OSX it's possible to have a negative scrolltop, so, we set it to zero.
767
+ if(renderTop < 0) {
768
+ renderTop = 0;
769
+ }
770
+
771
+ //Did the scroll position even change?
772
+ if(_forceRender || _lastTop !== renderTop) {
773
+ //Remember in which direction are we scrolling?
774
+ _direction = (renderTop >= _lastTop) ? 'down' : 'up';
775
+
776
+ _forceRender = false;
777
+
778
+ var listenerParams = {
779
+ curTop: renderTop,
780
+ lastTop: _lastTop,
781
+ maxTop: _maxKeyFrame,
782
+ direction: _direction
783
+ };
784
+
785
+ //Tell the listener we are about to render.
786
+ var continueRendering = _listeners.beforerender && _listeners.beforerender.call(_instance, listenerParams);
787
+
788
+ //The beforerender listener function is able the cancel rendering.
789
+ if(continueRendering !== false) {
790
+ //Now actually interpolate all the styles.
791
+ _calcSteps(renderTop, _instance.getScrollTop());
792
+
793
+ //Remember when we last rendered.
794
+ _lastTop = renderTop;
795
+
796
+ if(_listeners.render) {
797
+ _listeners.render.call(_instance, listenerParams);
798
+ }
799
+ }
800
+
801
+ if(afterAnimationCallback) {
802
+ afterAnimationCallback.call(_instance, false);
803
+ }
804
+ }
805
+
806
+ _lastRenderCall = now;
807
+ };
808
+
809
+ /**
810
+ * Parses the properties for each key frame of the given skrollable.
811
+ */
812
+ var _parseProps = function(skrollable) {
813
+ //Iterate over all key frames
814
+ for(var keyFrameIndex = 0; keyFrameIndex < skrollable.keyFrames.length; keyFrameIndex++) {
815
+ var frame = skrollable.keyFrames[keyFrameIndex];
816
+ var easing;
817
+ var value;
818
+ var prop;
819
+ var props = {};
820
+
821
+ var match;
822
+
823
+ while((match = rxPropValue.exec(frame.props)) !== null) {
824
+ prop = match[1];
825
+ value = match[2];
826
+
827
+ easing = prop.match(rxPropEasing);
828
+
829
+ //Is there an easing specified for this prop?
830
+ if(easing !== null) {
831
+ prop = easing[1];
832
+ easing = easing[2];
833
+ } else {
834
+ easing = DEFAULT_EASING;
835
+ }
836
+
837
+ //Exclamation point at first position forces the value to be taken literal.
838
+ value = value.indexOf('!') ? _parseProp(value) : [value.slice(1)];
839
+
840
+ //Save the prop for this key frame with his value and easing function
841
+ props[prop] = {
842
+ value: value,
843
+ easing: easings[easing]
844
+ };
845
+ }
846
+
847
+ frame.props = props;
848
+ }
849
+ };
850
+
851
+ /**
852
+ * Parses a value extracting numeric values and generating a format string
853
+ * for later interpolation of the new values in old string.
854
+ *
855
+ * @param val The CSS value to be parsed.
856
+ * @return Something like ["rgba(?%,?%, ?%,?)", 100, 50, 0, .7]
857
+ * where the first element is the format string later used
858
+ * and all following elements are the numeric value.
859
+ */
860
+ var _parseProp = function(val) {
861
+ var numbers = [];
862
+
863
+ //One special case, where floats don't work.
864
+ //We replace all occurences of rgba colors
865
+ //which don't use percentage notation with the percentage notation.
866
+ rxRGBAIntegerColor.lastIndex = 0;
867
+ val = val.replace(rxRGBAIntegerColor, function(rgba) {
868
+ return rgba.replace(rxNumericValue, function(n) {
869
+ return n / 255 * 100 + '%';
870
+ });
871
+ });
872
+
873
+ //Handle prefixing of "gradient" values.
874
+ //For now only the prefixed value will be set. Unprefixed isn't supported anyway.
875
+ if(theDashedCSSPrefix) {
876
+ rxGradient.lastIndex = 0;
877
+ val = val.replace(rxGradient, function(s) {
878
+ return theDashedCSSPrefix + s;
879
+ });
880
+ }
881
+
882
+ //Now parse ANY number inside this string and create a format string.
883
+ val = val.replace(rxNumericValue, function(n) {
884
+ numbers.push(+n);
885
+ return '{?}';
886
+ });
887
+
888
+ //Add the formatstring as first value.
889
+ numbers.unshift(val);
890
+
891
+ return numbers;
892
+ };
893
+
894
+ /**
895
+ * Fills the key frames with missing left and right hand properties.
896
+ * If key frame 1 has property X and key frame 2 is missing X,
897
+ * but key frame 3 has X again, then we need to assign X to key frame 2 too.
898
+ *
899
+ * @param sk A skrollable.
900
+ */
901
+ var _fillProps = function(sk) {
902
+ //Will collect the properties key frame by key frame
903
+ var propList = {};
904
+ var keyFrameIndex;
905
+
906
+ //Iterate over all key frames from left to right
907
+ for(keyFrameIndex = 0; keyFrameIndex < sk.keyFrames.length; keyFrameIndex++) {
908
+ _fillPropForFrame(sk.keyFrames[keyFrameIndex], propList);
909
+ }
910
+
911
+ //Now do the same from right to fill the last gaps
912
+
913
+ propList = {};
914
+
915
+ //Iterate over all key frames from right to left
916
+ for(keyFrameIndex = sk.keyFrames.length - 1; keyFrameIndex >= 0; keyFrameIndex--) {
917
+ _fillPropForFrame(sk.keyFrames[keyFrameIndex], propList);
918
+ }
919
+ };
920
+
921
+ var _fillPropForFrame = function(frame, propList) {
922
+ var key;
923
+
924
+ //For each key frame iterate over all right hand properties and assign them,
925
+ //but only if the current key frame doesn't have the property by itself
926
+ for(key in propList) {
927
+ //The current frame misses this property, so assign it.
928
+ if(!hasProp.call(frame.props, key)) {
929
+ frame.props[key] = propList[key];
930
+ }
931
+ }
932
+
933
+ //Iterate over all props of the current frame and collect them
934
+ for(key in frame.props) {
935
+ propList[key] = frame.props[key];
936
+ }
937
+ };
938
+
939
+ /**
940
+ * Calculates the new values for two given values array.
941
+ */
942
+ var _calcInterpolation = function(val1, val2, progress) {
943
+ //They both need to have the same length
944
+ if(val1.length !== val2.length) {
945
+ throw 'Can\'t interpolate between "' + val1[0] + '" and "' + val2[0] + '"';
946
+ }
947
+
948
+ //Add the format string as first element.
949
+ var interpolated = [val1[0]];
950
+
951
+ for(var valueIndex = 1; valueIndex < val1.length; valueIndex++) {
952
+ //That's the line where the two numbers are actually interpolated.
953
+ interpolated[valueIndex] = val1[valueIndex] + ((val2[valueIndex] - val1[valueIndex]) * progress);
954
+ }
955
+
956
+ return interpolated;
957
+ };
958
+
959
+ /**
960
+ * Interpolates the numeric values into the format string.
961
+ */
962
+ var _interpolateString = function(val) {
963
+ var valueIndex = 1;
964
+
965
+ rxInterpolateString.lastIndex = 0;
966
+
967
+ return val[0].replace(rxInterpolateString, function() {
968
+ return val[valueIndex++];
969
+ });
970
+ };
971
+
972
+ /**
973
+ * Set the CSS property on the given element. Sets prefixed properties as well.
974
+ */
975
+ skrollr.setStyle = function(el, prop, val) {
976
+ var style = el.style;
977
+
978
+ //Camel case.
979
+ prop = prop.replace(rxCamelCase, rxCamelCaseFn).replace('-', '');
980
+
981
+ //Make sure z-index gets a <integer>.
982
+ //This is the only <integer> case we need to handle.
983
+ if(prop === 'zIndex') {
984
+ //Floor
985
+ style[prop] = '' + (val | 0);
986
+ }
987
+ //#64: "float" can't be set across browsers. Needs to use "cssFloat" for all except IE.
988
+ else if(prop === 'float') {
989
+ style.styleFloat = style.cssFloat = val;
990
+ }
991
+ else {
992
+ //Need try-catch for old IE.
993
+ try {
994
+ //Set prefixed property if there's a prefix.
995
+ if(theCSSPrefix) {
996
+ style[theCSSPrefix + prop.slice(0,1).toUpperCase() + prop.slice(1)] = val;
997
+ }
998
+
999
+ //Set unprefixed.
1000
+ style[prop] = val;
1001
+ } catch(ignore) {}
1002
+ }
1003
+ };
1004
+
1005
+ /**
1006
+ * Cross browser event handling.
1007
+ */
1008
+ skrollr.addEvent = function(element, name, callback) {
1009
+ var intermediate = function(e) {
1010
+ //Normalize IE event stuff.
1011
+ e = e || window.event;
1012
+
1013
+ if(!e.target) {
1014
+ e.target = e.srcElement;
1015
+ }
1016
+
1017
+ if(!e.preventDefault) {
1018
+ e.preventDefault = function() {
1019
+ e.returnValue = false;
1020
+ };
1021
+ }
1022
+
1023
+ return callback.call(this, e);
1024
+ };
1025
+
1026
+ if(window.addEventListener) {
1027
+ element.addEventListener(name, intermediate, false);
1028
+ } else {
1029
+ element.attachEvent('on' + name, intermediate);
1030
+ }
1031
+ };
1032
+
1033
+ var _reflow = function() {
1034
+ //Will be recalculated by _updateDependentKeyFrames.
1035
+ _maxKeyFrame = 0;
1036
+
1037
+ _updateDependentKeyFrames();
1038
+
1039
+ _forceRender = true;
1040
+
1041
+ if(skrollr.iscroll) {
1042
+ window.setTimeout(function () {
1043
+ skrollr.iscroll.refresh();
1044
+ }, 0);
1045
+ }
1046
+ };
1047
+
1048
+ /*
1049
+ * Returns the height of the document.
1050
+ */
1051
+ var _getDocumentHeight = function() {
1052
+ var bodyHeight = Math.max(body.scrollHeight, body.offsetHeight, documentElement.scrollHeight, documentElement.offsetHeight, documentElement.clientHeight);
1053
+
1054
+ return bodyHeight - documentElement.clientHeight;
1055
+ };
1056
+
1057
+ /**
1058
+ * Returns a string of space separated classnames for the current element.
1059
+ * Works with SVG as well.
1060
+ */
1061
+ var _getClass = function(element) {
1062
+ var prop = 'className';
1063
+
1064
+ //SVG support by using className.baseVal instead of just className
1065
+ if(window.SVGElement && element instanceof window.SVGElement) {
1066
+ element = element[prop];
1067
+ prop = 'baseVal';
1068
+ }
1069
+
1070
+ return element[prop];
1071
+ };
1072
+
1073
+ /**
1074
+ * Adds and removes a CSS classes.
1075
+ * Works with SVG as well.
1076
+ * add and remove are either arrays of strings,
1077
+ * or if remove is ommited add is a string and overwrites all classes.
1078
+ */
1079
+ var _updateClass = function(element, add, remove) {
1080
+ var prop = 'className';
1081
+
1082
+ //SVG support by using className.baseVal instead of just className
1083
+ if(window.SVGElement && element instanceof window.SVGElement) {
1084
+ element = element[prop];
1085
+ prop = 'baseVal';
1086
+ }
1087
+
1088
+ //When remove is ommited, we want to overwrite/set the classes.
1089
+ if(remove === undefined) {
1090
+ element[prop] = add;
1091
+ return;
1092
+ }
1093
+
1094
+ //Cache current classes. We will work on a string before passing back to DOM.
1095
+ var val = element[prop];
1096
+
1097
+ //All classes to be added.
1098
+ for(var classAddIndex = 0; classAddIndex < add.length; classAddIndex++) {
1099
+ //Only add if el not already has class.
1100
+ if(_untrim(val).indexOf(_untrim(add[classAddIndex])) === -1) {
1101
+ val += ' ' + add[classAddIndex];
1102
+ }
1103
+ }
1104
+
1105
+ //All classes to be removed.
1106
+ for(var classRemoveIndex = 0; classRemoveIndex < remove.length; classRemoveIndex++) {
1107
+ val = _untrim(val).replace(_untrim(remove[classRemoveIndex]), ' ');
1108
+ }
1109
+
1110
+ element[prop] = _trim(val);
1111
+ };
1112
+
1113
+ var _trim = function(a) {
1114
+ return a.replace(rxTrim, '$1');
1115
+ };
1116
+
1117
+ /**
1118
+ * Adds a space before and after the string.
1119
+ */
1120
+ var _untrim = function(a) {
1121
+ return ' ' + a + ' ';
1122
+ };
1123
+
1124
+ var _now = Date.now || function() {
1125
+ return +new Date();
1126
+ };
1127
+
1128
+ var _keyFrameComparator = function(a, b) {
1129
+ return a.frame - b.frame;
1130
+ };
1131
+
1132
+ /*
1133
+ * Private variables.
1134
+ */
1135
+
1136
+ //Singleton
1137
+ var _instance;
1138
+
1139
+ /*
1140
+ A list of all elements which should be animated associated with their the metadata.
1141
+ Exmaple skrollable with two key frames animating from 100px width to 20px:
1142
+
1143
+ skrollable = {
1144
+ element: <the DOM element>,
1145
+ styleAttr: <style attribute of the element before skrollr>,
1146
+ classAttr: <class attribute of the element before skrollr>,
1147
+ keyFrames: [
1148
+ {
1149
+ frame: 100,
1150
+ props: {
1151
+ width: {
1152
+ value: ['{?}px', 100],
1153
+ easing: <reference to easing function>
1154
+ }
1155
+ },
1156
+ mode: "absolute"
1157
+ },
1158
+ {
1159
+ frame: 200,
1160
+ props: {
1161
+ width: {
1162
+ value: ['{?}px', 20],
1163
+ easing: <reference to easing function>
1164
+ }
1165
+ },
1166
+ mode: "absolute"
1167
+ }
1168
+ ]
1169
+ };
1170
+ */
1171
+ var _skrollables;
1172
+
1173
+ var _listeners;
1174
+ var _forceHeight;
1175
+ var _maxKeyFrame = 0;
1176
+
1177
+ var _scale = 1;
1178
+ var _constants;
1179
+
1180
+ //Current direction (up/down).
1181
+ var _direction = 'down';
1182
+
1183
+ //The last top offset value. Needed to determine direction.
1184
+ var _lastTop = -1;
1185
+
1186
+ //The last time we called the render method (doesn't mean we rendered!).
1187
+ var _lastRenderCall = _now();
1188
+
1189
+ //Will contain data about a running scrollbar animation, if any.
1190
+ var _scrollAnimation;
1191
+
1192
+ var _smoothScrollingEnabled;
1193
+
1194
+ //Will contain settins for smooth scrolling if enabled.
1195
+ var _smoothScrolling;
1196
+
1197
+ //Can be set by any operation/event to force rendering even if the scrollbar didn't move.
1198
+ var _forceRender;
1199
+
1200
+ //Each skrollable gets an unique ID incremented for each skrollable.
1201
+ //The ID is the index in the _skrollables array.
1202
+ var _skrollableIdCounter = 0;
1203
+ }(window, document));