Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP

Accordion: Attempt at improving animation. #587

Closed
wants to merge 11 commits into from

2 participants

Scott González Jörn Zaefferer
Scott González

This is an attempt at fixing #4178.

The logic is to use core as much as possible, then override the animation of the height of the panel being shown. Most browsers don't support fractional pixels, and even browsers that do support fractions can't handle the level of precision that we get during an animation. This causes the combined height of the panel being shown and the panel being hidden to jump around a bit during the animation. We try to deal with that by recalculating the total height at each step of the animation and adjusting the height of the panel being shown to account for any pixels that were lost or gained by the browser not being able to handle the exact values that were calculated by .animate().

This works great in Chrome and Opera. It's ok in IE and not so great in Firefox. However, this is better than what we have now in all browsers.

This code should not e committed as is. $.fx.step._height and the data key should probably be prefixed for accordion, there are var statements in the middle of functions, etc. This pull is just to keep notes around and hopefully provide a good starting point for a final solution.

ui/jquery.ui.accordion.js
@@ -17,6 +17,7 @@ $.widget( "ui.accordion", {
17 17
 	version: "@VERSION",
18 18
 	options: {
19 19
 		active: 0,
  20
+		// TODO: animation option
1
Jörn Zaefferer Owner

actually animate

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Scott González

Landed in 3d9f6b5 :-)

Scott González scottgonzalez closed this February 16, 2012
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
This page is out of date. Refresh to see the latest.
2  tests/unit/accordion/accordion_defaults.js
... ...
@@ -1,7 +1,7 @@
1 1
 commonWidgetTests( "accordion", {
2 2
 	defaults: {
3 3
 		active: 0,
4  
-		animated: "slide",
  4
+		animate: {},
5 5
 		collapsible: false,
6 6
 		disabled: false,
7 7
 		event: "click",
1  tests/unit/accordion/accordion_defaults_deprecated.js
... ...
@@ -1,6 +1,7 @@
1 1
 commonWidgetTests( "accordion", {
2 2
 	defaults: {
3 3
 		active: 0,
  4
+		animate: null,
4 5
 		animated: "slide",
5 6
 		autoHeight: true,
6 7
 		clearStyle: false,
6  tests/unit/accordion/accordion_test_helpers.js
@@ -18,13 +18,13 @@ function accordion_equalHeights( accordion, min, max ) {
18 18
 }
19 19
 
20 20
 function accordion_setupTeardown() {
21  
-	var animated = $.ui.accordion.prototype.options.animated;
  21
+	var animate = $.ui.accordion.prototype.options.animate;
22 22
 	return {
23 23
 		setup: function() {
24  
-			$.ui.accordion.prototype.options.animated = false;
  24
+			$.ui.accordion.prototype.options.animate = false;
25 25
 		},
26 26
 		teardown: function() {
27  
-			$.ui.accordion.prototype.options.animated = animated;
  27
+			$.ui.accordion.prototype.options.animate = animate;
28 28
 		}
29 29
 	};
30 30
 }
267  ui/jquery.ui.accordion.js
@@ -17,7 +17,7 @@ $.widget( "ui.accordion", {
17 17
 	version: "@VERSION",
18 18
 	options: {
19 19
 		active: 0,
20  
-		animated: "slide",
  20
+		animate: {},
21 21
 		collapsible: false,
22 22
 		event: "click",
23 23
 		header: "> li > :first-child,> :not(li):even",
@@ -35,7 +35,7 @@ $.widget( "ui.accordion", {
35 35
 	_create: function() {
36 36
 		var options = this.options;
37 37
 
38  
-		this.lastToggle = {};
  38
+		this.prevShow = this.prevHide = $();
39 39
 		this.element.addClass( "ui-accordion ui-widget ui-helper-reset" );
40 40
 
41 41
 		this.headers = this.element.find( options.header )
@@ -62,6 +62,7 @@ $.widget( "ui.accordion", {
62 62
 		this.active.next().addClass( "ui-accordion-content-active" );
63 63
 
64 64
 		this._createIcons();
  65
+		this.originalHeight = this.element[0].style.height;
65 66
 		this.refresh();
66 67
 
67 68
 		// ARIA
@@ -156,6 +157,7 @@ $.widget( "ui.accordion", {
156 157
 			.removeAttr( "role" )
157 158
 			.removeClass( "ui-helper-reset ui-widget-content ui-corner-bottom ui-accordion-content ui-accordion-content-active ui-accordion-disabled ui-state-disabled" );
158 159
 		if ( this.options.heightStyle !== "content" ) {
  160
+			this.element.css( "height", this.originalHeight );
159 161
 			contents.css( "height", "" );
160 162
 		}
161 163
 	},
@@ -229,12 +231,12 @@ $.widget( "ui.accordion", {
229 231
 	},
230 232
 
231 233
 	refresh: function() {
232  
-		var options = this.options,
  234
+		var heightStyle = this.options.heightStyle,
233 235
 			parent = this.element.parent(),
234 236
 			maxHeight,
235 237
 			overflow;
236 238
 
237  
-		if ( options.heightStyle === "fill" ) {
  239
+		if ( heightStyle === "fill" ) {
238 240
 			// IE 6 treats height like minHeight, so we need to turn off overflow
239 241
 			// in order to get a reliable height
240 242
 			// we use the minHeight support test because we assume that only
@@ -267,7 +269,7 @@ $.widget( "ui.accordion", {
267 269
 						$( this ).innerHeight() + $( this ).height() ) );
268 270
 				})
269 271
 				.css( "overflow", "auto" );
270  
-		} else if ( options.heightStyle === "auto" ) {
  272
+		} else if ( heightStyle === "auto" ) {
271 273
 			maxHeight = 0;
272 274
 			this.headers.next()
273 275
 				.each(function() {
@@ -276,7 +278,9 @@ $.widget( "ui.accordion", {
276 278
 				.height( maxHeight );
277 279
 		}
278 280
 
279  
-		return this;
  281
+		if ( heightStyle !== "content" ) {
  282
+			this.element.height( this.element.height() );
  283
+		}
280 284
 	},
281 285
 
282 286
 	_activate: function( index ) {
@@ -361,41 +365,20 @@ $.widget( "ui.accordion", {
361 365
 	},
362 366
 
363 367
 	_toggle: function( data ) {
364  
-		var that = this,
365  
-			options = this.options,
366  
-			toShow = data.newContent,
367  
-			toHide = data.oldContent;
368  
-
369  
-		function complete() {
370  
-			that._completed( data );
371  
-		}
  368
+		var toShow = data.newContent,
  369
+			toHide = this.prevShow.length ? this.prevShow : data.oldContent;
372 370
 
373  
-		if ( options.animated ) {
374  
-			var animations = $.ui.accordion.animations,
375  
-				animation = options.animated,
376  
-				additional;
377  
-
378  
-			if ( !animations[ animation ] ) {
379  
-				additional = {
380  
-					easing: $.easing[ animation ] ? animation : "slide",
381  
-					duration: 700
382  
-				};
383  
-				animation = "slide";
384  
-			}
  371
+		// handle activating a panel during the animation for another activation
  372
+		this.prevShow.add( this.prevHide ).stop( true, true );
  373
+		this.prevShow = toShow;
  374
+		this.prevHide = toHide;
385 375
 
386  
-			animations[ animation ]({
387  
-				widget: this,
388  
-				toShow: toShow,
389  
-				toHide: toHide,
390  
-				prevShow: this.lastToggle.toShow,
391  
-				prevHide: this.lastToggle.toHide,
392  
-				complete: complete,
393  
-				down: toShow.length && ( !toHide.length || ( toShow.index() < toHide.index() ) )
394  
-			}, additional );
  376
+		if ( this.options.animate ) {
  377
+			this._animate( toShow, toHide, data );
395 378
 		} else {
396 379
 			toHide.hide();
397 380
 			toShow.show();
398  
-			complete();
  381
+			this._completed( data );
399 382
 		}
400 383
 
401 384
 		// TODO assert that the blur and focus triggers are really necessary, remove otherwise
@@ -415,17 +398,51 @@ $.widget( "ui.accordion", {
415 398
 			.focus();
416 399
 	},
417 400
 
  401
+	_animate: function( toShow, toHide, data ) {
  402
+		var total, easing, duration,
  403
+			that = this,
  404
+			down = toShow.length &&
  405
+				( !toHide.length || ( toShow.index() < toHide.index() ) ),
  406
+			animate = this.options.animate || {},
  407
+			options = down && animate.down || animate,
  408
+			complete = function() {
  409
+				toShow.removeData( "accordionHeight" );
  410
+				that._completed( data );
  411
+			};
  412
+
  413
+		if ( typeof options === "number" ) {
  414
+			duration = options;
  415
+		}
  416
+		if ( typeof options === "string" ) {
  417
+			easing = options;
  418
+		}
  419
+		// fall back from options to animation in case of partial down settings
  420
+		easing = easing || options.easing || animate.easing;
  421
+		duration = duration || options.duration || animate.duration;
  422
+
  423
+		if ( !toHide.size() ) {
  424
+			return toShow.animate( showProps, duration, easing, complete );
  425
+		}
  426
+		if ( !toShow.size() ) {
  427
+			return toHide.animate( hideProps, duration, easing, complete );
  428
+		}
  429
+
  430
+		total = toShow.show().outerHeight();
  431
+		toHide.animate( hideProps, duration, easing );
  432
+		toShow
  433
+			.hide()
  434
+			.data( "accordionHeight", {
  435
+				total: total,
  436
+				toHide: toHide
  437
+			})
  438
+			.animate( this.options.heightStyle === "content" ? showProps : showPropsAdjust,
  439
+				duration, easing, complete );
  440
+	},
  441
+
418 442
 	_completed: function( data ) {
419 443
 		var toShow = data.newContent,
420 444
 			toHide = data.oldContent;
421 445
 
422  
-		if ( this.options.heightStyle === "content" ) {
423  
-			toShow.add( toHide ).css({
424  
-				height: "",
425  
-				overflow: ""
426  
-			});
427  
-		}
428  
-
429 446
 		// other classes are removed before the animation; this one needs to stay until completed
430 447
 		toHide.removeClass( "ui-accordion-content-active" );
431 448
 		// Work around for rendering bug in IE (#5421)
@@ -437,123 +454,19 @@ $.widget( "ui.accordion", {
437 454
 	}
438 455
 });
439 456
 
440  
-$.extend( $.ui.accordion, {
441  
-	animations: {
442  
-		slide: function( options, additions ) {
443  
-			if ( options.prevShow || options.prevHide ) {
444  
-				options.prevHide.stop( true, true );
445  
-				options.toHide = options.prevShow;
446  
-			}
447  
-
448  
-			var showOverflow = options.toShow.css( "overflow" ),
449  
-				showOverflowX = options.toHide.css( "overflowX" ),
450  
-				showOverflowY = options.toHide.css( "overflowY" ),
451  
-				hideOverflow = options.toHide.css( "overflow" ),
452  
-				hideOverflowX = options.toHide.css( "overflowX" ),
453  
-				hideOverflowY = options.toHide.css( "overflowY" ),
454  
-				percentDone = 0,
455  
-				showProps = {},
456  
-				hideProps = {},
457  
-				fxAttrs = [ "height", "paddingTop", "paddingBottom" ],
458  
-				originalWidth;
459  
-			options = $.extend({
460  
-				easing: "swing",
461  
-				duration: 300
462  
-			}, options, additions );
463  
-
464  
-			options.widget.lastToggle = options;
465  
-
466  
-			if ( !options.toHide.size() ) {
467  
-				originalWidth = options.toShow[0].style.width;
468  
-				options.toShow
469  
-					.show()
470  
-					.width( options.toShow.width() )
471  
-					.hide()
472  
-					.animate({
473  
-						height: "show",
474  
-						paddingTop: "show",
475  
-						paddingBottom: "show"
476  
-					}, {
477  
-						duration: options.duration,
478  
-						easing: options.easing,
479  
-						complete: function() {
480  
-							options.toShow.width( originalWidth );
481  
-							options.complete();
482  
-						}
483  
-					});
484  
-				return;
485  
-			}
486  
-			if ( !options.toShow.size() ) {
487  
-				options.toHide.animate({
488  
-					height: "hide",
489  
-					paddingTop: "hide",
490  
-					paddingBottom: "hide"
491  
-				}, options );
492  
-				return;
493  
-			}
494  
-			// fix width before calculating height of hidden element
495  
-			var s = options.toShow;
496  
-			originalWidth = s[0].style.width;
497  
-			s.width( s.parent().width()
498  
-				- parseFloat( s.css( "paddingLeft" ) )
499  
-				- parseFloat( s.css( "paddingRight" ) )
500  
-				- ( parseFloat( s.css( "borderLeftWidth" ) ) || 0 )
501  
-				- ( parseFloat( s.css( "borderRightWidth" ) ) || 0 ) );
502  
-
503  
-			$.each( fxAttrs, function( i, prop ) {
504  
-				hideProps[ prop ] = "hide";
505  
-
506  
-				var parts = ( "" + $.css( options.toShow[0], prop ) ).match( /^([\d+-.]+)(.*)$/ ),
507  
-					// work around bug when a panel has no height - #7335
508  
-					propVal = prop === "height" && parts[ 1 ] === "0" ? 1 : parts[ 1 ];
509  
-				showProps[ prop ] = {
510  
-					value: propVal,
511  
-					unit: parts[ 2 ] || "px"
512  
-				};
513  
-			});
514  
-			options.toShow.css({ height: 0, overflow: "hidden" }).show();
515  
-			options.toHide
516  
-				.filter( ":hidden" )
517  
-					.each( options.complete )
518  
-				.end()
519  
-				.filter( ":visible" )
520  
-				.animate( hideProps, {
521  
-				step: function( now, settings ) {
522  
-					if ( settings.prop == "height" || settings.prop == "paddingTop" || settings.prop == "paddingBottom" ) {
523  
-						percentDone = ( settings.end - settings.start === 0 ) ? 0 :
524  
-							( settings.now - settings.start ) / ( settings.end - settings.start );
525  
-					}
526  
-
527  
-					options.toShow[ 0 ].style[ settings.prop ] =
528  
-						( percentDone * showProps[ settings.prop ].value )
529  
-						+ showProps[ settings.prop ].unit;
530  
-				},
531  
-				duration: options.duration,
532  
-				easing: options.easing,
533  
-				complete: function() {
534  
-					options.toShow.css({
535  
-						width: originalWidth,
536  
-						overflow: showOverflow,
537  
-						overflowX: showOverflowX,
538  
-						overflowY: showOverflowY
539  
-					});
540  
-					options.toHide.css({
541  
-						overflow: hideOverflow,
542  
-						overflowX: hideOverflowX,
543  
-						overflowY: hideOverflowY
544  
-					});
545  
-					options.complete();
546  
-				}
547  
-			});
548  
-		},
549  
-		bounceslide: function( options ) {
550  
-			this.slide( options, {
551  
-				easing: options.down ? "easeOutBounce" : "swing",
552  
-				duration: options.down ? 1000 : 200
553  
-			});
554  
-		}
555  
-	}
556  
-});
  457
+$.fx.step.accordionHeight = function( fx ) {
  458
+	var elem = $( fx.elem ),
  459
+		data = elem.data( "accordionHeight" );
  460
+	elem.height( data.total - elem.outerHeight() - data.toHide.outerHeight() + elem.height() );
  461
+};
  462
+var hideProps = {},
  463
+	showProps = {},
  464
+	showPropsAdjust = {};
  465
+hideProps.height = hideProps.paddingTop = hideProps.paddingBottom =
  466
+	hideProps.borderTopWidth = hideProps.borderBottomWidth = "hide";
  467
+showProps.height = showProps.paddingTop = showProps.paddingBottom =
  468
+	showProps.borderTopWidth = showProps.borderBottomWidth = "show";
  469
+$.extend( showPropsAdjust, showProps, { accordionHeight: "show" } );
557 470
 
558 471
 
559 472
 
@@ -697,6 +610,40 @@ if ( $.uiBackCompat !== false ) {
697 610
 			return ret;
698 611
 		};
699 612
 	}( jQuery, jQuery.ui.accordion.prototype ) );
  613
+
  614
+	// animated option
  615
+	// NOTE: this only provides support for "slide", "bounceslide", and easings
  616
+	// not the full $.ui.accordion.animations API
  617
+	(function( $, prototype ) {
  618
+		$.extend( prototype.options, {
  619
+			animate: null,
  620
+			animated: "slide"
  621
+		});
  622
+
  623
+		var _create = prototype._create;
  624
+		prototype._create = function() {
  625
+			var options = this.options;
  626
+			if ( options.animate === null ) {
  627
+				if ( !options.animated ) {
  628
+					options.animate = false;
  629
+				} else if ( options.animated === "slide" ) {
  630
+					options.animate = 300;
  631
+				} else if ( options.animated === "bounceslide" ) {
  632
+					options.animate = {
  633
+						duration: 200,
  634
+						down: {
  635
+							easing: "easeOutBounce",
  636
+							duration: 1000
  637
+						}
  638
+					}
  639
+				} else {
  640
+					options.animate = options.animated;
  641
+				}
  642
+			}
  643
+
  644
+			_create.call( this );
  645
+		};
  646
+	}( jQuery, jQuery.ui.accordion.prototype ) );
700 647
 }
701 648
 
702 649
 })( jQuery );
Commit_comment_tip

Tip: You can add notes to lines in a file. Hover to the left of a line to make a note

Something went wrong with that request. Please try again.