├── test ├── transition_child.html ├── transition.html └── index.html ├── README.md └── jquery.transitions.js /test/transition_child.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 44 | 45 | 46 |

Child node transition test

[click]

47 | 48 |
49 | screen 50 |
51 | 52 | 53 | 54 | 75 | 76 | -------------------------------------------------------------------------------- /test/transition.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 59 | 60 | 61 | 62 |

Same node transition test

[click]

63 | 64 |
65 | Success 66 |
67 | 68 | 69 | 70 | 135 | 136 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

Now that we have class based transitions in the browser, we can trigger simple animations by adding and removing classes instead of relying on jQuery's .animate(). 2 | There are some advantages to class based animation, not least of which is the fact that it progressively enhances normal classes, but we do lose some functionality that .animate() provides - notably callbacks on animation end, and the ability to animate to 'hide', where display: none; is applied at the end of the animation. 3 | These two methods address these problems.

4 | 5 |

New in 1.8 - transitions to and from 'auto'

6 | 10 | 11 |

New in 1.6 - IE fallback!

12 |

Automatic fallback to jQuery's .animate() in IE6, IE7, IE8 and IE9 allowing you to write transitions in CSS and have them display in these browsers, too. Support is basic for the moment, but works if you stick to a few rules.

13 | 19 | 20 |

Methods

21 |
.addTransitionClass( className, options )
22 | .removeTransitionClass( className, options )
23 | 24 |

Adds or removes the class className to the node. In addition, the class 'transition' is added for the duration of the transition, allowing you to define styles before, during and after a transition. 25 | Where support for CSS transitions is not detected, .addTransitionClass() and .removeTransitionClass behave as .addClass() and .removeClass() respectively. 26 | The class 'transition' is not added, and the callback is called immediately after the className is applied.

27 | 28 |

Options

29 |
30 |
callback
function() Called at the end of the CSS transition, or if CSS transition support is not detected, directly after className has been applied. Where fallback is defined, callback is passed to fallback as the second argument.
31 |
fallback
function(class, callback) Overrides the default fallback, which provides automatic animation for transitions on this element in IE6, IE7, IE8 and IE9. The fallback is only called when native CSS transition support is not detected, typically allowing you to define jQuery animations to replace the missing CSS transitions.
32 |
33 | 34 |

An example

35 |

Meet Jim.

36 |
.jim {
37 |   display: none;
38 |   opacity: 0;
39 |   /* For IE */
40 |   filter: alpha(opacity=0);
41 |   
42 |   -webkit-transition: opacity 0.06s ease-in;
43 |      -moz-transition: opacity 0.06s ease-in;
44 |           transition: opacity 0.06s ease-in;
45 | }
46 | 
47 | .jim.active {
48 |   display: block;
49 |   opacity: 1;
50 |   /* For IE */
51 |   filter: alpha(opacity=100);
52 | }
53 | 
54 | .jim.transition {
55 |   display: block;
56 | }
57 |

Jim is hidden until:

58 |
jQuery('.jim').addTransitionClass('active')
59 |

...at which point he becomes a block and fades in to opacity: 1. On:

60 |
jQuery('.jim').removeTransitionClass('active')
61 |

...he fades out again, and then is removed from display.

62 |

Note that if you try doing this simply by adding and removing the class active, you get some surprising results. When you add active, Jim appears at full opacity, without any transition. The browser does not judge a transition applicable because it has just rendered Jim for the first time, with display: block; opacity: 1. Jim disappears just as quickly when you remove the class active, because he suddenly no longer has display: block. The transition class, along with .addTransitionClass() and .removeTransitionClass(), solve these problems.

63 | 64 |

More reading

65 |

Original blog post: http://stephband.info/using-jquery-to-manage-css-transitions

66 | 67 |

Help

68 |

Help improve me. Fork and help out!

-------------------------------------------------------------------------------- /test/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | template - jquery.transitions fallback test 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 182 | 183 | 184 | 185 |

Transition

186 |
187 |
188 |
189 | 190 |

Transition to auto

191 |
192 |

Click me.

193 |
194 |

