1
+ /*
2
+ * jQuery++ - 1.0.0 (2012-11-20)
3
+ * http://jquerypp.com
4
+ * Copyright (c) 2012 Bitovi
5
+ * Licensed MIT
6
+ */
7
+ define ( [ 'jquery' , 'jquerypp/dom/styles' ] , function ( $ ) {
8
+
9
+ // Overwrites `jQuery.fn.animate` to use CSS 3 animations if possible
10
+ var
11
+ // The global animation counter
12
+ animationNum = 0 ,
13
+ // The stylesheet for our animations
14
+ styleSheet = null ,
15
+ // The animation cache
16
+ cache = [ ] ,
17
+ // Stores the browser properties like transition end event name and prefix
18
+ browser = null ,
19
+ // Store the original $.fn.animate
20
+ oldanimate = $ . fn . animate ,
21
+
22
+ // Return the stylesheet, create it if it doesn't exists
23
+ getStyleSheet = function ( ) {
24
+ if ( ! styleSheet ) {
25
+ var style = document . createElement ( 'style' ) ;
26
+ style . setAttribute ( "type" , "text/css" ) ;
27
+ style . setAttribute ( "media" , "screen" ) ;
28
+
29
+ document . getElementsByTagName ( 'head' ) [ 0 ] . appendChild ( style ) ;
30
+ if ( ! window . createPopup ) {
31
+ style . appendChild ( document . createTextNode ( '' ) ) ;
32
+ }
33
+
34
+ styleSheet = style . sheet ;
35
+ }
36
+
37
+ return styleSheet ;
38
+ } ,
39
+
40
+ //removes an animation rule from a sheet
41
+ removeAnimation = function ( sheet , name ) {
42
+ for ( var j = sheet . cssRules . length - 1 ; j >= 0 ; j -- ) {
43
+ var rule = sheet . cssRules [ j ] ;
44
+ // 7 means the keyframe rule
45
+ if ( rule . type === 7 && rule . name == name ) {
46
+ sheet . deleteRule ( j )
47
+ return ;
48
+ }
49
+ }
50
+ } ,
51
+
52
+ // Returns whether the animation should be passed to the original $.fn.animate.
53
+ passThrough = function ( props , ops ) {
54
+ var nonElement = ! ( this [ 0 ] && this [ 0 ] . nodeType ) ,
55
+ isInline = ! nonElement && $ ( this ) . css ( "display" ) === "inline" && $ ( this ) . css ( "float" ) === "none" ;
56
+
57
+ for ( var name in props ) {
58
+ // jQuery does something with these values
59
+ if ( props [ name ] == 'show' || props [ name ] == 'hide' || props [ name ] == 'toggle'
60
+ // Arrays for individual easing
61
+ || $ . isArray ( props [ name ] )
62
+ // Negative values not handled the same
63
+ || props [ name ] < 0
64
+ // unit-less value
65
+ || name == 'zIndex' || name == 'z-index' || name == 'scrollTop' || name == 'scrollLeft' ) {
66
+ return true ;
67
+ }
68
+ }
69
+
70
+ return props . jquery === true || getBrowser ( ) === null ||
71
+ // Animating empty properties
72
+ $ . isEmptyObject ( props ) ||
73
+ // We can't do custom easing
74
+ ( ops && ops . length == 4 ) || ( ops && typeof ops [ 2 ] == 'string' ) ||
75
+ // Second parameter is an object - we can only handle primitives
76
+ $ . isPlainObject ( ops ) ||
77
+ // Inline and non elements
78
+ isInline || nonElement ;
79
+ } ,
80
+
81
+ // Gets a CSS number (with px added as the default unit if the value is a number)
82
+ cssValue = function ( origName , value ) {
83
+ if ( typeof value === "number" && ! $ . cssNumber [ origName ] ) {
84
+ return value += "px" ;
85
+ }
86
+ return value ;
87
+ } ,
88
+
89
+ // Feature detection borrowed by http://modernizr.com/
90
+ getBrowser = function ( ) {
91
+ if ( ! browser ) {
92
+ var t , el = document . createElement ( 'fakeelement' ) ,
93
+ transitions = {
94
+ 'transition' : {
95
+ transitionEnd : 'transitionEnd' ,
96
+ prefix : ''
97
+ } ,
98
+ // 'OTransition': {
99
+ // transitionEnd : 'oTransitionEnd',
100
+ // prefix : '-o-'
101
+ // },
102
+ // 'MSTransition': {
103
+ // transitionEnd : 'msTransitionEnd',
104
+ // prefix : '-ms-'
105
+ // },
106
+ 'MozTransition' : {
107
+ transitionEnd : 'animationend' ,
108
+ prefix : '-moz-'
109
+ } ,
110
+ 'WebkitTransition' : {
111
+ transitionEnd : 'webkitAnimationEnd' ,
112
+ prefix : '-webkit-'
113
+ }
114
+ }
115
+
116
+ for ( t in transitions ) {
117
+ if ( el . style [ t ] !== undefined ) {
118
+ browser = transitions [ t ] ;
119
+ }
120
+ }
121
+ }
122
+ return browser ;
123
+ } ,
124
+
125
+ // Properties that Firefox can't animate if set to 'auto':
126
+ // https://bugzilla.mozilla.org/show_bug.cgi?id=571344
127
+ // Provides a converter that returns the actual value
128
+ ffProps = {
129
+ top : function ( el ) {
130
+ return el . position ( ) . top ;
131
+ } ,
132
+ left : function ( el ) {
133
+ return el . position ( ) . left ;
134
+ } ,
135
+ width : function ( el ) {
136
+ return el . width ( ) ;
137
+ } ,
138
+ height : function ( el ) {
139
+ return el . height ( ) ;
140
+ } ,
141
+ fontSize : function ( el ) {
142
+ return '1em' ;
143
+ }
144
+ } ,
145
+
146
+ // Add browser specific prefix
147
+ addPrefix = function ( properties ) {
148
+ var result = { } ;
149
+ $ . each ( properties , function ( name , value ) {
150
+ result [ getBrowser ( ) . prefix + name ] = value ;
151
+ } ) ;
152
+ return result ;
153
+ } ,
154
+
155
+ // Returns the animation name for a given style. It either uses a cached
156
+ // version or adds it to the stylesheet, removing the oldest style if the
157
+ // cache has reached a certain size.
158
+ getAnimation = function ( style ) {
159
+ var sheet , name , last ;
160
+
161
+ // Look up the cached style, set it to that name and reset age if found
162
+ // increment the age for any other animation
163
+ $ . each ( cache , function ( i , animation ) {
164
+ if ( style === animation . style ) {
165
+ name = animation . name ;
166
+ animation . age = 0 ;
167
+ } else {
168
+ animation . age += 1 ;
169
+ }
170
+ } ) ;
171
+
172
+ if ( ! name ) { // Add a new style
173
+ sheet = getStyleSheet ( ) ;
174
+ name = "jquerypp_animation_" + ( animationNum ++ ) ;
175
+ // get the last sheet and insert this rule into it
176
+ sheet . insertRule ( "@" + getBrowser ( ) . prefix + "keyframes " + name + ' ' + style , ( sheet . cssRules && sheet . cssRules . length ) || 0 ) ;
177
+ cache . push ( {
178
+ name : name ,
179
+ style : style ,
180
+ age : 0
181
+ } ) ;
182
+
183
+ // Sort the cache by age
184
+ cache . sort ( function ( first , second ) {
185
+ return first . age - second . age ;
186
+ } ) ;
187
+
188
+ // Remove the last (oldest) item from the cache if it has more than 20 items
189
+ if ( cache . length > 20 ) {
190
+ last = cache . pop ( ) ;
191
+ removeAnimation ( sheet , last . name ) ;
192
+ }
193
+ }
194
+
195
+ return name ;
196
+ } ;
197
+
198
+
199
+ $ . fn . animate = function ( props , speed , easing , callback ) {
200
+ //default to normal animations if browser doesn't support them
201
+ if ( passThrough . apply ( this , arguments ) ) {
202
+ return oldanimate . apply ( this , arguments ) ;
203
+ }
204
+
205
+ var optall = jQuery . speed ( speed , easing , callback ) ;
206
+
207
+ // Add everything to the animation queue
208
+ this . queue ( optall . queue , function ( done ) {
209
+ var
210
+ //current CSS values
211
+ current ,
212
+ // The list of properties passed
213
+ properties = [ ] ,
214
+ to = "" ,
215
+ prop , self = $ ( this ) ,
216
+ duration = optall . duration ,
217
+ //the animation keyframe name
218
+ animationName ,
219
+ // The key used to store the animation hook
220
+ dataKey ,
221
+ //the text for the keyframe
222
+ style = "{ from {" ,
223
+ // The animation end event handler.
224
+ // Will be called both on animation end and after calling .stop()
225
+ animationEnd = function ( currentCSS , exec ) {
226
+ self . css ( currentCSS ) ;
227
+
228
+ self . css ( addPrefix ( {
229
+ "animation-duration" : "" ,
230
+ "animation-name" : "" ,
231
+ "animation-fill-mode" : "" ,
232
+ "animation-play-state" : ""
233
+ } ) ) ;
234
+
235
+ // Call the original callback
236
+ if ( $ . isFunction ( optall . old ) && exec ) {
237
+ // Call success, pass the DOM element as the this reference
238
+ optall . old . call ( self [ 0 ] , true )
239
+ }
240
+
241
+ $ . removeData ( self , dataKey , true ) ;
242
+ } ,
243
+ finishAnimation = function ( ) {
244
+ // Call animationEnd using the passed properties
245
+ animationEnd ( props , true ) ;
246
+ done ( ) ;
247
+ } ;
248
+
249
+ for ( prop in props ) {
250
+ properties . push ( prop ) ;
251
+ }
252
+
253
+ if ( getBrowser ( ) . prefix === '-moz-' ) {
254
+ // Normalize 'auto' properties in FF
255
+ $ . each ( properties , function ( i , prop ) {
256
+ var converter = ffProps [ $ . camelCase ( prop ) ] ;
257
+ if ( converter && self . css ( prop ) == 'auto' ) {
258
+ self . css ( prop , converter ( self ) ) ;
259
+ }
260
+ } ) ;
261
+ }
262
+
263
+ // Use $.styles
264
+ current = self . styles . apply ( self , properties ) ;
265
+ $ . each ( properties , function ( i , cur ) {
266
+ // Convert a camelcased property name
267
+ var name = cur . replace ( / ( [ A - Z ] | ^ m s ) / g, "-$1" ) . toLowerCase ( ) ;
268
+ style += name + " : " + cssValue ( cur , current [ cur ] ) + "; " ;
269
+ to += name + " : " + cssValue ( cur , props [ cur ] ) + "; " ;
270
+ } ) ;
271
+
272
+ style += "} to {" + to + " }}" ;
273
+
274
+ animationName = getAnimation ( style ) ;
275
+ dataKey = animationName + '.run' ;
276
+
277
+ // Add a hook which will be called when the animation stops
278
+ $ . _data ( this , dataKey , {
279
+ stop : function ( gotoEnd ) {
280
+ // Pause the animation
281
+ self . css ( addPrefix ( {
282
+ 'animation-play-state' : 'paused'
283
+ } ) ) ;
284
+ // Unbind the animation end handler
285
+ self . off ( getBrowser ( ) . transitionEnd , finishAnimation ) ;
286
+ if ( ! gotoEnd ) {
287
+ // We were told not to finish the animation
288
+ // Call animationEnd but set the CSS to the current computed style
289
+ animationEnd ( self . styles . apply ( self , properties ) , false ) ;
290
+ } else {
291
+ // Finish animaion
292
+ animationEnd ( props , true ) ;
293
+ }
294
+ }
295
+ } ) ;
296
+
297
+ // set this element to point to that animation
298
+ self . css ( addPrefix ( {
299
+ "animation-duration" : duration + "ms" ,
300
+ "animation-name" : animationName ,
301
+ "animation-fill-mode" : "forwards"
302
+ } ) ) ;
303
+
304
+ // Attach the transition end event handler to run only once
305
+ self . one ( getBrowser ( ) . transitionEnd , finishAnimation ) ;
306
+
307
+ } ) ;
308
+
309
+ return this ;
310
+ } ;
311
+
312
+ return $ ;
313
+ } ) ;
0 commit comments