Accordion: Rewrote the animation code. Fixes #4178 - Accordion animation a bit jumpy in some browsers. Fixes #7371 - Accordion: Incorrect size when zoomed.

This commit is contained in:
Scott González 2012-02-16 16:51:46 -05:00
parent f262a531fa
commit 3d9f6b5bc7
4 changed files with 112 additions and 164 deletions

View File

@ -1,7 +1,7 @@
commonWidgetTests( "accordion", {
defaults: {
active: 0,
animated: "slide",
animate: {},
collapsible: false,
disabled: false,
event: "click",

View File

@ -1,6 +1,7 @@
commonWidgetTests( "accordion", {
defaults: {
active: 0,
animate: null,
animated: "slide",
autoHeight: true,
clearStyle: false,

View File

@ -18,13 +18,13 @@ function accordion_equalHeights( accordion, min, max ) {
}
function accordion_setupTeardown() {
var animated = $.ui.accordion.prototype.options.animated;
var animate = $.ui.accordion.prototype.options.animate;
return {
setup: function() {
$.ui.accordion.prototype.options.animated = false;
$.ui.accordion.prototype.options.animate = false;
},
teardown: function() {
$.ui.accordion.prototype.options.animated = animated;
$.ui.accordion.prototype.options.animate = animate;
}
};
}

View File

@ -17,7 +17,7 @@ $.widget( "ui.accordion", {
version: "@VERSION",
options: {
active: 0,
animated: "slide",
animate: {},
collapsible: false,
event: "click",
header: "> li > :first-child,> :not(li):even",
@ -35,7 +35,7 @@ $.widget( "ui.accordion", {
_create: function() {
var options = this.options;
this.lastToggle = {};
this.prevShow = this.prevHide = $();
this.element.addClass( "ui-accordion ui-widget ui-helper-reset" );
this.headers = this.element.find( options.header )
@ -62,6 +62,7 @@ $.widget( "ui.accordion", {
this.active.next().addClass( "ui-accordion-content-active" );
this._createIcons();
this.originalHeight = this.element[0].style.height;
this.refresh();
// ARIA
@ -156,6 +157,7 @@ $.widget( "ui.accordion", {
.removeAttr( "role" )
.removeClass( "ui-helper-reset ui-widget-content ui-corner-bottom ui-accordion-content ui-accordion-content-active ui-accordion-disabled ui-state-disabled" );
if ( this.options.heightStyle !== "content" ) {
this.element.css( "height", this.originalHeight );
contents.css( "height", "" );
}
},
@ -229,12 +231,12 @@ $.widget( "ui.accordion", {
},
refresh: function() {
var options = this.options,
var heightStyle = this.options.heightStyle,
parent = this.element.parent(),
maxHeight,
overflow;
if ( options.heightStyle === "fill" ) {
if ( heightStyle === "fill" ) {
// IE 6 treats height like minHeight, so we need to turn off overflow
// in order to get a reliable height
// we use the minHeight support test because we assume that only
@ -267,7 +269,7 @@ $.widget( "ui.accordion", {
$( this ).innerHeight() + $( this ).height() ) );
})
.css( "overflow", "auto" );
} else if ( options.heightStyle === "auto" ) {
} else if ( heightStyle === "auto" ) {
maxHeight = 0;
this.headers.next()
.each(function() {
@ -276,7 +278,9 @@ $.widget( "ui.accordion", {
.height( maxHeight );
}
return this;
if ( heightStyle !== "content" ) {
this.element.height( this.element.height() );
}
},
_activate: function( index ) {
@ -361,41 +365,20 @@ $.widget( "ui.accordion", {
},
_toggle: function( data ) {
var that = this,
options = this.options,
toShow = data.newContent,
toHide = data.oldContent;
var toShow = data.newContent,
toHide = this.prevShow.length ? this.prevShow : data.oldContent;
function complete() {
that._completed( data );
}
// handle activating a panel during the animation for another activation
this.prevShow.add( this.prevHide ).stop( true, true );
this.prevShow = toShow;
this.prevHide = toHide;
if ( options.animated ) {
var animations = $.ui.accordion.animations,
animation = options.animated,
additional;
if ( !animations[ animation ] ) {
additional = {
easing: $.easing[ animation ] ? animation : "slide",
duration: 700
};
animation = "slide";
}
animations[ animation ]({
widget: this,
toShow: toShow,
toHide: toHide,
prevShow: this.lastToggle.toShow,
prevHide: this.lastToggle.toHide,
complete: complete,
down: toShow.length && ( !toHide.length || ( toShow.index() < toHide.index() ) )
}, additional );
if ( this.options.animate ) {
this._animate( toShow, toHide, data );
} else {
toHide.hide();
toShow.show();
complete();
this._completed( data );
}
// TODO assert that the blur and focus triggers are really necessary, remove otherwise
@ -415,17 +398,51 @@ $.widget( "ui.accordion", {
.focus();
},
_animate: function( toShow, toHide, data ) {
var total, easing, duration,
that = this,
down = toShow.length &&
( !toHide.length || ( toShow.index() < toHide.index() ) ),
animate = this.options.animate || {},
options = down && animate.down || animate,
complete = function() {
toShow.removeData( "accordionHeight" );
that._completed( data );
};
if ( typeof options === "number" ) {
duration = options;
}
if ( typeof options === "string" ) {
easing = options;
}
// fall back from options to animation in case of partial down settings
easing = easing || options.easing || animate.easing;
duration = duration || options.duration || animate.duration;
if ( !toHide.size() ) {
return toShow.animate( showProps, duration, easing, complete );
}
if ( !toShow.size() ) {
return toHide.animate( hideProps, duration, easing, complete );
}
total = toShow.show().outerHeight();
toHide.animate( hideProps, duration, easing );
toShow
.hide()
.data( "accordionHeight", {
total: total,
toHide: toHide
})
.animate( this.options.heightStyle === "content" ? showProps : showPropsAdjust,
duration, easing, complete );
},
_completed: function( data ) {
var toShow = data.newContent,
toHide = data.oldContent;
if ( this.options.heightStyle === "content" ) {
toShow.add( toHide ).css({
height: "",
overflow: ""
});
}
// other classes are removed before the animation; this one needs to stay until completed
toHide.removeClass( "ui-accordion-content-active" );
// Work around for rendering bug in IE (#5421)
@ -437,123 +454,19 @@ $.widget( "ui.accordion", {
}
});
$.extend( $.ui.accordion, {
animations: {
slide: function( options, additions ) {
if ( options.prevShow || options.prevHide ) {
options.prevHide.stop( true, true );
options.toHide = options.prevShow;
}
var showOverflow = options.toShow.css( "overflow" ),
showOverflowX = options.toHide.css( "overflowX" ),
showOverflowY = options.toHide.css( "overflowY" ),
hideOverflow = options.toHide.css( "overflow" ),
hideOverflowX = options.toHide.css( "overflowX" ),
hideOverflowY = options.toHide.css( "overflowY" ),
percentDone = 0,
$.fx.step.accordionHeight = function( fx ) {
var elem = $( fx.elem ),
data = elem.data( "accordionHeight" );
elem.height( data.total - elem.outerHeight() - data.toHide.outerHeight() + elem.height() );
};
var hideProps = {},
showProps = {},
hideProps = {},
fxAttrs = [ "height", "paddingTop", "paddingBottom" ],
originalWidth;
options = $.extend({
easing: "swing",
duration: 300
}, options, additions );
options.widget.lastToggle = options;
if ( !options.toHide.size() ) {
originalWidth = options.toShow[0].style.width;
options.toShow
.show()
.width( options.toShow.width() )
.hide()
.animate({
height: "show",
paddingTop: "show",
paddingBottom: "show"
}, {
duration: options.duration,
easing: options.easing,
complete: function() {
options.toShow.width( originalWidth );
options.complete();
}
});
return;
}
if ( !options.toShow.size() ) {
options.toHide.animate({
height: "hide",
paddingTop: "hide",
paddingBottom: "hide"
}, options );
return;
}
// fix width before calculating height of hidden element
var s = options.toShow;
originalWidth = s[0].style.width;
s.width( s.parent().width()
- parseFloat( s.css( "paddingLeft" ) )
- parseFloat( s.css( "paddingRight" ) )
- ( parseFloat( s.css( "borderLeftWidth" ) ) || 0 )
- ( parseFloat( s.css( "borderRightWidth" ) ) || 0 ) );
$.each( fxAttrs, function( i, prop ) {
hideProps[ prop ] = "hide";
var parts = ( "" + $.css( options.toShow[0], prop ) ).match( /^([\d+-.]+)(.*)$/ ),
// work around bug when a panel has no height - #7335
propVal = prop === "height" && parts[ 1 ] === "0" ? 1 : parts[ 1 ];
showProps[ prop ] = {
value: propVal,
unit: parts[ 2 ] || "px"
};
});
options.toShow.css({ height: 0, overflow: "hidden" }).show();
options.toHide
.filter( ":hidden" )
.each( options.complete )
.end()
.filter( ":visible" )
.animate( hideProps, {
step: function( now, settings ) {
if ( settings.prop == "height" || settings.prop == "paddingTop" || settings.prop == "paddingBottom" ) {
percentDone = ( settings.end - settings.start === 0 ) ? 0 :
( settings.now - settings.start ) / ( settings.end - settings.start );
}
options.toShow[ 0 ].style[ settings.prop ] =
( percentDone * showProps[ settings.prop ].value )
+ showProps[ settings.prop ].unit;
},
duration: options.duration,
easing: options.easing,
complete: function() {
options.toShow.css({
width: originalWidth,
overflow: showOverflow,
overflowX: showOverflowX,
overflowY: showOverflowY
});
options.toHide.css({
overflow: hideOverflow,
overflowX: hideOverflowX,
overflowY: hideOverflowY
});
options.complete();
}
});
},
bounceslide: function( options ) {
this.slide( options, {
easing: options.down ? "easeOutBounce" : "swing",
duration: options.down ? 1000 : 200
});
}
}
});
showPropsAdjust = {};
hideProps.height = hideProps.paddingTop = hideProps.paddingBottom =
hideProps.borderTopWidth = hideProps.borderBottomWidth = "hide";
showProps.height = showProps.paddingTop = showProps.paddingBottom =
showProps.borderTopWidth = showProps.borderBottomWidth = "show";
$.extend( showPropsAdjust, showProps, { accordionHeight: "show" } );
@ -697,6 +610,40 @@ if ( $.uiBackCompat !== false ) {
return ret;
};
}( jQuery, jQuery.ui.accordion.prototype ) );
// animated option
// NOTE: this only provides support for "slide", "bounceslide", and easings
// not the full $.ui.accordion.animations API
(function( $, prototype ) {
$.extend( prototype.options, {
animate: null,
animated: "slide"
});
var _create = prototype._create;
prototype._create = function() {
var options = this.options;
if ( options.animate === null ) {
if ( !options.animated ) {
options.animate = false;
} else if ( options.animated === "slide" ) {
options.animate = 300;
} else if ( options.animated === "bounceslide" ) {
options.animate = {
duration: 200,
down: {
easing: "easeOutBounce",
duration: 1000
}
}
} else {
options.animate = options.animated;
}
}
_create.call( this );
};
}( jQuery, jQuery.ui.accordion.prototype ) );
}
})( jQuery );