Add `visibility: hidden` to concealed items so that overlapping hidden items cannot be clicked. Closes #21.

Other assorted improvements to event handling. Broke up `_transition` into smaller methods.
pull/56/head
Glen Cheney 10 years ago
parent eb272e80a6
commit 0842133655

@ -24,9 +24,6 @@ if (typeof Modernizr !== 'object') {
'http://vestride.github.io/Shuffle/#dependencies');
}
// Used for unique instance variables
var id = 0;
/**
* Returns css prefixed properties like `-webkit-transition` or `box-sizing`
@ -61,8 +58,13 @@ var CSS_TRANSFORM = dashify(TRANSFORM);
var CAN_TRANSITION_TRANSFORMS = Modernizr.csstransforms && Modernizr.csstransitions;
var HAS_TRANSFORMS_3D = Modernizr.csstransforms3d;
var SHUFFLE = 'shuffle';
// Configurable. You can change these constants to fit your application.
// The default scale and concealed scale, however, have to be different values.
var ALL_ITEMS = 'all';
var FILTER_ATTRIBUTE_KEY = 'groups';
var DEFAULT_SCALE = 1;
var CONCEALED_SCALE = 0.001;
// Underscore's throttle function.
@ -98,6 +100,9 @@ function throttle(func, wait, options) {
};
}
// Used for unique instance variables
var id = 0;
/**
* Categorize, sort, and filter a responsive grid of items.
@ -342,22 +347,18 @@ Shuffle.prototype = {
/**
* Set the initial css for each item
* @param {jQuery} [$items] Optionally specifiy at set to initialize
* @return {jQuery} The items which were just set
*/
_initItems : function( $items ) {
$items = $items || this.$items;
return $items.css( this.itemCss ).data('position', {x: 0, y: 0});
$items.css( this.itemCss ).data('position', {x: 0, y: 0});
},
_updateItemCount : function() {
this.visibleItems = this.$items.filter('.filtered').length;
return this;
},
_setTransition : function( element ) {
var self = this;
element.style[ TRANSITION ] = CSS_TRANSFORM + ' ' + self.speed + 'ms ' + self.easing + ', opacity ' + self.speed + 'ms ' + self.easing;
return element;
element.style[ TRANSITION ] = CSS_TRANSFORM + ' ' + this.speed + 'ms ' + this.easing + ', opacity ' + this.speed + 'ms ' + this.easing;
},
_setTransitions : function( $items ) {
@ -367,7 +368,6 @@ Shuffle.prototype = {
$items.each(function() {
self._setTransition( this );
});
return self;
},
_setSequentialDelay : function( $collection ) {
@ -378,13 +378,17 @@ Shuffle.prototype = {
}
// $collection can be an array of dom elements or jquery object
$.each( $collection, function(i) {
$.each($collection, function(i, el) {
// This works because the transition-property: transform, opacity;
this.style[ TRANSITION_DELAY ] = '0ms,' + ((i + 1) * self.sequentialFadeDelay) + 'ms';
el.style[ TRANSITION_DELAY ] = '0ms,' + ((i + 1) * self.sequentialFadeDelay) + 'ms';
// Set the delay back to zero after one transition
$(this).one(TRANSITIONEND, function() {
this.style[ TRANSITION_DELAY ] = '0ms';
$(el).on(TRANSITIONEND + '.' + self.unique, function(evt) {
var target = evt.currentTarget;
if ( target === evt.target ) {
target.style[ TRANSITION_DELAY ] = '0ms';
$(target).off(TRANSITIONEND + '.' + self.unique);
}
});
});
},
@ -418,8 +422,11 @@ Shuffle.prototype = {
// if there are inline styles.
if (includeMargins) {
var styles = $(element).css(['marginLeft', 'marginRight']);
var marginLeft = parseFloat(styles.marginLeft);
var marginRight = parseFloat(styles.marginRight);
// Defaults to zero if parsing fails because IE will return 'auto' when
// the element doesn't have margins instead of the computed style.
var marginLeft = parseFloat(styles.marginLeft) || 0;
var marginRight = parseFloat(styles.marginRight) || 0;
width += marginLeft + marginRight;
}
@ -438,8 +445,8 @@ Shuffle.prototype = {
if (includeMargins) {
var styles = $(element).css(['marginTop', 'marginBottom']);
var marginTop = parseFloat(styles.marginTop);
var marginBottom = parseFloat(styles.marginBottom);
var marginTop = parseFloat(styles.marginTop) || 0;
var marginBottom = parseFloat(styles.marginBottom) || 0;
height += marginTop + marginBottom;
}
@ -543,62 +550,25 @@ Shuffle.prototype = {
fn = fn || self._filterEnd;
$.each(items, function(index, item) {
var $item = $(item),
itemData = $item.data(),
itemWidth = self._getOuterWidth(item, true),
columnSpan = itemWidth / self.colWidth;
// If the difference between the rounded column span number and the
// calculated column span number is really small, round the number to
// make it fit.
if ( Math.abs(Math.round(columnSpan) - columnSpan) < 0.03 ) {
// e.g. columnSpan = 4.0089945390298745
columnSpan = Math.round( columnSpan );
}
var $item = $(item);
var itemData = $item.data();
var currPos = itemData.position;
var pos = self._getItemPosition( $item );
// How many columns does this brick span. Ensure it's not more than the
// amount of columns in the whole layout.
var colSpan = Math.min( Math.ceil(columnSpan), self.cols );
// The item spans only one column.
var currentPosition = itemData.position;
var position;
if ( colSpan === 1 ) {
position = self._placeItem( $item, self.colYs );
// The item spans more than one column, figure out how many different
// places it could fit horizontally
} else {
var groupCount = self.cols + 1 - colSpan,
groupY = [],
groupColY,
i;
// for each group potential horizontal position
for ( i = 0; i < groupCount; i++ ) {
// make an array of colY values for that one group
groupColY = self.colYs.slice( i, i + colSpan );
// and get the max value of the array
groupY[i] = Math.max.apply( Math, groupColY );
}
position = self._placeItem( $item, groupY );
}
var currentX = currentPosition.x;
var currentY = currentPosition.y;
// Save data for shrink
$item.data( 'position', pos );
// If the item will not change its position, do not add it to the render
// queue. Transitions don't fire when setting a property to the same value.
if ( position.x === currentX && position.y === currentY && itemData.scale === 1 ) {
if ( pos.x === currPos.x && pos.y === currPos.y && itemData.scale === DEFAULT_SCALE ) {
return;
}
var transitionObj = {
$this: $item,
x: position.x,
y: position.y,
scale: 1
$item: $item,
x: pos.x,
y: pos.y,
scale: DEFAULT_SCALE
};
if ( isOnlyPosition ) {
@ -641,7 +611,48 @@ Shuffle.prototype = {
}
},
// worker method that places brick in the columnSet with the the minY
_getItemPosition : function( $item ) {
var self = this;
var itemWidth = self._getOuterWidth( $item[0], true );
var columnSpan = itemWidth / self.colWidth;
// If the difference between the rounded column span number and the
// calculated column span number is really small, round the number to
// make it fit.
if ( Math.abs(Math.round(columnSpan) - columnSpan) < 0.03 ) {
// e.g. columnSpan = 4.0089945390298745
columnSpan = Math.round( columnSpan );
}
// How many columns does this item span. Ensure it's not more than the
// amount of columns in the whole layout.
var colSpan = Math.min( Math.ceil(columnSpan), self.cols );
// The item spans only one column.
if ( colSpan === 1 ) {
return self._placeItem( $item, self.colYs );
// The item spans more than one column, figure out how many different
// places it could fit horizontally
} else {
var groupCount = self.cols + 1 - colSpan,
groupY = [],
groupColY,
i;
// for each group potential horizontal position
for ( i = 0; i < groupCount; i++ ) {
// make an array of colY values for that one group
groupColY = self.colYs.slice( i, i + colSpan );
// and get the max value of the array
groupY[i] = Math.max.apply( Math, groupColY );
}
return self._placeItem( $item, groupY );
}
},
// worker method that places item in the columnSet with the the minY
_placeItem : function( $item, setY ) {
// get the minimum Y value from the columns
var self = this,
@ -664,9 +675,6 @@ Shuffle.prototype = {
y: Math.round( minimumY + self.offset.top )
};
// Save data for shrink
$item.data( 'position', position );
// Apply setHeight to necessary columns
var setHeight = minimumY + self._getOuterHeight( $item[0], true ),
setSpan = self.cols + 1 - len;
@ -698,13 +706,21 @@ Shuffle.prototype = {
$concealed.each(function() {
var $item = $(this);
var position = $item.data('position');
var itemData = $item.data();
var alreadyShrunk = itemData.scale === CONCEALED_SCALE;
// Continuing would add a transitionend event listener to the element, but
// that listener would execute because the transform and opacity would
// stay the same.
if ( alreadyShrunk ) {
return;
}
var transitionObj = {
$this: $item,
x: position.x,
y: position.y,
scale : 0.001,
$item: $item,
x: itemData.position.x,
y: itemData.position.y,
scale : CONCEALED_SCALE,
opacity: 0,
callback: fn
};
@ -741,7 +757,7 @@ Shuffle.prototype = {
* @return {string} A normalized string which can be used with the transform style.
* @private
*/
_getItemTransformString: function(x, y, scale) {
_getItemTransformString : function(x, y, scale) {
if ( HAS_TRANSFORMS_3D ) {
return 'translate3d(' + x + 'px, ' + y + 'px, 0) scale3d(' + scale + ', ' + scale + ', 1)';
} else {
@ -749,12 +765,34 @@ Shuffle.prototype = {
}
},
_getStylesForTransition : function( opts ) {
var styles = {
opacity: opts.opacity
};
if ( this.supported ) {
if ( opts.x !== undefined ) {
styles[ TRANSFORM ] = this._getItemTransformString( opts.x, opts.y, opts.scale );
}
} else {
styles.left = opts.x;
styles.top = opts.y;
}
// Show the item if its opacity will be 1.
if ( opts.opacity === 1 ) {
styles.visibility = 'visible';
}
return styles;
},
/**
* Transitions an item in the grid
*
* @param {Object} opts options
* @param {jQuery} opts.$this jQuery object representing the current item
* @param {jQuery} opts.$item jQuery object representing the current item
* @param {number} opts.x translate's x
* @param {number} opts.y translate's y
* @param {number} opts.scale amount to scale the item
@ -763,49 +801,49 @@ Shuffle.prototype = {
* @private
*/
_transition : function( opts ) {
var complete = $.proxy( this._handleItemAnimationEnd, this,
opts.callback || $.noop, opts.$this[0] );
opts.$item.data('scale', opts.scale);
opts.scale = opts.scale || 1;
opts.$this.data('scale', opts.scale);
var styles = this._getStylesForTransition( opts );
this._startItemAnimation( opts.$item, styles, opts.callback );
},
// Use CSS Transforms if we have them
if ( this.supported ) {
var styles = {};
if ( opts.x !== undefined ) {
styles[ TRANSFORM ] = this._getItemTransformString( opts.x, opts.y, opts.scale );
}
_startItemAnimation : function( $item, styles, callback ) {
var willBeVisible = styles.opacity === 1;
var complete = $.proxy( this._handleItemAnimationEnd, this,
callback || $.noop, $item[0], willBeVisible );
if ( opts.opacity !== undefined ) {
styles.opacity = opts.opacity;
}
// Use CSS Transforms if we have them
if ( this.supported ) {
opts.$this.css( styles );
$item.css( styles );
// Transitions are not set until shuffle has loaded to avoid the initial transition.
if ( this.initialized ) {
opts.$this.on( TRANSITIONEND, complete );
// Namespaced because the reveal appended function also wants to know
// about the transition end event.
$item.on( TRANSITIONEND + '.shuffleitem', complete );
} else {
complete();
}
// Use jQuery to animate left/top
} else {
opts.$this.stop( true ).animate({
left: opts.x,
top: opts.y,
opacity: opts.opacity
}, this.speed, 'swing', complete);
// jQuery cannot animate visibility, set it immediately.
if ( 'visibility' in styles ) {
$item.css('visibility', styles.visibility);
delete styles.visibility;
}
$item.stop( true ).animate( styles, this.speed, 'swing', complete );
}
},
_handleItemAnimationEnd : function( callback, item, evt ) {
_handleItemAnimationEnd : function( callback, item, willBeVisible, evt ) {
// Make sure this event handler has not bubbled up from a child.
if ( evt ) {
if ( evt.target === item ) {
$( item ).off( TRANSITIONEND );
$( item ).off( '.shuffleitem' );
} else {
return;
}
@ -819,6 +857,10 @@ Shuffle.prototype = {
callback.call( this );
this._shrinkList.length = 0;
}
if ( !willBeVisible ) {
item.style.visibility = 'hidden';
}
},
_processStyleQueue : function() {
@ -827,8 +869,8 @@ Shuffle.prototype = {
$.each(this.styleQueue, function(i, transitionObj) {
if ( transitionObj.skipTransition ) {
self._skipTransition( transitionObj.$this[0], function() {
self._transition( transitionObj );
self._skipTransition(transitionObj.$item[0], function() {
transitionObj.$item.css( self._getStylesForTransition( transitionObj ) );
});
} else {
self._transition( transitionObj );
@ -902,7 +944,7 @@ Shuffle.prototype = {
self._updateItemCount();
if ( animateIn ) {
self._layout( passed, null, true, true );
self._layout( passed, null, true );
if ( isSequential ) {
self._setSequentialDelay( $passed );
@ -920,8 +962,9 @@ Shuffle.prototype = {
setTimeout(function() {
$newFilteredItems.each(function(i, el) {
self._transition({
$this: $(el),
opacity: 1
$item: $(el),
opacity: 1,
scale: DEFAULT_SCALE
});
});
}, self.revealAppendedDelay);

File diff suppressed because one or more lines are too long

@ -30,9 +30,6 @@ if (typeof Modernizr !== 'object') {
'http://vestride.github.io/Shuffle/#dependencies');
}
// Used for unique instance variables
var id = 0;
/**
* Returns css prefixed properties like `-webkit-transition` or `box-sizing`
@ -67,8 +64,13 @@ var CSS_TRANSFORM = dashify(TRANSFORM);
var CAN_TRANSITION_TRANSFORMS = Modernizr.csstransforms && Modernizr.csstransitions;
var HAS_TRANSFORMS_3D = Modernizr.csstransforms3d;
var SHUFFLE = 'shuffle';
// Configurable. You can change these constants to fit your application.
// The default scale and concealed scale, however, have to be different values.
var ALL_ITEMS = 'all';
var FILTER_ATTRIBUTE_KEY = 'groups';
var DEFAULT_SCALE = 1;
var CONCEALED_SCALE = 0.001;
// Underscore's throttle function.
@ -104,6 +106,9 @@ function throttle(func, wait, options) {
};
}
// Used for unique instance variables
var id = 0;
/**
* Categorize, sort, and filter a responsive grid of items.
@ -348,22 +353,18 @@ Shuffle.prototype = {
/**
* Set the initial css for each item
* @param {jQuery} [$items] Optionally specifiy at set to initialize
* @return {jQuery} The items which were just set
*/
_initItems : function( $items ) {
$items = $items || this.$items;
return $items.css( this.itemCss ).data('position', {x: 0, y: 0});
$items.css( this.itemCss ).data('position', {x: 0, y: 0});
},
_updateItemCount : function() {
this.visibleItems = this.$items.filter('.filtered').length;
return this;
},
_setTransition : function( element ) {
var self = this;
element.style[ TRANSITION ] = CSS_TRANSFORM + ' ' + self.speed + 'ms ' + self.easing + ', opacity ' + self.speed + 'ms ' + self.easing;
return element;
element.style[ TRANSITION ] = CSS_TRANSFORM + ' ' + this.speed + 'ms ' + this.easing + ', opacity ' + this.speed + 'ms ' + this.easing;
},
_setTransitions : function( $items ) {
@ -373,7 +374,6 @@ Shuffle.prototype = {
$items.each(function() {
self._setTransition( this );
});
return self;
},
_setSequentialDelay : function( $collection ) {
@ -384,13 +384,17 @@ Shuffle.prototype = {
}
// $collection can be an array of dom elements or jquery object
$.each( $collection, function(i) {
$.each($collection, function(i, el) {
// This works because the transition-property: transform, opacity;
this.style[ TRANSITION_DELAY ] = '0ms,' + ((i + 1) * self.sequentialFadeDelay) + 'ms';
el.style[ TRANSITION_DELAY ] = '0ms,' + ((i + 1) * self.sequentialFadeDelay) + 'ms';
// Set the delay back to zero after one transition
$(this).one(TRANSITIONEND, function() {
this.style[ TRANSITION_DELAY ] = '0ms';
$(el).on(TRANSITIONEND + '.' + self.unique, function(evt) {
var target = evt.currentTarget;
if ( target === evt.target ) {
target.style[ TRANSITION_DELAY ] = '0ms';
$(target).off(TRANSITIONEND + '.' + self.unique);
}
});
});
},
@ -424,8 +428,11 @@ Shuffle.prototype = {
// if there are inline styles.
if (includeMargins) {
var styles = $(element).css(['marginLeft', 'marginRight']);
var marginLeft = parseFloat(styles.marginLeft);
var marginRight = parseFloat(styles.marginRight);
// Defaults to zero if parsing fails because IE will return 'auto' when
// the element doesn't have margins instead of the computed style.
var marginLeft = parseFloat(styles.marginLeft) || 0;
var marginRight = parseFloat(styles.marginRight) || 0;
width += marginLeft + marginRight;
}
@ -444,8 +451,8 @@ Shuffle.prototype = {
if (includeMargins) {
var styles = $(element).css(['marginTop', 'marginBottom']);
var marginTop = parseFloat(styles.marginTop);
var marginBottom = parseFloat(styles.marginBottom);
var marginTop = parseFloat(styles.marginTop) || 0;
var marginBottom = parseFloat(styles.marginBottom) || 0;
height += marginTop + marginBottom;
}
@ -549,62 +556,25 @@ Shuffle.prototype = {
fn = fn || self._filterEnd;
$.each(items, function(index, item) {
var $item = $(item),
itemData = $item.data(),
itemWidth = self._getOuterWidth(item, true),
columnSpan = itemWidth / self.colWidth;
// If the difference between the rounded column span number and the
// calculated column span number is really small, round the number to
// make it fit.
if ( Math.abs(Math.round(columnSpan) - columnSpan) < 0.03 ) {
// e.g. columnSpan = 4.0089945390298745
columnSpan = Math.round( columnSpan );
}
var $item = $(item);
var itemData = $item.data();
var currPos = itemData.position;
var pos = self._getItemPosition( $item );
// How many columns does this brick span. Ensure it's not more than the
// amount of columns in the whole layout.
var colSpan = Math.min( Math.ceil(columnSpan), self.cols );
// The item spans only one column.
var currentPosition = itemData.position;
var position;
if ( colSpan === 1 ) {
position = self._placeItem( $item, self.colYs );
// The item spans more than one column, figure out how many different
// places it could fit horizontally
} else {
var groupCount = self.cols + 1 - colSpan,
groupY = [],
groupColY,
i;
// for each group potential horizontal position
for ( i = 0; i < groupCount; i++ ) {
// make an array of colY values for that one group
groupColY = self.colYs.slice( i, i + colSpan );
// and get the max value of the array
groupY[i] = Math.max.apply( Math, groupColY );
}
position = self._placeItem( $item, groupY );
}
var currentX = currentPosition.x;
var currentY = currentPosition.y;
// Save data for shrink
$item.data( 'position', pos );
// If the item will not change its position, do not add it to the render
// queue. Transitions don't fire when setting a property to the same value.
if ( position.x === currentX && position.y === currentY && itemData.scale === 1 ) {
if ( pos.x === currPos.x && pos.y === currPos.y && itemData.scale === DEFAULT_SCALE ) {
return;
}
var transitionObj = {
$this: $item,
x: position.x,
y: position.y,
scale: 1
$item: $item,
x: pos.x,
y: pos.y,
scale: DEFAULT_SCALE
};
if ( isOnlyPosition ) {
@ -647,7 +617,48 @@ Shuffle.prototype = {
}
},
// worker method that places brick in the columnSet with the the minY
_getItemPosition : function( $item ) {
var self = this;
var itemWidth = self._getOuterWidth( $item[0], true );
var columnSpan = itemWidth / self.colWidth;
// If the difference between the rounded column span number and the
// calculated column span number is really small, round the number to
// make it fit.
if ( Math.abs(Math.round(columnSpan) - columnSpan) < 0.03 ) {
// e.g. columnSpan = 4.0089945390298745
columnSpan = Math.round( columnSpan );
}
// How many columns does this item span. Ensure it's not more than the
// amount of columns in the whole layout.
var colSpan = Math.min( Math.ceil(columnSpan), self.cols );
// The item spans only one column.
if ( colSpan === 1 ) {
return self._placeItem( $item, self.colYs );
// The item spans more than one column, figure out how many different
// places it could fit horizontally
} else {
var groupCount = self.cols + 1 - colSpan,
groupY = [],
groupColY,
i;
// for each group potential horizontal position
for ( i = 0; i < groupCount; i++ ) {
// make an array of colY values for that one group
groupColY = self.colYs.slice( i, i + colSpan );
// and get the max value of the array
groupY[i] = Math.max.apply( Math, groupColY );
}
return self._placeItem( $item, groupY );
}
},
// worker method that places item in the columnSet with the the minY
_placeItem : function( $item, setY ) {
// get the minimum Y value from the columns
var self = this,
@ -670,9 +681,6 @@ Shuffle.prototype = {
y: Math.round( minimumY + self.offset.top )
};
// Save data for shrink
$item.data( 'position', position );
// Apply setHeight to necessary columns
var setHeight = minimumY + self._getOuterHeight( $item[0], true ),
setSpan = self.cols + 1 - len;
@ -704,13 +712,21 @@ Shuffle.prototype = {
$concealed.each(function() {
var $item = $(this);
var position = $item.data('position');
var itemData = $item.data();
var alreadyShrunk = itemData.scale === CONCEALED_SCALE;
// Continuing would add a transitionend event listener to the element, but
// that listener would execute because the transform and opacity would
// stay the same.
if ( alreadyShrunk ) {
return;
}
var transitionObj = {
$this: $item,
x: position.x,
y: position.y,
scale : 0.001,
$item: $item,
x: itemData.position.x,
y: itemData.position.y,
scale : CONCEALED_SCALE,
opacity: 0,
callback: fn
};
@ -747,7 +763,7 @@ Shuffle.prototype = {
* @return {string} A normalized string which can be used with the transform style.
* @private
*/
_getItemTransformString: function(x, y, scale) {
_getItemTransformString : function(x, y, scale) {
if ( HAS_TRANSFORMS_3D ) {
return 'translate3d(' + x + 'px, ' + y + 'px, 0) scale3d(' + scale + ', ' + scale + ', 1)';
} else {
@ -755,12 +771,34 @@ Shuffle.prototype = {
}
},
_getStylesForTransition : function( opts ) {
var styles = {
opacity: opts.opacity
};
if ( this.supported ) {
if ( opts.x !== undefined ) {
styles[ TRANSFORM ] = this._getItemTransformString( opts.x, opts.y, opts.scale );
}
} else {
styles.left = opts.x;
styles.top = opts.y;
}
// Show the item if its opacity will be 1.
if ( opts.opacity === 1 ) {
styles.visibility = 'visible';
}
return styles;
},
/**
* Transitions an item in the grid
*
* @param {Object} opts options
* @param {jQuery} opts.$this jQuery object representing the current item
* @param {jQuery} opts.$item jQuery object representing the current item
* @param {number} opts.x translate's x
* @param {number} opts.y translate's y
* @param {number} opts.scale amount to scale the item
@ -769,49 +807,49 @@ Shuffle.prototype = {
* @private
*/
_transition : function( opts ) {
var complete = $.proxy( this._handleItemAnimationEnd, this,
opts.callback || $.noop, opts.$this[0] );
opts.$item.data('scale', opts.scale);
opts.scale = opts.scale || 1;
opts.$this.data('scale', opts.scale);
var styles = this._getStylesForTransition( opts );
this._startItemAnimation( opts.$item, styles, opts.callback );
},
// Use CSS Transforms if we have them
if ( this.supported ) {
var styles = {};
if ( opts.x !== undefined ) {
styles[ TRANSFORM ] = this._getItemTransformString( opts.x, opts.y, opts.scale );
}
_startItemAnimation : function( $item, styles, callback ) {
var willBeVisible = styles.opacity === 1;
var complete = $.proxy( this._handleItemAnimationEnd, this,
callback || $.noop, $item[0], willBeVisible );
if ( opts.opacity !== undefined ) {
styles.opacity = opts.opacity;
}
// Use CSS Transforms if we have them
if ( this.supported ) {
opts.$this.css( styles );
$item.css( styles );
// Transitions are not set until shuffle has loaded to avoid the initial transition.
if ( this.initialized ) {
opts.$this.on( TRANSITIONEND, complete );
// Namespaced because the reveal appended function also wants to know
// about the transition end event.
$item.on( TRANSITIONEND + '.shuffleitem', complete );
} else {
complete();
}
// Use jQuery to animate left/top
} else {
opts.$this.stop( true ).animate({
left: opts.x,
top: opts.y,
opacity: opts.opacity
}, this.speed, 'swing', complete);
// jQuery cannot animate visibility, set it immediately.
if ( 'visibility' in styles ) {
$item.css('visibility', styles.visibility);
delete styles.visibility;
}
$item.stop( true ).animate( styles, this.speed, 'swing', complete );
}
},
_handleItemAnimationEnd : function( callback, item, evt ) {
_handleItemAnimationEnd : function( callback, item, willBeVisible, evt ) {
// Make sure this event handler has not bubbled up from a child.
if ( evt ) {
if ( evt.target === item ) {
$( item ).off( TRANSITIONEND );
$( item ).off( '.shuffleitem' );
} else {
return;
}
@ -825,6 +863,10 @@ Shuffle.prototype = {
callback.call( this );
this._shrinkList.length = 0;
}
if ( !willBeVisible ) {
item.style.visibility = 'hidden';
}
},
_processStyleQueue : function() {
@ -833,8 +875,8 @@ Shuffle.prototype = {
$.each(this.styleQueue, function(i, transitionObj) {
if ( transitionObj.skipTransition ) {
self._skipTransition( transitionObj.$this[0], function() {
self._transition( transitionObj );
self._skipTransition(transitionObj.$item[0], function() {
transitionObj.$item.css( self._getStylesForTransition( transitionObj ) );
});
} else {
self._transition( transitionObj );
@ -908,7 +950,7 @@ Shuffle.prototype = {
self._updateItemCount();
if ( animateIn ) {
self._layout( passed, null, true, true );
self._layout( passed, null, true );
if ( isSequential ) {
self._setSequentialDelay( $passed );
@ -926,8 +968,9 @@ Shuffle.prototype = {
setTimeout(function() {
$newFilteredItems.each(function(i, el) {
self._transition({
$this: $(el),
opacity: 1
$item: $(el),
opacity: 1,
scale: DEFAULT_SCALE
});
});
}, self.revealAppendedDelay);

File diff suppressed because one or more lines are too long

@ -7,9 +7,6 @@ if (typeof Modernizr !== 'object') {
'http://vestride.github.io/Shuffle/#dependencies');
}
// Used for unique instance variables
var id = 0;
/**
* Returns css prefixed properties like `-webkit-transition` or `box-sizing`
@ -44,8 +41,13 @@ var CSS_TRANSFORM = dashify(TRANSFORM);
var CAN_TRANSITION_TRANSFORMS = Modernizr.csstransforms && Modernizr.csstransitions;
var HAS_TRANSFORMS_3D = Modernizr.csstransforms3d;
var SHUFFLE = 'shuffle';
// Configurable. You can change these constants to fit your application.
// The default scale and concealed scale, however, have to be different values.
var ALL_ITEMS = 'all';
var FILTER_ATTRIBUTE_KEY = 'groups';
var DEFAULT_SCALE = 1;
var CONCEALED_SCALE = 0.001;
// Underscore's throttle function.
@ -81,6 +83,9 @@ function throttle(func, wait, options) {
};
}
// Used for unique instance variables
var id = 0;
/**
* Categorize, sort, and filter a responsive grid of items.
@ -325,22 +330,18 @@ Shuffle.prototype = {
/**
* Set the initial css for each item
* @param {jQuery} [$items] Optionally specifiy at set to initialize
* @return {jQuery} The items which were just set
*/
_initItems : function( $items ) {
$items = $items || this.$items;
return $items.css( this.itemCss ).data('position', {x: 0, y: 0});
$items.css( this.itemCss ).data('position', {x: 0, y: 0});
},
_updateItemCount : function() {
this.visibleItems = this.$items.filter('.filtered').length;
return this;
},
_setTransition : function( element ) {
var self = this;
element.style[ TRANSITION ] = CSS_TRANSFORM + ' ' + self.speed + 'ms ' + self.easing + ', opacity ' + self.speed + 'ms ' + self.easing;
return element;
element.style[ TRANSITION ] = CSS_TRANSFORM + ' ' + this.speed + 'ms ' + this.easing + ', opacity ' + this.speed + 'ms ' + this.easing;
},
_setTransitions : function( $items ) {
@ -350,7 +351,6 @@ Shuffle.prototype = {
$items.each(function() {
self._setTransition( this );
});
return self;
},
_setSequentialDelay : function( $collection ) {
@ -361,13 +361,17 @@ Shuffle.prototype = {
}
// $collection can be an array of dom elements or jquery object
$.each( $collection, function(i) {
$.each($collection, function(i, el) {
// This works because the transition-property: transform, opacity;
this.style[ TRANSITION_DELAY ] = '0ms,' + ((i + 1) * self.sequentialFadeDelay) + 'ms';
el.style[ TRANSITION_DELAY ] = '0ms,' + ((i + 1) * self.sequentialFadeDelay) + 'ms';
// Set the delay back to zero after one transition
$(this).one(TRANSITIONEND, function() {
this.style[ TRANSITION_DELAY ] = '0ms';
$(el).on(TRANSITIONEND + '.' + self.unique, function(evt) {
var target = evt.currentTarget;
if ( target === evt.target ) {
target.style[ TRANSITION_DELAY ] = '0ms';
$(target).off(TRANSITIONEND + '.' + self.unique);
}
});
});
},
@ -401,8 +405,11 @@ Shuffle.prototype = {
// if there are inline styles.
if (includeMargins) {
var styles = $(element).css(['marginLeft', 'marginRight']);
var marginLeft = parseFloat(styles.marginLeft);
var marginRight = parseFloat(styles.marginRight);
// Defaults to zero if parsing fails because IE will return 'auto' when
// the element doesn't have margins instead of the computed style.
var marginLeft = parseFloat(styles.marginLeft) || 0;
var marginRight = parseFloat(styles.marginRight) || 0;
width += marginLeft + marginRight;
}
@ -421,8 +428,8 @@ Shuffle.prototype = {
if (includeMargins) {
var styles = $(element).css(['marginTop', 'marginBottom']);
var marginTop = parseFloat(styles.marginTop);
var marginBottom = parseFloat(styles.marginBottom);
var marginTop = parseFloat(styles.marginTop) || 0;
var marginBottom = parseFloat(styles.marginBottom) || 0;
height += marginTop + marginBottom;
}
@ -526,62 +533,25 @@ Shuffle.prototype = {
fn = fn || self._filterEnd;
$.each(items, function(index, item) {
var $item = $(item),
itemData = $item.data(),
itemWidth = self._getOuterWidth(item, true),
columnSpan = itemWidth / self.colWidth;
// If the difference between the rounded column span number and the
// calculated column span number is really small, round the number to
// make it fit.
if ( Math.abs(Math.round(columnSpan) - columnSpan) < 0.03 ) {
// e.g. columnSpan = 4.0089945390298745
columnSpan = Math.round( columnSpan );
}
var $item = $(item);
var itemData = $item.data();
var currPos = itemData.position;
var pos = self._getItemPosition( $item );
// How many columns does this brick span. Ensure it's not more than the
// amount of columns in the whole layout.
var colSpan = Math.min( Math.ceil(columnSpan), self.cols );
// The item spans only one column.
var currentPosition = itemData.position;
var position;
if ( colSpan === 1 ) {
position = self._placeItem( $item, self.colYs );
// The item spans more than one column, figure out how many different
// places it could fit horizontally
} else {
var groupCount = self.cols + 1 - colSpan,
groupY = [],
groupColY,
i;
// for each group potential horizontal position
for ( i = 0; i < groupCount; i++ ) {
// make an array of colY values for that one group
groupColY = self.colYs.slice( i, i + colSpan );
// and get the max value of the array
groupY[i] = Math.max.apply( Math, groupColY );
}
position = self._placeItem( $item, groupY );
}
var currentX = currentPosition.x;
var currentY = currentPosition.y;
// Save data for shrink
$item.data( 'position', pos );
// If the item will not change its position, do not add it to the render
// queue. Transitions don't fire when setting a property to the same value.
if ( position.x === currentX && position.y === currentY && itemData.scale === 1 ) {
if ( pos.x === currPos.x && pos.y === currPos.y && itemData.scale === DEFAULT_SCALE ) {
return;
}
var transitionObj = {
$this: $item,
x: position.x,
y: position.y,
scale: 1
$item: $item,
x: pos.x,
y: pos.y,
scale: DEFAULT_SCALE
};
if ( isOnlyPosition ) {
@ -624,7 +594,48 @@ Shuffle.prototype = {
}
},
// worker method that places brick in the columnSet with the the minY
_getItemPosition : function( $item ) {
var self = this;
var itemWidth = self._getOuterWidth( $item[0], true );
var columnSpan = itemWidth / self.colWidth;
// If the difference between the rounded column span number and the
// calculated column span number is really small, round the number to
// make it fit.
if ( Math.abs(Math.round(columnSpan) - columnSpan) < 0.03 ) {
// e.g. columnSpan = 4.0089945390298745
columnSpan = Math.round( columnSpan );
}
// How many columns does this item span. Ensure it's not more than the
// amount of columns in the whole layout.
var colSpan = Math.min( Math.ceil(columnSpan), self.cols );
// The item spans only one column.
if ( colSpan === 1 ) {
return self._placeItem( $item, self.colYs );
// The item spans more than one column, figure out how many different
// places it could fit horizontally
} else {
var groupCount = self.cols + 1 - colSpan,
groupY = [],
groupColY,
i;
// for each group potential horizontal position
for ( i = 0; i < groupCount; i++ ) {
// make an array of colY values for that one group
groupColY = self.colYs.slice( i, i + colSpan );
// and get the max value of the array
groupY[i] = Math.max.apply( Math, groupColY );
}
return self._placeItem( $item, groupY );
}
},
// worker method that places item in the columnSet with the the minY
_placeItem : function( $item, setY ) {
// get the minimum Y value from the columns
var self = this,
@ -647,9 +658,6 @@ Shuffle.prototype = {
y: Math.round( minimumY + self.offset.top )
};
// Save data for shrink
$item.data( 'position', position );
// Apply setHeight to necessary columns
var setHeight = minimumY + self._getOuterHeight( $item[0], true ),
setSpan = self.cols + 1 - len;
@ -681,13 +689,21 @@ Shuffle.prototype = {
$concealed.each(function() {
var $item = $(this);
var position = $item.data('position');
var itemData = $item.data();
var alreadyShrunk = itemData.scale === CONCEALED_SCALE;
// Continuing would add a transitionend event listener to the element, but
// that listener would execute because the transform and opacity would
// stay the same.
if ( alreadyShrunk ) {
return;
}
var transitionObj = {
$this: $item,
x: position.x,
y: position.y,
scale : 0.001,
$item: $item,
x: itemData.position.x,
y: itemData.position.y,
scale : CONCEALED_SCALE,
opacity: 0,
callback: fn
};
@ -724,7 +740,7 @@ Shuffle.prototype = {
* @return {string} A normalized string which can be used with the transform style.
* @private
*/
_getItemTransformString: function(x, y, scale) {
_getItemTransformString : function(x, y, scale) {
if ( HAS_TRANSFORMS_3D ) {
return 'translate3d(' + x + 'px, ' + y + 'px, 0) scale3d(' + scale + ', ' + scale + ', 1)';
} else {
@ -732,12 +748,34 @@ Shuffle.prototype = {
}
},
_getStylesForTransition : function( opts ) {
var styles = {
opacity: opts.opacity
};
if ( this.supported ) {
if ( opts.x !== undefined ) {
styles[ TRANSFORM ] = this._getItemTransformString( opts.x, opts.y, opts.scale );
}
} else {
styles.left = opts.x;
styles.top = opts.y;
}
// Show the item if its opacity will be 1.
if ( opts.opacity === 1 ) {
styles.visibility = 'visible';
}
return styles;
},
/**
* Transitions an item in the grid
*
* @param {Object} opts options
* @param {jQuery} opts.$this jQuery object representing the current item
* @param {jQuery} opts.$item jQuery object representing the current item
* @param {number} opts.x translate's x
* @param {number} opts.y translate's y
* @param {number} opts.scale amount to scale the item
@ -746,49 +784,49 @@ Shuffle.prototype = {
* @private
*/
_transition : function( opts ) {
var complete = $.proxy( this._handleItemAnimationEnd, this,
opts.callback || $.noop, opts.$this[0] );
opts.$item.data('scale', opts.scale);
opts.scale = opts.scale || 1;
opts.$this.data('scale', opts.scale);
var styles = this._getStylesForTransition( opts );
this._startItemAnimation( opts.$item, styles, opts.callback );
},
// Use CSS Transforms if we have them
if ( this.supported ) {
var styles = {};
if ( opts.x !== undefined ) {
styles[ TRANSFORM ] = this._getItemTransformString( opts.x, opts.y, opts.scale );
}
_startItemAnimation : function( $item, styles, callback ) {
var willBeVisible = styles.opacity === 1;
var complete = $.proxy( this._handleItemAnimationEnd, this,
callback || $.noop, $item[0], willBeVisible );
if ( opts.opacity !== undefined ) {
styles.opacity = opts.opacity;
}
// Use CSS Transforms if we have them
if ( this.supported ) {
opts.$this.css( styles );
$item.css( styles );
// Transitions are not set until shuffle has loaded to avoid the initial transition.
if ( this.initialized ) {
opts.$this.on( TRANSITIONEND, complete );
// Namespaced because the reveal appended function also wants to know
// about the transition end event.
$item.on( TRANSITIONEND + '.shuffleitem', complete );
} else {
complete();
}
// Use jQuery to animate left/top
} else {
opts.$this.stop( true ).animate({
left: opts.x,
top: opts.y,
opacity: opts.opacity
}, this.speed, 'swing', complete);
// jQuery cannot animate visibility, set it immediately.
if ( 'visibility' in styles ) {
$item.css('visibility', styles.visibility);
delete styles.visibility;
}
$item.stop( true ).animate( styles, this.speed, 'swing', complete );
}
},
_handleItemAnimationEnd : function( callback, item, evt ) {
_handleItemAnimationEnd : function( callback, item, willBeVisible, evt ) {
// Make sure this event handler has not bubbled up from a child.
if ( evt ) {
if ( evt.target === item ) {
$( item ).off( TRANSITIONEND );
$( item ).off( '.shuffleitem' );
} else {
return;
}
@ -802,6 +840,10 @@ Shuffle.prototype = {
callback.call( this );
this._shrinkList.length = 0;
}
if ( !willBeVisible ) {
item.style.visibility = 'hidden';
}
},
_processStyleQueue : function() {
@ -810,8 +852,8 @@ Shuffle.prototype = {
$.each(this.styleQueue, function(i, transitionObj) {
if ( transitionObj.skipTransition ) {
self._skipTransition( transitionObj.$this[0], function() {
self._transition( transitionObj );
self._skipTransition(transitionObj.$item[0], function() {
transitionObj.$item.css( self._getStylesForTransition( transitionObj ) );
});
} else {
self._transition( transitionObj );
@ -885,7 +927,7 @@ Shuffle.prototype = {
self._updateItemCount();
if ( animateIn ) {
self._layout( passed, null, true, true );
self._layout( passed, null, true );
if ( isSequential ) {
self._setSequentialDelay( $passed );
@ -903,8 +945,9 @@ Shuffle.prototype = {
setTimeout(function() {
$newFilteredItems.each(function(i, el) {
self._transition({
$this: $(el),
opacity: 1
$item: $(el),
opacity: 1,
scale: DEFAULT_SCALE
});
});
}, self.revealAppendedDelay);

@ -4,6 +4,7 @@
* TESTS!
* Horizontal layout
* More JSDoc
* Create an `Item` class for shuffle items so they can handle storing values and more.
## Things I don't like and would like to fix
* Move everything to Shuffle.prototype.method = function.

Loading…
Cancel
Save