Performance improvements to minimize layouts, style recalculations, and repaints

Plugin now requires jQuery 1.9 because it access's the container's css
using an array. e.g.: $container.css(['paddingLeft', 'width']);
pull/56/head
Glen Cheney 11 years ago
parent 970f4cd6f9
commit ac86150413

@ -10,15 +10,14 @@
* jQuery Shuffle Plugin
* Uses CSS Transforms to filter down a grid of items (degrades to jQuery's animate).
* Inspired by Isotope http://isotope.metafizzy.co/
* Use it for whatever you want!
* Use it for whatever you want! Requires jQuery 1.9+
* @author Glen Cheney (http://glencheney.com)
* @version 1.6.2
* @date 03/06/13
* @version 1.6.3
* @date 03/13/13
*/
(function($, Modernizr, undefined) {
'use strict';
// You can return `undefined` from the `by` function to revert to DOM order
// This plugin does NOT return a jQuery object. It returns a plain array because
// jQuery sorts everything in DOM order.
@ -73,9 +72,9 @@
};
$.fn.sorted.defaults = {
reverse: false,
by: null,
randomize: false
reverse: false, // Use array.reverse() to reverse the results
by: null, // Sorting function
randomize: false // If true, this will skip the sorting and return a randomized order in the array
};
@ -120,28 +119,18 @@
_init : function() {
var self = this,
containerCSS,
transEndEventNames,
resizeFunc = $.proxy( self._onResize, self ),
afterResizeFunc = $.proxy( self._afterResize, self ),
beforeResizeFunc;
if ( self.hideLayoutWithFade ) {
beforeResizeFunc = $.proxy( self._beforeResize, self );
self._debouncedBeforeResize = self.throttle ? self.throttle( self.throttleTime, true, beforeResizeFunc ) : beforeResizeFunc;
}
// Get debounced versions of our resize methods
self._debouncedResize = self.throttle ? self.throttle( self.throttleTime, afterResizeFunc ) : afterResizeFunc;
afterResizeFunc,
debouncedAfterResize,
beforeResizeFunc,
debouncedBeforeResize;
self.$items = self._getItems().addClass('shuffle-item');
self.transitionName = self.prefixed('transition'),
self.transitionDelayName = self.prefixed('transitionDelay');
self.transform = self.getPrefixed('transform');
// Get offset from container
self.offset = {
left: parseInt( ( self.$container.css('padding-left') || 0 ), 10 ),
top: parseInt( ( self.$container.css('padding-top') || 0 ), 10 )
};
self.isFluid = self.columnWidth && typeof self.columnWidth === 'function';
// Get transitionend event name
@ -152,35 +141,46 @@
'msTransition' : 'MSTransitionEnd',
'transition' : 'transitionend'
};
self.transitionEndName = transEndEventNames[ self.transitionName ];
// CSS for each item
self.itemCss = {
position: 'absolute',
top: 0,
left: 0
};
if ( self.$container.css('position') === 'static' ) {
self.$container.css('position', 'relative');
}
self.transitionend = transEndEventNames[ self.transitionName ];
// Set up css for transitions
self.$container[0].style[ self.transitionName ] = 'height ' + self.speed + 'ms ' + self.easing;
self._initItems( !self.showInitialTransition );
self._initItems( self.showInitialTransition );
// Bind resize events (http://stackoverflow.com/questions/1852751/window-resize-event-firing-in-internet-explorer)
// Get debounced versions of our resize methods
afterResizeFunc = $.proxy( self._afterResize, self );
debouncedAfterResize = self.throttle ? self.throttle( self.throttleTime, afterResizeFunc ) : afterResizeFunc;
self.$window.on('resize.shuffle', debouncedAfterResize);
// If we need to hide layouts with a fade instead, we need another event on window resize
// which is only fired the first time window resize is triggered
if ( self.hideLayoutWithFade ) {
beforeResizeFunc = $.proxy( self._beforeResize, self );
debouncedBeforeResize = self.throttle ? self.throttle( self.throttleTime, true, beforeResizeFunc ) : beforeResizeFunc;
self.$window.on('resize.shuffle', debouncedBeforeResize);
}
// http://stackoverflow.com/questions/1852751/window-resize-event-firing-in-internet-explorer
// self.windowHeight = self.$window.height();
// self.windowWidth = self.$window.width();
self.$window.on('resize.shuffle', resizeFunc);
// Position cannot be static
containerCSS = self.$container.css(['paddingLeft', 'paddingRight', 'position', 'width']);
if ( containerCSS.position === 'static' ) {
self.$container.css('position', 'relative');
}
self._setColumns();
// Get offset from container
self.offset = {
left: parseInt( containerCSS.paddingLeft, 10 ) || 0,
top: parseInt( containerCSS.paddingTop, 10 ) || 0
};
self._resetCols();
// We already got the container's width above, no need to cause another reflow getting it again...
self._setColumns( parseInt( containerCSS.width, 10 ) );
self.shuffle( self.group );
if ( !self.showInitialTransition ) {
self._initItems();
setTimeout(function() {
self._initItems( true );
}, 0);
}
},
@ -195,15 +195,15 @@
self.fire('filter');
// Default is to show all items
$items.removeClass('concealed filtered');
// $items.removeClass('concealed filtered');
// Loop through each item and use provided function to determine
// whether to hide it or not.
if ($.isFunction(category)) {
if ( $.isFunction(category) ) {
$items.each(function() {
var $item = $(this),
passes = category.call($item[0], $item, self);
$item.addClass(passes ? 'filtered' : 'concealed');
// $item.addClass(passes ? 'filtered' : 'concealed');
if ( passes ) {
$filtered = $filtered.add($item);
@ -219,10 +219,10 @@
var $this = $(this),
groups = $this.data('groups'),
keys = self.delimeter && !$.isArray( groups ) ? groups.split( self.delimeter ) : groups,
passes = $.inArray(category, keys) > -1,
theClass = passes ? 'concealed' : 'filtered';
passes = $.inArray(category, keys) > -1;
// theClass = passes ? 'concealed' : 'filtered';
$this.addClass( theClass );
// $this.addClass( theClass );
if ( passes ) {
$filtered = $filtered.add($this);
@ -232,23 +232,53 @@
// category === 'all', add filtered class to everything
else {
$filtered = $items.addClass('filtered');
$filtered = $items;
// $filtered = $items.addClass('filtered');
}
}
// Individually add/remove concealed/filtered classes
$items.filter( $filtered ).each(function() {
var $filteredItem = $(this);
// Remove concealed if it's there
if ( $filteredItem.hasClass('concealed') ) {
$filteredItem.removeClass('concealed');
}
// Add filtered class if it's not there
if ( !$filteredItem.hasClass('filtered') ) {
$filteredItem.addClass('filtered');
}
});
$items.not( $filtered ).each(function() {
var $filteredItem = $(this);
// Add concealed if it's not there
if ( !$filteredItem.hasClass('concealed') ) {
$filteredItem.addClass('concealed');
}
// Remove filtered class if it's there
if ( $filteredItem.hasClass('filtered') ) {
$filteredItem.removeClass('filtered');
}
});
$items = null;
$collection = null;
return $filtered;
},
_initItems : function( withoutTransition, $items ) {
_initItems : function( withTransition, $items ) {
var self = this;
// Default to true if unspecified
withTransition = withTransition === false ? false : true;
$items = $items || self.$items;
$items.each(function() {
$(this).css(self.itemCss);
// Set CSS transition for transforms and opacity
if ( self.supported && !withoutTransition && self.useTransition ) {
if ( self.supported && self.useTransition && withTransition ) {
self._setTransition(this);
}
});
@ -270,11 +300,11 @@
// $collection can be an array of dom elements or jquery object
$.each( $collection, function(i) {
// This works because the transition-property: transform, opacity;
this.style[self.transitionName + 'Delay'] = '0ms,' + ((i + 1) * self.sequentialFadeDelay) + 'ms';
this.style[ self.transitionDelayName ] = '0ms,' + ((i + 1) * self.sequentialFadeDelay) + 'ms';
// Set the delay back to zero after one transition
$(this).one(self.transitionEndName, function() {
this.style[self.transitionName + 'Delay'] = '0ms';
$(this).one(self.transitionend, function() {
this.style[ self.transitionDelayName ] = '0ms';
});
});
},
@ -287,8 +317,8 @@
}
$.each( $collection, function() {
$(this).off( self.transitionEndName );
this.style[ self.transitionName + 'Delay' ] = '0ms';
$(this).off( self.transitionend );
this.style[ self.transitionDelayName ] = '0ms';
});
},
@ -340,9 +370,9 @@
// calculates number of columns
// i.e. this.colWidth = 200
_setColumns : function() {
_setColumns : function( theContainerWidth ) {
var self = this,
containerWidth = self.$container.width(),
containerWidth = theContainerWidth || self.$container.width(),
gutter = typeof self.gutterWidth === 'function' ? self.gutterWidth( containerWidth ) : self.gutterWidth;
// use fluid columnWidth function if there
@ -557,36 +587,31 @@
});
},
_onResize : function() {
_afterResize : function() {
var self = this,
containerWidth = self.$container.width();
containerWidth;
// If shuffle is disabled, destroyed, or containerWidth hasn't changed, don't do anything
if ( !self.enabled || self.destroyed || containerWidth === self.containerWidth ) {
// If shuffle is disabled, destroyed, don't do anything
if ( !self.enabled || self.destroyed ) {
return;
}
// This should execute the first time _onResize is called
if ( self.hideLayoutWithFade ) {
self._debouncedBeforeResize();
}
// This should execute the last time _onResize is called
self._debouncedResize();
// Will need to check height in the future if it's layed out horizontaly
containerWidth = self.$container.width();
},
_afterResize : function() {
var self = this;
// containerWidth hasn't changed, don't do anything
if ( containerWidth === self.containerWidth ) {
return;
}
// If we're hiding the layout with a fade,
if ( self.hideLayoutWithFade && self.supported ) {
// recaculate column and gutter values
self._setColumns();
self._setColumns( containerWidth );
// Layout the items with only a position
self._reLayout( false, true );
// Change the transition-delay value accordingly
self._setSequentialDelay( self.itemsOrderedByDelay );
self._setSequentialDelay( self.itemsOrderedByDelay || self.$items.get() );
self.fire('done');
self.$items.css('opacity', 1);
} else {
@ -595,9 +620,19 @@
},
_beforeResize : function() {
var self = this;
var self = this,
containerWidth;
if ( !self.supported ) {
// If shuffle is disabled, destroyed, don't do anything
if ( !self.hideLayoutWithFade || !self.supported || !self.enabled || self.destroyed ) {
return;
}
// Will need to check height in the future if it's layed out horizontaly
containerWidth = self.$container.width();
// containerWidth hasn't changed, don't do anything
if ( containerWidth === self.containerWidth ) {
return;
}
@ -621,7 +656,7 @@
/**
* Returns things like webkitTransition or boxSizing
* Returns things like `-webkit-transition` or `box-sizing` from `transition` or `boxSizing`, respectively
*
* @param {string} property to be prefixed.
* @return {string} the prefixed css property
@ -677,17 +712,17 @@
transform = 'translate(' + opts.x + 'px, ' + opts.y + 'px) scale(' + opts.scale + ', ' + opts.scale + ')';
}
if ( opts.x !== undefined ) {
self.setPrefixedCss(opts.$this, 'transform', transform);
}
if ( opts.opacity !== undefined && self.useTransition ) {
// Update css to trigger CSS Animation
opts.$this.css('opacity' , opts.opacity);
}
if ( opts.x !== undefined ) {
self.setPrefixedCss(opts.$this, 'transform', transform);
}
if ( self.useTransition ) {
opts.$this.one(self.transitionEndName, complete);
opts.$this.one(self.transitionend, complete);
} else {
complete();
}
@ -994,8 +1029,14 @@
// Not overrideable
$.fn.shuffle.settings = {
itemCss : { // default CSS for each item
position: 'absolute',
top: 0,
left: 0
},
revealAppendedDelay: 300,
enabled: true,
destroyed: false,
styleQueue: [],
supported: Modernizr.csstransforms && Modernizr.csstransitions, // supports transitions and transforms
prefixed: Modernizr.prefixed,

Loading…
Cancel
Save