The quick brown fox jumps over the lazy dog.

195 |

The quick brown fox jumps over the lazy dog.

196 |

The quick brown fox jumps over the lazy dog.

197 |
198 |
199 | 200 |

Transition from auto

201 |
202 |

Click me.

203 |
204 |

The quick brown fox jumps over the lazy dog.

205 |

The quick brown fox jumps over the lazy dog.

206 |

The quick brown fox jumps over the lazy dog.

207 |
208 |
209 | 210 |

Show/hide transition

211 |
212 |

Click me.

213 |
214 |
215 | 216 |

Child transition

217 |
218 |

Click me.

219 |
220 |
221 |
222 |
223 |
224 | 225 |

Child show/hide transition

226 |
227 |

Click me.

228 |
229 |
230 |
231 |
232 |
233 | 234 |

No transition

235 | 236 |
237 |
238 |
239 | 240 | 241 | 242 | 243 | 273 | 274 | -------------------------------------------------------------------------------- /jquery.transitions.js: -------------------------------------------------------------------------------- 1 | // jquery.transitions.js 2 | // 3 | // 1.8 4 | // 5 | // Feature detects CSS transitions and provides a means to manage 6 | // transitions that start or end with un-transitionable properties 7 | // using CSS classes. Adds two methods to jQuery: 8 | // 9 | // .addTransitionClass( className, callback ) 10 | // .removeTransitionClass( className, callback ) 11 | // 12 | // These fall back to jQuery's standard .addClass() and .removeClass() 13 | // when the browser does not support CSS transitions, so they are safe 14 | // to use without forking your code. 15 | 16 | (function(jQuery, undefined){ 17 | var debug = (window.console && console.log), 18 | docElem = jQuery(document), 19 | testElem = jQuery('
').css({ 20 | // position: 'absolute' makes IE8 jump into Compatibility 21 | // Mode. Use position: 'relative'. 22 | position: 'relative', 23 | top: -200, 24 | left: -9999, 25 | width: 100, 26 | height: 100, 27 | WebkitTransition: 'top 0.01s linear', 28 | MozTransition: 'top 0.01s linear', 29 | OTransition: 'top 0.01s linear', 30 | transition: 'top 0.01s linear' 31 | }), 32 | transitionClass = 'transition', 33 | addOptions = { fallback: makeFallback(true) }, 34 | removeOptions = { fallback: makeFallback(false) }, 35 | autoProperties = { 36 | 'height': true, 37 | 'width': true, 38 | 'margin-left': true, 39 | 'margin-right': true 40 | }, 41 | keywordProperties = { 42 | 'display': true, 43 | 'overflow': true 44 | }, 45 | transitionStr, 46 | transitionPropertyStr, 47 | cssTransitionNone, 48 | cssTransitionEmpty, 49 | timer; 50 | 51 | function makeFallback(add) { 52 | var doClass = add ? jQuery.fn.addClass : jQuery.fn.removeClass, 53 | undoClass = add ? jQuery.fn.removeClass : jQuery.fn.addClass; 54 | 55 | return function(className, callback) { 56 | var elem = this, 57 | transition, css, key, options, style; 58 | 59 | if (elem.hasClass(className) === add) { 60 | // No need to continue. Element already has got (or has not 61 | // got) this class. 62 | return; 63 | } 64 | 65 | doClass.call(elem, className); 66 | transition = this.css('transition') || (window.getComputedStyle && getComputedStyle(this[0], null).transition) ; 67 | 68 | if (transition) { 69 | // IE, even though it doesn't support CSS transitions, at least 70 | // sees the rules, so we can interpret them and animate accordingly. 71 | 72 | css = {}; 73 | style = elem.attr('style'); 74 | options = { 75 | queue: true, 76 | specialEasing: {}, 77 | complete: function() { 78 | var key; 79 | 80 | // Remove the transition class and reset the style attribute 81 | // to it's pre-animated state. In an ideal world, the elem will 82 | // remain the same because we have just animated to it. 83 | 84 | elem.removeClass(transitionClass); 85 | 86 | if (style) { elem.attr('style', style); } 87 | else { elem.removeAttr('style'); } 88 | 89 | callback && callback.apply(this); 90 | } 91 | }; 92 | 93 | // transitionClass must be added before anything is measured, else 94 | // IE8 goes marching merrily off into compatibility mode. 95 | elem.addClass(transitionClass); 96 | 97 | // Regex should be looser, to allow incompolete transition definitions... 98 | transition.replace(/([a-z\-]+)\s+([^\s]+)\s+([a-z\-]+)/g, function($match, $key, $duration, $easing) { 99 | css[$key] = elem.css($key); 100 | options.duration = parseFloat($duration) * 1000; // Convert seconds to milliseconds 101 | options.specialEasing[$key] = $easing; 102 | }); 103 | 104 | undoClass.call(elem, className); 105 | 106 | // Remove any style definitions that don't change, hopefully reducing 107 | // animation processing, and protecting predefined inline styles from 108 | // being removed. 109 | for (key in css) { 110 | if (css[key] === elem.css(key)) { 111 | delete css[key]; 112 | } 113 | } 114 | 115 | elem.animate(css, options); 116 | doClass.call(elem, className); 117 | } 118 | else { 119 | if (debug) { console.log('[jquery.transitions] Transition definition not readable'); } 120 | callback && callback.apply(this); 121 | } 122 | }; 123 | } 124 | 125 | function end(e){ 126 | var elem = e.data.obj, 127 | style = e.data.style, 128 | callback = e.data.callback, 129 | properties = e.data.properties, 130 | property = e.originalEvent.propertyName; 131 | 132 | if (properties) { 133 | properties.splice(properties.indexOf(property), 1); 134 | 135 | // If properties are still animating, do nothing. 136 | if (properties.length) { return; } 137 | } 138 | 139 | elem.unbind(jQuery.support.cssTransitionEnd, end); 140 | 141 | // Reset the style attribute to how it was before 142 | if (style) { elem.attr('style', style); } 143 | else { elem.removeAttr('style'); } 144 | 145 | // Stop transitions and repaint 146 | elem.css(cssTransitionNone).width(); 147 | 148 | if (style) { elem.attr('style', style); } 149 | else { elem.removeAttr('style'); } 150 | 151 | elem.data('preTransitionStyle', false); 152 | elem.removeClass(transitionClass); 153 | 154 | callback && callback.call(e.data.obj); 155 | } 156 | 157 | 158 | // jQuery plugins 159 | 160 | function applyClass(elem, doMethod, undoMethod, classNames, options) { 161 | var autoValues = {}, 162 | cssStart = {}, 163 | cssEnd = {}, 164 | style, flag, properties, property, l; 165 | 166 | elem 167 | .addClass(transitionClass) 168 | .width(); 169 | 170 | // Measure the values of auto properties. We must do this before 171 | // adding the class and testing for transition (unfortunately), 172 | // because if they are transition properties, auto values collapse 173 | // to 0. And that's what we're trying to avoid. 174 | for (property in autoProperties) { 175 | autoValues[property] = elem.css(property); 176 | } 177 | 178 | doMethod.call(elem, classNames); 179 | 180 | // Make array out of transition properties. 181 | durations = (elem.css(transitionDurationStr) || elem.css('MozTransitionDuration')).split(/\s*,\s*/); 182 | 183 | if (durations.length > 1 || parseFloat(durations[0]) > 0) { 184 | properties = (elem.css(transitionPropertyStr) || elem.css('MozTransitionProperty')).split(/\s*,\s*/); 185 | 186 | if (debug) { console.log('[jquery.transitions]', properties, durations); } 187 | 188 | l = properties.length; 189 | style = elem.data('preTransitionStyle', style); 190 | 191 | if (!style) { 192 | style = elem.attr('style'); 193 | elem.data('preTransitionStyle', style); 194 | } 195 | 196 | while (l--) { 197 | property = properties[l]; 198 | 199 | if (autoProperties[property]) { 200 | if (!flag) { 201 | // Apply a stop to the transitions 202 | elem.css(cssTransitionNone); 203 | flag = true; 204 | } 205 | 206 | // Store their pre-transition value 207 | cssStart[property] = autoValues[property]; 208 | 209 | // Measure their post-transition value 210 | cssEnd[property] = elem.css(property); 211 | 212 | if (debug) { console.log('[jquery.transitions]', property+' start:', cssStart[property], 'end:', cssEnd[property]); } 213 | } 214 | 215 | if (keywordProperties[property]) { 216 | //console.log('keyword property', property, (parseFloat(jQuery.trim(elem.css(transitionStr+'Delay')).split(',')[l])) || (parseFloat(jQuery.trim(elem.css(transitionStr+'Duration')).split(',')[l]))); 217 | // This is ok because we're looping backwards through 218 | // the properties array, so our index, l, still goes onto 219 | // the 'next' entry. 220 | properties.splice(l, 1); 221 | } 222 | 223 | if (flag) { 224 | // Apply the pre-transition values and force a repaint. 225 | undoMethod.call(elem, classNames).css(cssStart).width(); 226 | 227 | // Apply the post-transition values and re-enable transitions. 228 | jQuery.extend(cssEnd, cssTransitionEmpty); 229 | doMethod.call(elem, classNames).css(cssEnd); 230 | } 231 | } 232 | 233 | elem 234 | .unbind(jQuery.support.cssTransitionEnd, end) 235 | .bind(jQuery.support.cssTransitionEnd, { obj: elem, callback: options && options.callback, properties: properties }, end); 236 | } 237 | // Check to see if children have transitions. This is just a 238 | // sanity check. It is not foolproof, because nodes could have 239 | // transitions defined that are nothing to do with the current 240 | // transition. 241 | else if (elem.find(':transition').length){ 242 | elem 243 | .unbind(jQuery.support.cssTransitionEnd, end) 244 | .bind(jQuery.support.cssTransitionEnd, { obj: elem, callback: options && options.callback }, end); 245 | } 246 | else { 247 | elem.removeClass(transitionClass); 248 | } 249 | } 250 | 251 | function addTransitionClass(classNames, options) { 252 | applyClass(this, jQuery.fn.addClass, jQuery.fn.removeClass, classNames, options); 253 | return this; 254 | } 255 | 256 | function removeTransitionClass(classNames, options) { 257 | applyClass(this, jQuery.fn.removeClass, jQuery.fn.addClass, classNames, options); 258 | return this; 259 | } 260 | 261 | // Feature testing for transitionend event 262 | 263 | function removeTest() { 264 | clearTimeout(timer); 265 | timer = null; 266 | testElem.remove(); 267 | } 268 | 269 | var setVars = { 270 | transitionend: function() { 271 | // The standard, but could also be -moz-. 272 | transitionStr = 'transition'; 273 | transitionPropertyStr = 'transitionProperty'; 274 | transitionDurationStr = 'transitionDuration'; 275 | cssTransitionNone = { transition: 'none', MozTransition: 'none' }; 276 | cssTransitionEmpty = { transition: '', MozTransition: '' }; 277 | }, 278 | webkitTransitionEnd: function() { 279 | transitionStr = 'WebkitTransition'; 280 | transitionPropertyStr = 'WebkitTransitionProperty'; 281 | transitionDurationStr = 'WebkitTransitionDuration'; 282 | cssTransitionNone = { WebkitTransition: 'none' }; 283 | cssTransitionEmpty = { WebkitTransition: '' }; 284 | }, 285 | oTransitionEnd: function() { 286 | transitionStr = 'OTransition'; 287 | transitionPropertyStr = 'OTransitionProperty'; 288 | transitionDurationStr = 'OTransitionDuration'; 289 | cssTransitionNone = { OTransition: 'none' }; 290 | cssTransitionEmpty = { OTransition: '' }; 291 | } 292 | } 293 | 294 | function transitionEnd(e) { 295 | if (debug) { console.log('[jquery.transitions] Transition feature test: PASS'); } 296 | 297 | // Get rid of the test element 298 | removeTest(); 299 | 300 | // Store flags in jQuery.support 301 | jQuery.support.cssTransition = true; 302 | jQuery.support.cssTransitionEnd = e.type; 303 | 304 | // Store local variables 305 | setVars[e.type](); 306 | 307 | // Redefine addTransitionClass and removeTransitionClass 308 | jQuery.fn.addTransitionClass = addTransitionClass; 309 | jQuery.fn.removeTransitionClass = removeTransitionClass; 310 | 311 | // Stop listening for transitionend 312 | docElem.unbind('transitionend webkitTransitionEnd oTransitionEnd', transitionEnd); 313 | } 314 | 315 | 316 | // Easing functions 'borrowed' from jQuery UI, renamed to meet 317 | // the CSS spec. TODO: At some point, we should rewrite these 318 | // to exactly match ths CSS spec, which is: 319 | // 320 | // ease: cubic-bezier(0,0,1,1) 321 | // ease-in: cubic-bezier(0.25,0.1,0.25,1) 322 | // ease-out: cubic-bezier(0,0,0.58,1) 323 | // ease-in-out: cubic-bezier(0.42,0,0.58,1) 324 | 325 | jQuery.extend(jQuery.easing, { 326 | 'ease': function (x, t, b, c, d) { 327 | if ((t/=d/2) < 1) { return c/2*t*t + b }; 328 | return -c/2 * ((--t)*(t-2) - 1) + b; 329 | }, 330 | 'ease-in': function (x, t, b, c, d) { 331 | return c*(t/=d)*t*t + b; 332 | }, 333 | 'ease-out': function (x, t, b, c, d) { 334 | return c*((t=t/d-1)*t*t + 1) + b; 335 | }, 336 | 'ease-in-out': function (x, t, b, c, d) { 337 | if ((t/=d/2) < 1) { return c/2*t*t*t + b }; 338 | return c/2*((t-=2)*t*t + 2) + b; 339 | } 340 | }); 341 | 342 | // Use addClass() and removeClass() methods by default 343 | 344 | jQuery.fn.addTransitionClass = function(classNames, o) { 345 | var options = jQuery.extend({}, o, addOptions); 346 | 347 | options.fallback.call(this, classNames, options.callback); 348 | return this; 349 | }; 350 | 351 | jQuery.fn.removeTransitionClass = function(classNames, o){ 352 | var options = jQuery.extend({}, o, removeOptions); 353 | 354 | options.fallback.call(this, classNames, options.callback); 355 | return this; 356 | }; 357 | 358 | // Custom selector :transition for finding elements with 359 | // a transition defined. 360 | 361 | jQuery.expr[':'].transition = function(obj){ 362 | var elem = jQuery(obj), 363 | durations = (elem.css(transitionDurationStr) || elem.css('MozTransitionDuration')).split(/\s*,\s*/); 364 | 365 | return durations && (durations.length > 1 || parseFloat(durations[0]) > 0); 366 | }; 367 | 368 | docElem 369 | .bind('transitionend webkitTransitionEnd oTransitionEnd', transitionEnd) 370 | .ready(function(){ 371 | var wait = setTimeout(function() { 372 | clearTimeout(wait); 373 | wait = null; 374 | 375 | if (debug) { console.log('[jquery.transitions] Running transition feature test.'); } 376 | 377 | // Put the test element in the body 378 | testElem.appendTo('body'); 379 | 380 | // Force the browser to reflow. 381 | testElem.width(); 382 | 383 | // Apply CSS to trigger a transition 384 | testElem.css({ top: -300 }); 385 | 386 | // Set a timeout for the transition test to finish, and if it does not, 387 | // get rid of the test element. Opera requires a much greater delay 388 | // than the time the transition should take, worryingly. 389 | timer = setTimeout(function(){ 390 | removeTest(); 391 | 392 | if (debug) { console.log('[jquery.transitions] Transition feature test: FAIL'); } 393 | 394 | // Store flags in jQuery.support 395 | jQuery.support.cssTransition = false; 396 | jQuery.support.cssTransitionEnd = false; 397 | }, 100); 398 | }, 1); 399 | }); 400 | })(jQuery); --------------------------------------------------------------------------------