1 | /* |
---|
2 | Animator.js 1.1.9 |
---|
3 | |
---|
4 | This library is released under the BSD license: |
---|
5 | |
---|
6 | Copyright (c) 2006, Bernard Sumption. All rights reserved. |
---|
7 | |
---|
8 | Redistribution and use in source and binary forms, with or without |
---|
9 | modification, are permitted provided that the following conditions are met: |
---|
10 | |
---|
11 | Redistributions of source code must retain the above copyright notice, this |
---|
12 | list of conditions and the following disclaimer. Redistributions in binary |
---|
13 | form must reproduce the above copyright notice, this list of conditions and |
---|
14 | the following disclaimer in the documentation and/or other materials |
---|
15 | provided with the distribution. Neither the name BernieCode nor |
---|
16 | the names of its contributors may be used to endorse or promote products |
---|
17 | derived from this software without specific prior written permission. |
---|
18 | |
---|
19 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" |
---|
20 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
---|
21 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE |
---|
22 | ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR |
---|
23 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL |
---|
24 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR |
---|
25 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER |
---|
26 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT |
---|
27 | LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY |
---|
28 | OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH |
---|
29 | DAMAGE. |
---|
30 | |
---|
31 | */ |
---|
32 | |
---|
33 | |
---|
34 | // Applies a sequence of numbers between 0 and 1 to a number of subjects |
---|
35 | // construct - see setOptions for parameters |
---|
36 | function Animator(options) { |
---|
37 | this.setOptions(options); |
---|
38 | var _this = this; |
---|
39 | this.timerDelegate = function(){_this.onTimerEvent()}; |
---|
40 | this.subjects = []; |
---|
41 | this.target = 0; |
---|
42 | this.state = 0; |
---|
43 | this.lastTime = null; |
---|
44 | }; |
---|
45 | Animator.prototype = { |
---|
46 | // apply defaults |
---|
47 | setOptions: function(options) { |
---|
48 | this.options = Animator.applyDefaults({ |
---|
49 | interval: 20, // time between animation frames |
---|
50 | duration: 400, // length of animation |
---|
51 | onComplete: function(){}, |
---|
52 | onStep: function(){}, |
---|
53 | transition: Animator.tx.easeInOut |
---|
54 | }, options); |
---|
55 | }, |
---|
56 | // animate from the current state to provided value |
---|
57 | seekTo: function(to) { |
---|
58 | this.seekFromTo(this.state, to); |
---|
59 | }, |
---|
60 | // animate from the current state to provided value |
---|
61 | seekFromTo: function(from, to) { |
---|
62 | this.target = Math.max(0, Math.min(1, to)); |
---|
63 | this.state = Math.max(0, Math.min(1, from)); |
---|
64 | this.lastTime = new Date().getTime(); |
---|
65 | if (!this.intervalId) { |
---|
66 | this.intervalId = window.setInterval(this.timerDelegate, this.options.interval); |
---|
67 | } |
---|
68 | }, |
---|
69 | // animate from the current state to provided value |
---|
70 | jumpTo: function(to) { |
---|
71 | this.target = this.state = Math.max(0, Math.min(1, to)); |
---|
72 | this.propagate(); |
---|
73 | }, |
---|
74 | // seek to the opposite of the current target |
---|
75 | toggle: function() { |
---|
76 | this.seekTo(1 - this.target); |
---|
77 | }, |
---|
78 | // add a function or an object with a method setState(state) that will be called with a number |
---|
79 | // between 0 and 1 on each frame of the animation |
---|
80 | addSubject: function(subject) { |
---|
81 | this.subjects[this.subjects.length] = subject; |
---|
82 | return this; |
---|
83 | }, |
---|
84 | // remove all subjects |
---|
85 | clearSubjects: function() { |
---|
86 | this.subjects = []; |
---|
87 | }, |
---|
88 | // forward the current state to the animation subjects |
---|
89 | propagate: function() { |
---|
90 | var value = this.options.transition(this.state); |
---|
91 | for (var i=0; i<this.subjects.length; i++) { |
---|
92 | if (this.subjects[i].setState) { |
---|
93 | this.subjects[i].setState(value); |
---|
94 | } else { |
---|
95 | this.subjects[i](value); |
---|
96 | } |
---|
97 | } |
---|
98 | }, |
---|
99 | // called once per frame to update the current state |
---|
100 | onTimerEvent: function() { |
---|
101 | var now = new Date().getTime(); |
---|
102 | var timePassed = now - this.lastTime; |
---|
103 | this.lastTime = now; |
---|
104 | var movement = (timePassed / this.options.duration) * (this.state < this.target ? 1 : -1); |
---|
105 | if (Math.abs(movement) >= Math.abs(this.state - this.target)) { |
---|
106 | this.state = this.target; |
---|
107 | } else { |
---|
108 | this.state += movement; |
---|
109 | } |
---|
110 | |
---|
111 | try { |
---|
112 | this.propagate(); |
---|
113 | } finally { |
---|
114 | this.options.onStep.call(this); |
---|
115 | if (this.target == this.state) { |
---|
116 | window.clearInterval(this.intervalId); |
---|
117 | this.intervalId = null; |
---|
118 | this.options.onComplete.call(this); |
---|
119 | } |
---|
120 | } |
---|
121 | }, |
---|
122 | // shortcuts |
---|
123 | play: function() {this.seekFromTo(0, 1)}, |
---|
124 | reverse: function() {this.seekFromTo(1, 0)}, |
---|
125 | // return a string describing this Animator, for debugging |
---|
126 | inspect: function() { |
---|
127 | var str = "#<Animator:\n"; |
---|
128 | for (var i=0; i<this.subjects.length; i++) { |
---|
129 | str += this.subjects[i].inspect(); |
---|
130 | } |
---|
131 | str += ">"; |
---|
132 | return str; |
---|
133 | } |
---|
134 | }; |
---|
135 | // merge the properties of two objects |
---|
136 | Animator.applyDefaults = function(defaults, prefs) { |
---|
137 | prefs = prefs || {}; |
---|
138 | var prop, result = {}; |
---|
139 | for (prop in defaults) result[prop] = prefs[prop] !== undefined ? prefs[prop] : defaults[prop]; |
---|
140 | return result; |
---|
141 | }; |
---|
142 | // make an array from any object |
---|
143 | Animator.makeArray = function(o) { |
---|
144 | if (o == null) return []; |
---|
145 | if (!o.length) return [o]; |
---|
146 | var result = []; |
---|
147 | for (var i=0; i<o.length; i++) result[i] = o[i]; |
---|
148 | return result; |
---|
149 | }; |
---|
150 | // convert a dash-delimited-property to a camelCaseProperty (c/o Prototype, thanks Sam!) |
---|
151 | Animator.camelize = function(string) { |
---|
152 | var oStringList = string.split('-'); |
---|
153 | if (oStringList.length == 1) return oStringList[0]; |
---|
154 | |
---|
155 | var camelizedString = string.indexOf('-') == 0 |
---|
156 | ? oStringList[0].charAt(0).toUpperCase() + oStringList[0].substring(1) |
---|
157 | : oStringList[0]; |
---|
158 | |
---|
159 | for (var i = 1, len = oStringList.length; i < len; i++) { |
---|
160 | var s = oStringList[i]; |
---|
161 | camelizedString += s.charAt(0).toUpperCase() + s.substring(1); |
---|
162 | } |
---|
163 | return camelizedString; |
---|
164 | }; |
---|
165 | // syntactic sugar for creating CSSStyleSubjects |
---|
166 | Animator.apply = function(el, style, options) { |
---|
167 | if (style instanceof Array) { |
---|
168 | return new Animator(options).addSubject(new CSSStyleSubject(el, style[0], style[1])); |
---|
169 | } |
---|
170 | return new Animator(options).addSubject(new CSSStyleSubject(el, style)); |
---|
171 | }; |
---|
172 | // make a transition function that gradually accelerates. pass a=1 for smooth |
---|
173 | // gravitational acceleration, higher values for an exaggerated effect |
---|
174 | Animator.makeEaseIn = function(a) { |
---|
175 | return function(state) { |
---|
176 | return Math.pow(state, a*2); |
---|
177 | } |
---|
178 | }; |
---|
179 | // as makeEaseIn but for deceleration |
---|
180 | Animator.makeEaseOut = function(a) { |
---|
181 | return function(state) { |
---|
182 | return 1 - Math.pow(1 - state, a*2); |
---|
183 | } |
---|
184 | }; |
---|
185 | // make a transition function that, like an object with momentum being attracted to a point, |
---|
186 | // goes past the target then returns |
---|
187 | Animator.makeElastic = function(bounces) { |
---|
188 | return function(state) { |
---|
189 | state = Animator.tx.easeInOut(state); |
---|
190 | return ((1-Math.cos(state * Math.PI * bounces)) * (1 - state)) + state; |
---|
191 | } |
---|
192 | }; |
---|
193 | // make an Attack Decay Sustain Release envelope that starts and finishes on the same level |
---|
194 | // |
---|
195 | Animator.makeADSR = function(attackEnd, decayEnd, sustainEnd, sustainLevel) { |
---|
196 | if (sustainLevel == null) sustainLevel = 0.5; |
---|
197 | return function(state) { |
---|
198 | if (state < attackEnd) { |
---|
199 | return state / attackEnd; |
---|
200 | } |
---|
201 | if (state < decayEnd) { |
---|
202 | return 1 - ((state - attackEnd) / (decayEnd - attackEnd) * (1 - sustainLevel)); |
---|
203 | } |
---|
204 | if (state < sustainEnd) { |
---|
205 | return sustainLevel; |
---|
206 | } |
---|
207 | return sustainLevel * (1 - ((state - sustainEnd) / (1 - sustainEnd))); |
---|
208 | } |
---|
209 | }; |
---|
210 | // make a transition function that, like a ball falling to floor, reaches the target and/ |
---|
211 | // bounces back again |
---|
212 | Animator.makeBounce = function(bounces) { |
---|
213 | var fn = Animator.makeElastic(bounces); |
---|
214 | return function(state) { |
---|
215 | state = fn(state); |
---|
216 | return state <= 1 ? state : 2-state; |
---|
217 | } |
---|
218 | }; |
---|
219 | |
---|
220 | // pre-made transition functions to use with the 'transition' option |
---|
221 | Animator.tx = { |
---|
222 | easeInOut: function(pos){ |
---|
223 | return ((-Math.cos(pos*Math.PI)/2) + 0.5); |
---|
224 | }, |
---|
225 | linear: function(x) { |
---|
226 | return x; |
---|
227 | }, |
---|
228 | easeIn: Animator.makeEaseIn(1.5), |
---|
229 | easeOut: Animator.makeEaseOut(1.5), |
---|
230 | strongEaseIn: Animator.makeEaseIn(2.5), |
---|
231 | strongEaseOut: Animator.makeEaseOut(2.5), |
---|
232 | elastic: Animator.makeElastic(1), |
---|
233 | veryElastic: Animator.makeElastic(3), |
---|
234 | bouncy: Animator.makeBounce(1), |
---|
235 | veryBouncy: Animator.makeBounce(3) |
---|
236 | }; |
---|
237 | |
---|
238 | // animates a pixel-based style property between two integer values |
---|
239 | function NumericalStyleSubject(els, property, from, to, units) { |
---|
240 | this.els = Animator.makeArray(els); |
---|
241 | if (property == 'opacity' && window.ActiveXObject) { |
---|
242 | this.property = 'filter'; |
---|
243 | } else { |
---|
244 | this.property = Animator.camelize(property); |
---|
245 | } |
---|
246 | this.from = parseFloat(from); |
---|
247 | this.to = parseFloat(to); |
---|
248 | this.units = units != null ? units : 'px'; |
---|
249 | } |
---|
250 | NumericalStyleSubject.prototype = { |
---|
251 | setState: function(state) { |
---|
252 | var style = this.getStyle(state); |
---|
253 | var visibility = (this.property == 'opacity' && state == 0) ? 'hidden' : ''; |
---|
254 | var j=0; |
---|
255 | for (var i=0; i<this.els.length; i++) { |
---|
256 | try { |
---|
257 | this.els[i].style[this.property] = style; |
---|
258 | } catch (e) { |
---|
259 | // ignore fontWeight - intermediate numerical values cause exeptions in firefox |
---|
260 | if (this.property != 'fontWeight') throw e; |
---|
261 | } |
---|
262 | if (j++ > 20) return; |
---|
263 | } |
---|
264 | }, |
---|
265 | getStyle: function(state) { |
---|
266 | state = this.from + ((this.to - this.from) * state); |
---|
267 | if (this.property == 'filter') return "alpha(opacity=" + Math.round(state*100) + ")"; |
---|
268 | if (this.property == 'opacity') return state; |
---|
269 | return Math.round(state) + this.units; |
---|
270 | }, |
---|
271 | inspect: function() { |
---|
272 | return "\t" + this.property + "(" + this.from + this.units + " to " + this.to + this.units + ")\n"; |
---|
273 | } |
---|
274 | }; |
---|
275 | |
---|
276 | // animates a colour based style property between two hex values |
---|
277 | function ColorStyleSubject(els, property, from, to) { |
---|
278 | this.els = Animator.makeArray(els); |
---|
279 | this.property = Animator.camelize(property); |
---|
280 | this.to = this.expandColor(to); |
---|
281 | this.from = this.expandColor(from); |
---|
282 | this.origFrom = from; |
---|
283 | this.origTo = to; |
---|
284 | } |
---|
285 | |
---|
286 | ColorStyleSubject.prototype = { |
---|
287 | // parse "#FFFF00" to [256, 256, 0] |
---|
288 | expandColor: function(color) { |
---|
289 | var hexColor, red, green, blue; |
---|
290 | hexColor = ColorStyleSubject.parseColor(color); |
---|
291 | if (hexColor) { |
---|
292 | red = parseInt(hexColor.slice(1, 3), 16); |
---|
293 | green = parseInt(hexColor.slice(3, 5), 16); |
---|
294 | blue = parseInt(hexColor.slice(5, 7), 16); |
---|
295 | return [red,green,blue] |
---|
296 | } |
---|
297 | if (window.DEBUG) { |
---|
298 | alert("Invalid colour: '" + color + "'"); |
---|
299 | } |
---|
300 | }, |
---|
301 | getValueForState: function(color, state) { |
---|
302 | return Math.round(this.from[color] + ((this.to[color] - this.from[color]) * state)); |
---|
303 | }, |
---|
304 | setState: function(state) { |
---|
305 | var color = '#' |
---|
306 | + ColorStyleSubject.toColorPart(this.getValueForState(0, state)) |
---|
307 | + ColorStyleSubject.toColorPart(this.getValueForState(1, state)) |
---|
308 | + ColorStyleSubject.toColorPart(this.getValueForState(2, state)); |
---|
309 | for (var i=0; i<this.els.length; i++) { |
---|
310 | this.els[i].style[this.property] = color; |
---|
311 | } |
---|
312 | }, |
---|
313 | inspect: function() { |
---|
314 | return "\t" + this.property + "(" + this.origFrom + " to " + this.origTo + ")\n"; |
---|
315 | } |
---|
316 | }; |
---|
317 | |
---|
318 | // return a properly formatted 6-digit hex colour spec, or false |
---|
319 | ColorStyleSubject.parseColor = function(string) { |
---|
320 | var color = '#', match; |
---|
321 | if(match = ColorStyleSubject.parseColor.rgbRe.exec(string)) { |
---|
322 | var part; |
---|
323 | for (var i=1; i<=3; i++) { |
---|
324 | part = Math.max(0, Math.min(255, parseInt(match[i]))); |
---|
325 | color += ColorStyleSubject.toColorPart(part); |
---|
326 | } |
---|
327 | return color; |
---|
328 | } |
---|
329 | if (match = ColorStyleSubject.parseColor.hexRe.exec(string)) { |
---|
330 | if(match[1].length == 3) { |
---|
331 | for (var i=0; i<3; i++) { |
---|
332 | color += match[1].charAt(i) + match[1].charAt(i); |
---|
333 | } |
---|
334 | return color; |
---|
335 | } |
---|
336 | return '#' + match[1]; |
---|
337 | } |
---|
338 | return false; |
---|
339 | }; |
---|
340 | // convert a number to a 2 digit hex string |
---|
341 | ColorStyleSubject.toColorPart = function(number) { |
---|
342 | if (number > 255) number = 255; |
---|
343 | var digits = number.toString(16); |
---|
344 | if (number < 16) return '0' + digits; |
---|
345 | return digits; |
---|
346 | }; |
---|
347 | ColorStyleSubject.parseColor.rgbRe = /^rgb\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*\)$/i; |
---|
348 | ColorStyleSubject.parseColor.hexRe = /^\#([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$/; |
---|
349 | |
---|
350 | // Animates discrete styles, i.e. ones that do not scale but have discrete values |
---|
351 | // that can't be interpolated |
---|
352 | function DiscreteStyleSubject(els, property, from, to, threshold) { |
---|
353 | this.els = Animator.makeArray(els); |
---|
354 | this.property = Animator.camelize(property); |
---|
355 | this.from = from; |
---|
356 | this.to = to; |
---|
357 | this.threshold = threshold || 0.5; |
---|
358 | } |
---|
359 | |
---|
360 | DiscreteStyleSubject.prototype = { |
---|
361 | setState: function(state) { |
---|
362 | var j=0; |
---|
363 | for (var i=0; i<this.els.length; i++) { |
---|
364 | this.els[i].style[this.property] = state <= this.threshold ? this.from : this.to; |
---|
365 | } |
---|
366 | }, |
---|
367 | inspect: function() { |
---|
368 | return "\t" + this.property + "(" + this.from + " to " + this.to + " @ " + this.threshold + ")\n"; |
---|
369 | } |
---|
370 | }; |
---|
371 | |
---|
372 | // animates between two styles defined using CSS. |
---|
373 | // if style1 and style2 are present, animate between them, if only style1 |
---|
374 | // is present, animate between the element's current style and style1 |
---|
375 | function CSSStyleSubject(els, style1, style2) { |
---|
376 | els = Animator.makeArray(els); |
---|
377 | this.subjects = []; |
---|
378 | if (els.length == 0) return; |
---|
379 | var prop, toStyle, fromStyle; |
---|
380 | if (style2) { |
---|
381 | fromStyle = this.parseStyle(style1, els[0]); |
---|
382 | toStyle = this.parseStyle(style2, els[0]); |
---|
383 | } else { |
---|
384 | toStyle = this.parseStyle(style1, els[0]); |
---|
385 | fromStyle = {}; |
---|
386 | for (prop in toStyle) { |
---|
387 | fromStyle[prop] = CSSStyleSubject.getStyle(els[0], prop); |
---|
388 | } |
---|
389 | } |
---|
390 | // remove unchanging properties |
---|
391 | var prop; |
---|
392 | for (prop in fromStyle) { |
---|
393 | if (fromStyle[prop] == toStyle[prop]) { |
---|
394 | delete fromStyle[prop]; |
---|
395 | delete toStyle[prop]; |
---|
396 | } |
---|
397 | } |
---|
398 | // discover the type (numerical or colour) of each style |
---|
399 | var prop, units, match, type, from, to; |
---|
400 | for (prop in fromStyle) { |
---|
401 | var fromProp = String(fromStyle[prop]); |
---|
402 | var toProp = String(toStyle[prop]); |
---|
403 | if (toStyle[prop] == null) { |
---|
404 | if (window.DEBUG) alert("No to style provided for '" + prop + '"'); |
---|
405 | continue; |
---|
406 | } |
---|
407 | |
---|
408 | if (from = ColorStyleSubject.parseColor(fromProp)) { |
---|
409 | to = ColorStyleSubject.parseColor(toProp); |
---|
410 | type = ColorStyleSubject; |
---|
411 | } else if (fromProp.match(CSSStyleSubject.numericalRe) |
---|
412 | && toProp.match(CSSStyleSubject.numericalRe)) { |
---|
413 | from = parseFloat(fromProp); |
---|
414 | to = parseFloat(toProp); |
---|
415 | type = NumericalStyleSubject; |
---|
416 | match = CSSStyleSubject.numericalRe.exec(fromProp); |
---|
417 | var reResult = CSSStyleSubject.numericalRe.exec(toProp); |
---|
418 | if (match[1] != null) { |
---|
419 | units = match[1]; |
---|
420 | } else if (reResult[1] != null) { |
---|
421 | units = reResult[1]; |
---|
422 | } else { |
---|
423 | units = reResult; |
---|
424 | } |
---|
425 | } else if (fromProp.match(CSSStyleSubject.discreteRe) |
---|
426 | && toProp.match(CSSStyleSubject.discreteRe)) { |
---|
427 | from = fromProp; |
---|
428 | to = toProp; |
---|
429 | type = DiscreteStyleSubject; |
---|
430 | units = 0; // hack - how to get an animator option down to here |
---|
431 | } else { |
---|
432 | if (window.DEBUG) { |
---|
433 | alert("Unrecognised format for value of " |
---|
434 | + prop + ": '" + fromStyle[prop] + "'"); |
---|
435 | } |
---|
436 | continue; |
---|
437 | } |
---|
438 | this.subjects[this.subjects.length] = new type(els, prop, from, to, units); |
---|
439 | } |
---|
440 | } |
---|
441 | |
---|
442 | CSSStyleSubject.prototype = { |
---|
443 | // parses "width: 400px; color: #FFBB2E" to {width: "400px", color: "#FFBB2E"} |
---|
444 | parseStyle: function(style, el) { |
---|
445 | var rtn = {}; |
---|
446 | // if style is a rule set |
---|
447 | if (style.indexOf(":") != -1) { |
---|
448 | var styles = style.split(";"); |
---|
449 | for (var i=0; i<styles.length; i++) { |
---|
450 | var parts = CSSStyleSubject.ruleRe.exec(styles[i]); |
---|
451 | if (parts) { |
---|
452 | rtn[parts[1]] = parts[2]; |
---|
453 | } |
---|
454 | } |
---|
455 | } |
---|
456 | // else assume style is a class name |
---|
457 | else { |
---|
458 | var prop, value, oldClass; |
---|
459 | oldClass = el.className; |
---|
460 | el.className = style; |
---|
461 | for (var i=0; i<CSSStyleSubject.cssProperties.length; i++) { |
---|
462 | prop = CSSStyleSubject.cssProperties[i]; |
---|
463 | value = CSSStyleSubject.getStyle(el, prop); |
---|
464 | if (value != null) { |
---|
465 | rtn[prop] = value; |
---|
466 | } |
---|
467 | } |
---|
468 | el.className = oldClass; |
---|
469 | } |
---|
470 | return rtn; |
---|
471 | |
---|
472 | }, |
---|
473 | setState: function(state) { |
---|
474 | for (var i=0; i<this.subjects.length; i++) { |
---|
475 | this.subjects[i].setState(state); |
---|
476 | } |
---|
477 | }, |
---|
478 | inspect: function() { |
---|
479 | var str = ""; |
---|
480 | for (var i=0; i<this.subjects.length; i++) { |
---|
481 | str += this.subjects[i].inspect(); |
---|
482 | } |
---|
483 | return str; |
---|
484 | } |
---|
485 | }; |
---|
486 | // get the current value of a css property, |
---|
487 | CSSStyleSubject.getStyle = function(el, property){ |
---|
488 | var style; |
---|
489 | if(document.defaultView && document.defaultView.getComputedStyle){ |
---|
490 | style = document.defaultView.getComputedStyle(el, "").getPropertyValue(property); |
---|
491 | if (style) { |
---|
492 | return style; |
---|
493 | } |
---|
494 | } |
---|
495 | property = Animator.camelize(property); |
---|
496 | if(el.currentStyle){ |
---|
497 | style = el.currentStyle[property]; |
---|
498 | } |
---|
499 | return style || el.style[property] |
---|
500 | }; |
---|
501 | |
---|
502 | |
---|
503 | CSSStyleSubject.ruleRe = /^\s*([a-zA-Z\-]+)\s*:\s*(\S(.+\S)?)\s*$/; |
---|
504 | CSSStyleSubject.numericalRe = /^-?\d+(?:\.\d+)?(%|[a-zA-Z]{2})?$/; |
---|
505 | CSSStyleSubject.discreteRe = /^\w+$/; |
---|
506 | |
---|
507 | // required because the style object of elements isn't enumerable in Safari |
---|
508 | /* |
---|
509 | CSSStyleSubject.cssProperties = ['background-color','border','border-color','border-spacing', |
---|
510 | 'border-style','border-top','border-right','border-bottom','border-left','border-top-color', |
---|
511 | 'border-right-color','border-bottom-color','border-left-color','border-top-width','border-right-width', |
---|
512 | 'border-bottom-width','border-left-width','border-width','bottom','color','font-size','font-size-adjust', |
---|
513 | 'font-stretch','font-style','height','left','letter-spacing','line-height','margin','margin-top', |
---|
514 | 'margin-right','margin-bottom','margin-left','marker-offset','max-height','max-width','min-height', |
---|
515 | 'min-width','orphans','outline','outline-color','outline-style','outline-width','overflow','padding', |
---|
516 | 'padding-top','padding-right','padding-bottom','padding-left','quotes','right','size','text-indent', |
---|
517 | 'top','width','word-spacing','z-index','opacity','outline-offset'];*/ |
---|
518 | |
---|
519 | |
---|
520 | CSSStyleSubject.cssProperties = ['azimuth','background','background-attachment','background-color','background-image','background-position','background-repeat','border-collapse','border-color','border-spacing','border-style','border-top','border-top-color','border-right-color','border-bottom-color','border-left-color','border-top-style','border-right-style','border-bottom-style','border-left-style','border-top-width','border-right-width','border-bottom-width','border-left-width','border-width','bottom','clear','clip','color','content','cursor','direction','display','elevation','empty-cells','css-float','font','font-family','font-size','font-size-adjust','font-stretch','font-style','font-variant','font-weight','height','left','letter-spacing','line-height','list-style','list-style-image','list-style-position','list-style-type','margin','margin-top','margin-right','margin-bottom','margin-left','max-height','max-width','min-height','min-width','orphans','outline','outline-color','outline-style','outline-width','overflow','padding','padding-top','padding-right','padding-bottom','padding-left','pause','position','right','size','table-layout','text-align','text-decoration','text-indent','text-shadow','text-transform','top','vertical-align','visibility','white-space','width','word-spacing','z-index','opacity','outline-offset','overflow-x','overflow-y']; |
---|
521 | |
---|
522 | |
---|
523 | // chains several Animator objects together |
---|
524 | function AnimatorChain(animators, options) { |
---|
525 | this.animators = animators; |
---|
526 | this.setOptions(options); |
---|
527 | for (var i=0; i<this.animators.length; i++) { |
---|
528 | this.listenTo(this.animators[i]); |
---|
529 | } |
---|
530 | this.forwards = false; |
---|
531 | this.current = 0; |
---|
532 | } |
---|
533 | |
---|
534 | AnimatorChain.prototype = { |
---|
535 | // apply defaults |
---|
536 | setOptions: function(options) { |
---|
537 | this.options = Animator.applyDefaults({ |
---|
538 | // by default, each call to AnimatorChain.play() calls jumpTo(0) of each animator |
---|
539 | // before playing, which can cause flickering if you have multiple animators all |
---|
540 | // targeting the same element. Set this to false to avoid this. |
---|
541 | resetOnPlay: true |
---|
542 | }, options); |
---|
543 | }, |
---|
544 | // play each animator in turn |
---|
545 | play: function() { |
---|
546 | this.forwards = true; |
---|
547 | this.current = -1; |
---|
548 | if (this.options.resetOnPlay) { |
---|
549 | for (var i=0; i<this.animators.length; i++) { |
---|
550 | this.animators[i].jumpTo(0); |
---|
551 | } |
---|
552 | } |
---|
553 | this.advance(); |
---|
554 | }, |
---|
555 | // play all animators backwards |
---|
556 | reverse: function() { |
---|
557 | this.forwards = false; |
---|
558 | this.current = this.animators.length; |
---|
559 | if (this.options.resetOnPlay) { |
---|
560 | for (var i=0; i<this.animators.length; i++) { |
---|
561 | this.animators[i].jumpTo(1); |
---|
562 | } |
---|
563 | } |
---|
564 | this.advance(); |
---|
565 | }, |
---|
566 | // if we have just play()'d, then call reverse(), and vice versa |
---|
567 | toggle: function() { |
---|
568 | if (this.forwards) { |
---|
569 | this.seekTo(0); |
---|
570 | } else { |
---|
571 | this.seekTo(1); |
---|
572 | } |
---|
573 | }, |
---|
574 | // internal: install an event listener on an animator's onComplete option |
---|
575 | // to trigger the next animator |
---|
576 | listenTo: function(animator) { |
---|
577 | var oldOnComplete = animator.options.onComplete; |
---|
578 | var _this = this; |
---|
579 | animator.options.onComplete = function() { |
---|
580 | if (oldOnComplete) oldOnComplete.call(animator); |
---|
581 | _this.advance(); |
---|
582 | } |
---|
583 | }, |
---|
584 | // play the next animator |
---|
585 | advance: function() { |
---|
586 | if (this.forwards) { |
---|
587 | if (this.animators[this.current + 1] == null) return; |
---|
588 | this.current++; |
---|
589 | this.animators[this.current].play(); |
---|
590 | } else { |
---|
591 | if (this.animators[this.current - 1] == null) return; |
---|
592 | this.current--; |
---|
593 | this.animators[this.current].reverse(); |
---|
594 | } |
---|
595 | }, |
---|
596 | // this function is provided for drop-in compatibility with Animator objects, |
---|
597 | // but only accepts 0 and 1 as target values |
---|
598 | seekTo: function(target) { |
---|
599 | if (target <= 0) { |
---|
600 | this.forwards = false; |
---|
601 | this.animators[this.current].seekTo(0); |
---|
602 | } else { |
---|
603 | this.forwards = true; |
---|
604 | this.animators[this.current].seekTo(1); |
---|
605 | } |
---|
606 | } |
---|
607 | }; |
---|
608 | |
---|
609 | // an Accordion is a class that creates and controls a number of Animators. An array of elements is passed in, |
---|
610 | // and for each element an Animator and a activator button is created. When an Animator's activator button is |
---|
611 | // clicked, the Animator and all before it seek to 0, and all Animators after it seek to 1. This can be used to |
---|
612 | // create the classic Accordion effect, hence the name. |
---|
613 | // see setOptions for arguments |
---|
614 | function Accordion(options) { |
---|
615 | this.setOptions(options); |
---|
616 | var selected = this.options.initialSection, current; |
---|
617 | if (this.options.rememberance) { |
---|
618 | current = document.location.hash.substring(1); |
---|
619 | } |
---|
620 | this.rememberanceTexts = []; |
---|
621 | this.ans = []; |
---|
622 | var _this = this; |
---|
623 | for (var i=0; i<this.options.sections.length; i++) { |
---|
624 | var el = this.options.sections[i]; |
---|
625 | var an = new Animator(this.options.animatorOptions); |
---|
626 | var from = this.options.from + (this.options.shift * i); |
---|
627 | var to = this.options.to + (this.options.shift * i); |
---|
628 | an.addSubject(new NumericalStyleSubject(el, this.options.property, from, to, this.options.units)); |
---|
629 | an.jumpTo(0); |
---|
630 | var activator = this.options.getActivator(el); |
---|
631 | activator.index = i; |
---|
632 | activator.onclick = function(){_this.show(this.index)}; |
---|
633 | this.ans[this.ans.length] = an; |
---|
634 | this.rememberanceTexts[i] = activator.innerHTML.replace(/\s/g, ""); |
---|
635 | if (this.rememberanceTexts[i] === current) { |
---|
636 | selected = i; |
---|
637 | } |
---|
638 | } |
---|
639 | this.show(selected); |
---|
640 | } |
---|
641 | |
---|
642 | Accordion.prototype = { |
---|
643 | // apply defaults |
---|
644 | setOptions: function(options) { |
---|
645 | this.options = Object.extend({ |
---|
646 | // REQUIRED: an array of elements to use as the accordion sections |
---|
647 | sections: null, |
---|
648 | // a function that locates an activator button element given a section element. |
---|
649 | // by default it takes a button id from the section's "activator" attibute |
---|
650 | getActivator: function(el) {return document.getElementById(el.getAttribute("activator"))}, |
---|
651 | // shifts each animator's range, for example with options {from:0,to:100,shift:20} |
---|
652 | // the animators' ranges will be 0-100, 20-120, 40-140 etc. |
---|
653 | shift: 0, |
---|
654 | // the first page to show |
---|
655 | initialSection: 0, |
---|
656 | // if set to true, document.location.hash will be used to preserve the open section across page reloads |
---|
657 | rememberance: true, |
---|
658 | // constructor arguments to the Animator objects |
---|
659 | animatorOptions: {} |
---|
660 | }, options || {}); |
---|
661 | }, |
---|
662 | show: function(section) { |
---|
663 | for (var i=0; i<this.ans.length; i++) { |
---|
664 | this.ans[i].seekTo(i > section ? 1 : 0); |
---|
665 | } |
---|
666 | if (this.options.rememberance) { |
---|
667 | document.location.hash = this.rememberanceTexts[section]; |
---|
668 | } |
---|
669 | } |
---|
670 | }; |
---|