-
Notifications
You must be signed in to change notification settings - Fork 21
/
jquery.runloop.1.0.3.js
205 lines (161 loc) · 6.74 KB
/
jquery.runloop.1.0.3.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
/*!
* jQuery Runloop Plugin -- version 1.0.3
* Copyright 2011, Faruk Ates
* Dual licensed under the MIT or BSD licenses
*
* Based on the concept by Ben Nadel:
* http://j.mp/ben-nadel-concept
*
* Documentation:
* https://github.com/KuraFire/runloop/blob/master/documentation.md
*/
(function( $ ){
$.runloop = function(settings) {
// Internal config, can be partially extended
var config = {
// roundType can be 'floor' or 'round'; used in the Math function for rounding each step to its closest 5% or 10% interval.
'roundType': 'floor',
// base 5 is used (and recommended to be left as-is) for loops from 500ms and up; < 500ms will always switch to base 10!
'base': 5
};
if (settings) $.extend(config, settings);
// Version!
var version = "1.0.3",
// Create the dummy object in nodespace that we're running the animation on
runloop = document.createElement('div'),
// Stores the callbacks for each runloop
map = {},
// Stores each mapped keyframe point once it has been executed, to prevent double executions
execLog = {},
// Internals for keeping track of things
currentDuration = false,
remainingDuration = false,
currentStep = 0,
// Our runloop return object
r,
// Private shortcut
makeAt = function(at) {
var val = parseInt(at.toString().match(/[0-9]+/g), 0);
return isNaN(val) ? false : "at" + val;
};
// Public methods:
return r = {
isPlaying: false,
// Add a keyframe at specified percentage point with code to execute
addKey: function(atPercentage, func) {
var at = makeAt(atPercentage);
if (at) {
var oldAt = map[at];
if (typeof map[at] != 'function') {
map[at] = func;
} else {
map[at] = function() {
if (oldAt) {
oldAt();
}
func();
};
}
}
},
// Remove a keyframe at specified percentage point
removeKey: function(atPercentage) {
var at = makeAt(atPercentage);
if (at) {
delete map[at];
}
},
// Add a map of multiple keyframe-function associations
addMap: function(newMap) {
// Run through each newMap property
for (var key in newMap){
// Validate newMap item
at = makeAt(key);
// If valid on both counts, add to Runloop map
if (at && typeof newMap[key] == "function") {
this.addKey(at, newMap[key]);
}
}
},
// This returns the current map for inspection
getMap: function() {
return map;
},
// Resets the runloop to state 0
reset: function() {
// Return internal variables to initial state
currentDuration = false;
remainingDuration = false;
currentStep = 0;
execLog = {};
// Explicitly set runloop back to 0
$(runloop).css({'top':0});
},
// Pauses the runloop at the current interval.
//
// WARNING !!
// This will not pause any animations triggered by keyframe functions!
//
pause: function() {
r.isPlaying = false;
// Get current time interval and store the remaining time
remainingDuration = currentDuration - Math.round(currentDuration * (Math.floor(currentStep) / 100));
$(runloop).stop();
},
// Starts playing the runloop; if paused, continues from where it was
play: function(duration, callback) {
// If we're already playing, return/ignore
if ( r.isPlaying ) {
return;
}
r.isPlaying = true;
// Default value
duration = parseInt(duration, 0) || 500;
// If our entire runloop is less than 500ms, simplify map steps to 10% intervals
if (duration < 500) {
config.base = 10;
}
// We're in an ongoing runloop; shorten duration to remaining time
if (remainingDuration) {
duration = remainingDuration;
$(runloop).css('top', Math.floor(currentStep)); // TODO: is this needed?
}
// It is important to note at this point that it is entirely possible to still have
// a config.base of 5, but a duration that is less than 500ms. This unusual scenario is,
// however, legitimate in case your original animation had x5% keyframes and was paused
// so close to the end that less than 500 ms remained on the overall duration time.
// That said, it can still cause some intervals to get skipped, so it's not recommended
// to get into this kind of situation. :)
// The optional callback on play() is just a keyframe 100% function
if (typeof callback == "function") {
this.addKey('100%', callback);
}
// Store duration in case it gets paused
currentDuration = duration;
$(runloop).animate({'top':'100'}, {
duration: duration,
step: function( step ) {
currentStep = step;
// Default is floor
if (config.roundType == "round") {
step = Math.round( step / config.base ) * config.base;
} else {
step = Math.floor( step / config.base ) * config.base;
}
if (!(step in execLog) && map['at' + step]) {
// Log this keyframe location, to prevent double-execution
execLog[step] = true;
// Execute the stored function for this keyframe
map['at' + step]();
}
},
easing: 'linear',
complete: function() {
r.isPlaying = false;
r.reset();
}
});
}
};
};
})( jQuery );