├── 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 |
7 | Adds support for transitions to and from 'auto' values, for the CSS properties height, width, margin-left and margin-right.
8 | Adds support for multiple transitions of different durations (not yet in the IE fallback).
9 |
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 |
14 | CSS must be written: transition: width 0.4s ease-in [, property duration easing];. Long-hand properties not supported. Yet.
15 | Supports any number of property definitions, but animates them all at the last defined duration (avoids launching multiple calls to .animate()).
16 | Supports animations on the current element only. That is, children that have transitions defined will not be animated. (Finding them all could, potentially, be expensive).
17 | Supports transition timing CSS values 'linear', 'ease', 'ease-in', 'ease-out' and 'ease-in-out'. cubic-bezier(n,n,n,n) not yet supported.
18 |
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 |
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 |
223 |
224 |
225 | Child show/hide transition
226 |
227 | Click me.
228 |
232 |
233 |
234 | No transition
235 |
236 |
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);
--------------------------------------------------------------------------------