You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1090 lines
37 KiB
JavaScript
1090 lines
37 KiB
JavaScript
// IMPORTANT!
|
|
// If you're already using Modernizr, delete it from this file. If you don't know what Modernizr is, leave it :)
|
|
|
|
/* Modernizr 2.6.2 (Custom Build) | MIT & BSD
|
|
* Build: http://modernizr.com/download/#-csstransforms-csstransforms3d-csstransitions-cssclasses-prefixed-teststyles-testprop-testallprops-prefixes-domprefixes
|
|
*/
|
|
;window.Modernizr=function(a,b,c){function z(a){j.cssText=a}function A(a,b){return z(m.join(a+";")+(b||""))}function B(a,b){return typeof a===b}function C(a,b){return!!~(""+a).indexOf(b)}function D(a,b){for(var d in a){var e=a[d];if(!C(e,"-")&&j[e]!==c)return b=="pfx"?e:!0}return!1}function E(a,b,d){for(var e in a){var f=b[a[e]];if(f!==c)return d===!1?a[e]:B(f,"function")?f.bind(d||b):f}return!1}function F(a,b,c){var d=a.charAt(0).toUpperCase()+a.slice(1),e=(a+" "+o.join(d+" ")+d).split(" ");return B(b,"string")||B(b,"undefined")?D(e,b):(e=(a+" "+p.join(d+" ")+d).split(" "),E(e,b,c))}var d="2.6.2",e={},f=!0,g=b.documentElement,h="modernizr",i=b.createElement(h),j=i.style,k,l={}.toString,m=" -webkit- -moz- -o- -ms- ".split(" "),n="Webkit Moz O ms",o=n.split(" "),p=n.toLowerCase().split(" "),q={},r={},s={},t=[],u=t.slice,v,w=function(a,c,d,e){var f,i,j,k,l=b.createElement("div"),m=b.body,n=m||b.createElement("body");if(parseInt(d,10))while(d--)j=b.createElement("div"),j.id=e?e[d]:h+(d+1),l.appendChild(j);return f=["­",'<style id="s',h,'">',a,"</style>"].join(""),l.id=h,(m?l:n).innerHTML+=f,n.appendChild(l),m||(n.style.background="",n.style.overflow="hidden",k=g.style.overflow,g.style.overflow="hidden",g.appendChild(n)),i=c(l,a),m?l.parentNode.removeChild(l):(n.parentNode.removeChild(n),g.style.overflow=k),!!i},x={}.hasOwnProperty,y;!B(x,"undefined")&&!B(x.call,"undefined")?y=function(a,b){return x.call(a,b)}:y=function(a,b){return b in a&&B(a.constructor.prototype[b],"undefined")},Function.prototype.bind||(Function.prototype.bind=function(b){var c=this;if(typeof c!="function")throw new TypeError;var d=u.call(arguments,1),e=function(){if(this instanceof e){var a=function(){};a.prototype=c.prototype;var f=new a,g=c.apply(f,d.concat(u.call(arguments)));return Object(g)===g?g:f}return c.apply(b,d.concat(u.call(arguments)))};return e}),q.csstransforms=function(){return!!F("transform")},q.csstransforms3d=function(){var a=!!F("perspective");return a&&"webkitPerspective"in g.style&&w("@media (transform-3d),(-webkit-transform-3d){#modernizr{left:9px;position:absolute;height:3px;}}",function(b,c){a=b.offsetLeft===9&&b.offsetHeight===3}),a},q.csstransitions=function(){return F("transition")};for(var G in q)y(q,G)&&(v=G.toLowerCase(),e[v]=q[G](),t.push((e[v]?"":"no-")+v));return e.addTest=function(a,b){if(typeof a=="object")for(var d in a)y(a,d)&&e.addTest(d,a[d]);else{a=a.toLowerCase();if(e[a]!==c)return e;b=typeof b=="function"?b():b,typeof f!="undefined"&&f&&(g.className+=" "+(b?"":"no-")+a),e[a]=b}return e},z(""),i=k=null,e._version=d,e._prefixes=m,e._domPrefixes=p,e._cssomPrefixes=o,e.testProp=function(a){return D([a])},e.testAllProps=F,e.testStyles=w,e.prefixed=function(a,b,c){return b?F(a,b,c):F(a,"pfx")},g.className=g.className.replace(/(^|\s)no-js(\s|$)/,"$1$2")+(f?" js "+t.join(" "):""),e}(this,this.document);
|
|
|
|
|
|
// Shuffle Doesn't require this shuffle/debounce plugin, but it works better with it.
|
|
|
|
/*
|
|
* jQuery throttle / debounce - v1.1 - 3/7/2010
|
|
* http://benalman.com/projects/jquery-throttle-debounce-plugin/
|
|
*
|
|
* Copyright (c) 2010 "Cowboy" Ben Alman
|
|
* Dual licensed under the MIT and GPL licenses.
|
|
* http://benalman.com/about/license/
|
|
*/
|
|
(function(b,c){var $=b.jQuery||b.Cowboy||(b.Cowboy={}),a;$.throttle=a=function(e,f,j,i){var h,d=0;if(typeof f!=="boolean"){i=j;j=f;f=c}function g(){var o=this,m=+new Date()-d,n=arguments;function l(){d=+new Date();j.apply(o,n)}function k(){h=c}if(i&&!h){l()}h&&clearTimeout(h);if(i===c&&m>e){l()}else{if(f!==true){h=setTimeout(i?k:l,i===c?e-m:e)}}}if($.guid){g.guid=j.guid=j.guid||$.guid++}return g};$.debounce=function(d,e,f){return f===c?a(d,e,false):a(d,f,e!==false)}})(this);
|
|
|
|
/*!
|
|
* jQuery Shuffle Plugin
|
|
* Uses CSS Transforms to filter down a grid of items.
|
|
* Dependencies: jQuery 1.9+, Modernizr 2.6.2. Optionally throttle/debounce by Ben Alman
|
|
* Inspired by Isotope http://isotope.metafizzy.co/
|
|
* Licensed under the MIT license.
|
|
* @author Glen Cheney (http://glencheney.com)
|
|
* @version 1.6.6
|
|
* @date 06/28/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.
|
|
$.fn.sorted = function(options) {
|
|
var opts = $.extend({}, $.fn.sorted.defaults, options),
|
|
arr = this.get(),
|
|
revert = false;
|
|
|
|
if ( !arr.length ) {
|
|
return [];
|
|
}
|
|
|
|
if ( opts.randomize ) {
|
|
return $.fn.sorted.randomize( arr );
|
|
}
|
|
|
|
// Sort the elements by the opts.by function.
|
|
// If we don't have opts.by, default to DOM order
|
|
if (opts.by !== $.noop && opts.by !== null && opts.by !== undefined) {
|
|
arr.sort(function(a, b) {
|
|
|
|
// Exit early if we already know we want to revert
|
|
if ( revert ) {
|
|
return 0;
|
|
}
|
|
|
|
var valA = opts.by($(a)),
|
|
valB = opts.by($(b));
|
|
|
|
// If both values are undefined, use the DOM order
|
|
if ( valA === undefined && valB === undefined ) {
|
|
revert = true;
|
|
return 0;
|
|
}
|
|
|
|
if ( valA === 'sortFirst' || valB === 'sortLast' ) {
|
|
return -1;
|
|
}
|
|
|
|
if ( valA === 'sortLast' || valB === 'sortFirst' ) {
|
|
return 1;
|
|
}
|
|
|
|
return (valA < valB) ? -1 :
|
|
(valA > valB) ? 1 : 0;
|
|
});
|
|
}
|
|
|
|
// Revert to the original array if necessary
|
|
if ( revert ) {
|
|
return this.get();
|
|
}
|
|
|
|
if ( opts.reverse ) {
|
|
arr.reverse();
|
|
}
|
|
|
|
return arr;
|
|
|
|
};
|
|
|
|
$.fn.sorted.defaults = {
|
|
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
|
|
};
|
|
|
|
|
|
// http://stackoverflow.com/a/962890/373422
|
|
$.fn.sorted.randomize = function( array ) {
|
|
var top = array.length,
|
|
tmp, current;
|
|
|
|
if ( !top ) {
|
|
return array;
|
|
}
|
|
|
|
while ( --top ) {
|
|
current = Math.floor( Math.random() * (top + 1) );
|
|
tmp = array[ current ];
|
|
array[ current ] = array[ top ];
|
|
array[ top ] = tmp;
|
|
}
|
|
|
|
return array;
|
|
};
|
|
|
|
// Used for unique instance variables
|
|
var id = 0;
|
|
|
|
var Shuffle = function( $container, options ) {
|
|
var self = this;
|
|
$.extend( self, Shuffle.options, options, Shuffle.settings );
|
|
|
|
self.$container = $container;
|
|
self.$window = $(window);
|
|
self.unique = 'shuffle_' + id++;
|
|
|
|
self._fire('loading');
|
|
self._init();
|
|
self._fire('done');
|
|
};
|
|
|
|
Shuffle.prototype = {
|
|
|
|
constructor: Shuffle,
|
|
|
|
_init : function() {
|
|
var self = this,
|
|
containerCSS,
|
|
resizeFunction = $.proxy( self._onResize, self ),
|
|
debouncedResize = self.throttle ? self.throttle( self.throttleTime, resizeFunction ) : resizeFunction;
|
|
|
|
// Save variables needed later then add some classes
|
|
self._setVars();
|
|
|
|
// Zero out all columns
|
|
self._resetCols();
|
|
|
|
// Add classes and invalidate styles
|
|
self._addClasses();
|
|
|
|
// Set initial css for each item
|
|
self._initItems();
|
|
|
|
// Bind resize events (http://stackoverflow.com/questions/1852751/window-resize-event-firing-in-internet-explorer)
|
|
self.$window.on('resize.shuffle.' + self.unique, debouncedResize);
|
|
|
|
// Get container css all in one request
|
|
containerCSS = self.$container.css(['paddingLeft', 'paddingRight', 'position', 'width']);
|
|
|
|
// Position cannot be static.
|
|
// This will cause an extra style layout if it has to be set and the sizer element is used.
|
|
if ( containerCSS.position === 'static' ) {
|
|
self.$container.css('position', 'relative');
|
|
}
|
|
|
|
// Get offset from container
|
|
self.offset = {
|
|
left: parseInt( containerCSS.paddingLeft, 10 ) || 0,
|
|
top: parseInt( containerCSS.paddingTop, 10 ) || 0
|
|
};
|
|
|
|
// We already got the container's width above, no need to cause another reflow getting it again...
|
|
// Calculate the number of columns there will be
|
|
self._setColumns( parseInt( containerCSS.width, 10 ) );
|
|
|
|
// Kick off!
|
|
self.shuffle( self.group );
|
|
|
|
// The shuffle items haven't had transitions set on them yet
|
|
// so the user doesn't see the first layout. Set them now that the first layout is done.
|
|
if ( self.supported ) {
|
|
setTimeout(function() {
|
|
self._setTransitions();
|
|
self.$container[0].style[ self.transitionName ] = 'height ' + self.speed + 'ms ' + self.easing;
|
|
}, 0);
|
|
}
|
|
},
|
|
|
|
// Will invalidate styles
|
|
_addClasses : function() {
|
|
var self = this;
|
|
|
|
self.$container.addClass('shuffle');
|
|
self.$items.addClass('shuffle-item filtered');
|
|
|
|
return self;
|
|
},
|
|
|
|
_setVars : function() {
|
|
var self = this;
|
|
|
|
self.$items = self._getItems();
|
|
self.transitionName = self.prefixed('transition'),
|
|
self.transitionDelayName = self.prefixed('transitionDelay');
|
|
self.transitionDuration = self.prefixed('transitionDuration');
|
|
self.transform = self.getPrefixed('transform');
|
|
|
|
// Get transitionend event name
|
|
self.transitionend = {
|
|
'WebkitTransition' : 'webkitTransitionEnd',
|
|
'MozTransition' : 'transitionend',
|
|
'OTransition' : 'oTransitionEnd',
|
|
'msTransition' : 'MSTransitionEnd',
|
|
'transition' : 'transitionend'
|
|
}[ self.transitionName ];
|
|
|
|
// If the columnWidth property is a function, then the grid is fluid
|
|
self.isFluid = self.columnWidth && typeof self.columnWidth === 'function';
|
|
|
|
// Column width is the default setting and sizer is not (meaning passed in)
|
|
// Assume they meant column width to be the sizer
|
|
if ( self.columnWidth === 0 && self.sizer !== null ) {
|
|
self.columnWidth = self.sizer;
|
|
}
|
|
|
|
// If column width is a string, treat is as a selector and search for the
|
|
// sizer element within the outermost container
|
|
if ( typeof self.columnWidth === 'string' ) {
|
|
self.$sizer = self.$container.find( self.columnWidth );
|
|
|
|
// Check for an element
|
|
} else if ( self.columnWidth && self.columnWidth.nodeType && self.columnWidth.nodeType === 1 ) {
|
|
// Wrap it in jQuery
|
|
self.$sizer = $( self.columnWidth );
|
|
|
|
// Check for jQuery object
|
|
} else if ( self.columnWidth && self.columnWidth.jquery ) {
|
|
self.$sizer = self.columnWidth;
|
|
}
|
|
|
|
if ( self.$sizer && self.$sizer.length ) {
|
|
self.useSizer = true;
|
|
self.sizer = self.$sizer[0];
|
|
}
|
|
|
|
return self;
|
|
},
|
|
|
|
_filter : function( category, $collection ) {
|
|
var self = this,
|
|
isPartialSet = $collection !== undefined,
|
|
$items = isPartialSet ? $collection : self.$items,
|
|
$filtered = $();
|
|
|
|
category = category || self.lastFilter;
|
|
|
|
self._fire('filter');
|
|
|
|
// Loop through each item and use provided function to determine
|
|
// whether to hide it or not.
|
|
if ( $.isFunction(category) ) {
|
|
$items.each(function() {
|
|
var $item = $(this),
|
|
passes = category.call($item[0], $item, self);
|
|
|
|
if ( passes ) {
|
|
$filtered = $filtered.add( $item );
|
|
}
|
|
});
|
|
}
|
|
|
|
// Otherwise we've been passed a category to filter by
|
|
else {
|
|
self.group = category;
|
|
if (category !== 'all') {
|
|
$items.each(function() {
|
|
var $this = $(this),
|
|
groups = $this.data('groups'),
|
|
keys = self.delimeter && !$.isArray( groups ) ? groups.split( self.delimeter ) : groups,
|
|
passes = $.inArray(category, keys) > -1;
|
|
|
|
if ( passes ) {
|
|
$filtered = $filtered.add( $this );
|
|
}
|
|
});
|
|
}
|
|
|
|
// category === 'all', add filtered class to everything
|
|
else {
|
|
$filtered = $items;
|
|
}
|
|
}
|
|
|
|
// Individually add/remove concealed/filtered classes
|
|
self._toggleFilterClasses( $items, $filtered );
|
|
|
|
$items = null;
|
|
$collection = null;
|
|
|
|
return $filtered;
|
|
},
|
|
|
|
|
|
_toggleFilterClasses : function( $items, $filtered ) {
|
|
var concealed = 'concealed',
|
|
filtered = 'filtered';
|
|
|
|
$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 );
|
|
}
|
|
});
|
|
},
|
|
|
|
/**
|
|
* 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 );
|
|
},
|
|
|
|
_updateItemCount : function() {
|
|
this.visibleItems = this.$items.filter('.filtered').length;
|
|
return this;
|
|
},
|
|
|
|
_setTransition : function( element ) {
|
|
var self = this;
|
|
element.style[ self.transitionName ] = self.transform + ' ' + self.speed + 'ms ' + self.easing + ', opacity ' + self.speed + 'ms ' + self.easing;
|
|
return element;
|
|
},
|
|
|
|
_setTransitions : function( $items ) {
|
|
var self = this;
|
|
|
|
$items = $items || self.$items;
|
|
$items.each(function() {
|
|
self._setTransition( this );
|
|
});
|
|
return self;
|
|
},
|
|
|
|
_setSequentialDelay : function( $collection ) {
|
|
var self = this;
|
|
|
|
if ( !self.supported ) {
|
|
return;
|
|
}
|
|
|
|
// $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.transitionDelayName ] = '0ms,' + ((i + 1) * self.sequentialFadeDelay) + 'ms';
|
|
|
|
// Set the delay back to zero after one transition
|
|
$(this).one(self.transitionend, function() {
|
|
this.style[ self.transitionDelayName ] = '0ms';
|
|
});
|
|
});
|
|
},
|
|
|
|
_getItems : function() {
|
|
return this.$container.children( this.itemSelector );
|
|
},
|
|
|
|
_getPreciseDimension : function( element, style ) {
|
|
var dimension;
|
|
if ( window.getComputedStyle ) {
|
|
dimension = window.getComputedStyle( element, null )[ style ];
|
|
} else {
|
|
dimension = $( element ).css( style );
|
|
}
|
|
return parseFloat( dimension );
|
|
},
|
|
|
|
// Calculate number of columns
|
|
// Gets css if using sizer element
|
|
_setColumns : function( theContainerWidth ) {
|
|
var self = this,
|
|
containerWidth = theContainerWidth || self.$container.width(),
|
|
gutter = typeof self.gutterWidth === 'function' ?
|
|
self.gutterWidth( containerWidth ) :
|
|
self.useSizer ?
|
|
self._getPreciseDimension( self.sizer, 'marginLeft' ) :
|
|
self.gutterWidth,
|
|
calculatedColumns;
|
|
|
|
// use fluid columnWidth function if there
|
|
self.colWidth = self.isFluid ? self.columnWidth( containerWidth ) :
|
|
// columnWidth option isn't a function, are they using a sizing element?
|
|
self.useSizer ? self._getPreciseDimension( self.sizer, 'width' ) :
|
|
// if not, how about the explicitly set option?
|
|
self.columnWidth ||
|
|
// or use the size of the first item
|
|
self.$items.outerWidth(true) ||
|
|
// if there's no items, use size of container
|
|
containerWidth;
|
|
|
|
// Don't let them set a column width of zero.
|
|
self.colWidth = self.colWidth || containerWidth;
|
|
|
|
self.colWidth += gutter;
|
|
|
|
calculatedColumns = (containerWidth + gutter) / self.colWidth;
|
|
// Widths given from getComputedStyle are not precise enough...
|
|
if ( Math.ceil(calculatedColumns) - calculatedColumns < 0.01 ) {
|
|
// e.g. calculatedColumns = 11.998876
|
|
calculatedColumns = Math.ceil( calculatedColumns );
|
|
}
|
|
self.cols = Math.floor( calculatedColumns );
|
|
self.cols = Math.max( self.cols, 1 );
|
|
|
|
// This can happen when .shuffle is called on something hidden (e.g. display:none for tabs)
|
|
if ( !self.colWidth || isNaN( self.cols ) || !containerWidth ) {
|
|
self.needsUpdate = true;
|
|
} else {
|
|
self.needsUpdate = false;
|
|
}
|
|
|
|
self.containerWidth = containerWidth;
|
|
},
|
|
|
|
/**
|
|
* Adjust the height of the grid
|
|
*/
|
|
_setContainerSize : function() {
|
|
var self = this,
|
|
gridHeight = Math.max.apply( Math, self.colYs );
|
|
self.$container.css( 'height', gridHeight + 'px' );
|
|
},
|
|
|
|
/**
|
|
* Fire events with .shuffle namespace
|
|
*/
|
|
_fire : function( name, args ) {
|
|
this.$container.trigger( name + '.shuffle', args && args.length ? args : [ this ] );
|
|
},
|
|
|
|
|
|
/**
|
|
* Loops through each item that should be shown
|
|
* Calculates the x and y position and then transitions it
|
|
* @param {array} items - array of items that will be shown/layed out in order in their array.
|
|
* Because jQuery collection are always ordered in DOM order, we can't pass a jq collection
|
|
* @param {function} complete callback function
|
|
* @param {boolean} isOnlyPosition set this to true to only trigger positioning of the items
|
|
*/
|
|
_layout: function( items, fn, isOnlyPosition, isHide ) {
|
|
var self = this;
|
|
|
|
fn = fn || self.filterEnd;
|
|
|
|
self.layoutTransitionEnded = false;
|
|
$.each(items, function(index) {
|
|
var $this = $(items[index]),
|
|
//how many columns does this brick span
|
|
colSpan = Math.ceil( $this.outerWidth(true) / self.colWidth );
|
|
colSpan = Math.min( colSpan, self.cols );
|
|
|
|
if ( colSpan === 1 ) {
|
|
// if brick spans only one column, just like singleMode
|
|
self._placeItem( $this, self.colYs, fn, isOnlyPosition, isHide );
|
|
} else {
|
|
// brick spans more than one column
|
|
// how many different places could this brick fit horizontally
|
|
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 );
|
|
}
|
|
|
|
self._placeItem( $this, groupY, fn, isOnlyPosition, isHide );
|
|
}
|
|
});
|
|
|
|
// `_layout` always happens after `_shrink`, so it's safe to process the style
|
|
// queue here with styles from the shrink method
|
|
self._processStyleQueue();
|
|
|
|
// Adjust the height of the container
|
|
self._setContainerSize();
|
|
},
|
|
|
|
_resetCols : function() {
|
|
// reset columns
|
|
var i = this.cols;
|
|
this.colYs = [];
|
|
while (i--) {
|
|
this.colYs.push( 0 );
|
|
}
|
|
},
|
|
|
|
_reLayout : function( callback, isOnlyPosition ) {
|
|
var self = this;
|
|
callback = callback || self.filterEnd;
|
|
self._resetCols();
|
|
|
|
// If we've already sorted the elements, keep them sorted
|
|
if ( self.keepSorted && self.lastSort ) {
|
|
self.sort( self.lastSort, true, isOnlyPosition );
|
|
} else {
|
|
self._layout( self.$items.filter('.filtered').get(), self.filterEnd, isOnlyPosition );
|
|
}
|
|
},
|
|
|
|
// worker method that places brick in the columnSet with the the minY
|
|
_placeItem : function( $item, setY, callback, isOnlyPosition, isHide ) {
|
|
// get the minimum Y value from the columns
|
|
var self = this,
|
|
minimumY = Math.min.apply( Math, setY ),
|
|
shortCol = 0;
|
|
|
|
// Find index of short column, the first from the left where this item will go
|
|
// if ( setY[i] === minimumY ) requires items' height to be exact every time.
|
|
// The buffer value is very useful when the height is a percentage of the width
|
|
for (var i = 0, len = setY.length; i < len; i++) {
|
|
if ( setY[i] >= minimumY - self.buffer && setY[i] <= minimumY + self.buffer ) {
|
|
shortCol = i;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Position the item
|
|
var x = self.colWidth * shortCol,
|
|
y = minimumY;
|
|
x = Math.round( x + self.offset.left );
|
|
y = Math.round( y + self.offset.top );
|
|
|
|
// Save data for shrink
|
|
$item.data( {x: x, y: y} );
|
|
|
|
// Apply setHeight to necessary columns
|
|
var setHeight = minimumY + $item.outerHeight(true),
|
|
setSpan = self.cols + 1 - len;
|
|
for ( i = 0; i < setSpan; i++ ) {
|
|
self.colYs[ shortCol + i ] = setHeight;
|
|
}
|
|
|
|
var transitionObj = {
|
|
from: 'layout',
|
|
$this: $item,
|
|
x: x,
|
|
y: y,
|
|
scale: 1
|
|
};
|
|
|
|
if ( !isOnlyPosition ) {
|
|
transitionObj.opacity = 1;
|
|
transitionObj.callback = callback;
|
|
} else {
|
|
transitionObj.skipTransition = true;
|
|
}
|
|
|
|
if ( isHide ) {
|
|
transitionObj.opacity = 0;
|
|
}
|
|
|
|
self.styleQueue.push( transitionObj );
|
|
},
|
|
|
|
/**
|
|
* Hides the elements that don't match our filter
|
|
*/
|
|
_shrink : function( $collection, fn ) {
|
|
var self = this,
|
|
$concealed = $collection || self.$items.filter('.concealed'),
|
|
transitionObj = {},
|
|
callback = fn || self.shrinkEnd;
|
|
|
|
// Abort if no items
|
|
if ($concealed.length === 0) {
|
|
return;
|
|
}
|
|
|
|
self._fire('shrink');
|
|
|
|
self.shrinkTransitionEnded = false;
|
|
$concealed.each(function() {
|
|
var $this = $(this),
|
|
data = $this.data();
|
|
|
|
transitionObj = {
|
|
from: 'shrink',
|
|
$this: $this,
|
|
x: data.x,
|
|
y: data.y,
|
|
scale : 0.001,
|
|
opacity: 0,
|
|
callback: callback
|
|
};
|
|
|
|
self.styleQueue.push( transitionObj );
|
|
});
|
|
},
|
|
|
|
_onResize : function() {
|
|
var self = this,
|
|
containerWidth;
|
|
|
|
// If shuffle is disabled, destroyed, don't do anything
|
|
if ( !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;
|
|
}
|
|
|
|
self.resized();
|
|
},
|
|
|
|
|
|
/**
|
|
* Uses Modernizr's prefixed() to get the correct vendor property name and sets it using jQuery .css()
|
|
*
|
|
* @param {jq} $el the jquery object to set the css on
|
|
* @param {string} prop the property to set (e.g. 'transition')
|
|
* @param {string} value the value of the prop
|
|
*/
|
|
setPrefixedCss : function( $el, prop, value ) {
|
|
$el.css( this.prefixed( prop ), value );
|
|
},
|
|
|
|
|
|
/**
|
|
* 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
|
|
*/
|
|
getPrefixed : function( prop ) {
|
|
var styleName = this.prefixed( prop );
|
|
return styleName ? styleName.replace(/([A-Z])/g, function(str,m1){ return '-' + m1.toLowerCase(); }).replace(/^ms-/,'-ms-') : styleName;
|
|
},
|
|
|
|
/**
|
|
* Transitions an item in the grid
|
|
*
|
|
* @param {object} opts options
|
|
* @param {jQuery} opts.$this jQuery object representing the current item
|
|
* @param {int} opts.x translate's x
|
|
* @param {int} opts.y translate's y
|
|
* @param {String} opts.left left position (used when no transforms available)
|
|
* @param {String} opts.top top position (used when no transforms available)
|
|
* @param {float} opts.scale amount to scale the item
|
|
* @param {float} opts.opacity opacity of the item
|
|
* @param {String} opts.height the height of the item (used when no transforms available)
|
|
* @param {String} opts.width the width of the item (used when no transforms available)
|
|
* @param {function} opts.callback complete function for the animation
|
|
*/
|
|
transition: function(opts) {
|
|
var self = this,
|
|
transform,
|
|
// Only fire callback once per collection's transition
|
|
complete = function() {
|
|
if ( !self.layoutTransitionEnded && opts.from === 'layout' ) {
|
|
self._fire('layout');
|
|
opts.callback.call( self );
|
|
self.layoutTransitionEnded = true;
|
|
} else if ( !self.shrinkTransitionEnded && opts.from === 'shrink' ) {
|
|
opts.callback.call( self );
|
|
self.shrinkTransitionEnded = true;
|
|
}
|
|
};
|
|
|
|
opts.callback = opts.callback || $.noop;
|
|
|
|
// Use CSS Transforms if we have them
|
|
if ( self.supported ) {
|
|
|
|
// Make scale one if it's not preset
|
|
if ( opts.scale === undefined ) {
|
|
opts.scale = 1;
|
|
}
|
|
|
|
if (self.threeD) {
|
|
transform = 'translate3d(' + opts.x + 'px, ' + opts.y + 'px, 0) scale3d(' + opts.scale + ', ' + opts.scale + ', 1)';
|
|
} else {
|
|
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 ) {
|
|
// Update css to trigger CSS Animation
|
|
opts.$this.css('opacity' , opts.opacity);
|
|
}
|
|
|
|
opts.$this.one(self.transitionend, complete);
|
|
} else {
|
|
|
|
var cssObj = {
|
|
left: opts.x,
|
|
top: opts.y,
|
|
opacity: opts.opacity
|
|
};
|
|
|
|
// Use jQuery to animate left/top
|
|
opts.$this.stop( true ).animate( cssObj, self.speed, 'swing', complete);
|
|
}
|
|
},
|
|
|
|
_processStyleQueue : function() {
|
|
var self = this,
|
|
queue = self.styleQueue;
|
|
|
|
$.each( queue, function(i, transitionObj) {
|
|
|
|
if ( transitionObj.skipTransition ) {
|
|
self._skipTransition( transitionObj.$this[0], function() {
|
|
self.transition( transitionObj );
|
|
});
|
|
} else {
|
|
self.transition( transitionObj );
|
|
}
|
|
});
|
|
|
|
// Remove everything in the style queue
|
|
self.styleQueue.length = 0;
|
|
},
|
|
|
|
shrinkEnd: function() {
|
|
this._fire('shrunk');
|
|
},
|
|
|
|
filterEnd: function() {
|
|
this._fire('filtered');
|
|
},
|
|
|
|
sortEnd: function() {
|
|
this._fire('sorted');
|
|
},
|
|
|
|
/**
|
|
* Change a property or execute a function which will not have a transition
|
|
* @param {Element} element DOM element that won't be transitioned
|
|
* @param {string|function} property the new style property which will be set or a function which will be called
|
|
* @param {string} [value] the value that `property` should be.
|
|
*/
|
|
_skipTransition : function( element, property, value ) {
|
|
var self = this,
|
|
reflow,
|
|
durationName = self.transitionDuration,
|
|
duration = element.style[ durationName ];
|
|
|
|
// Set the duration to zero so it happens immediately
|
|
element.style[ durationName ] = '0ms'; // ms needed for firefox!
|
|
|
|
if ( $.isFunction( property ) ) {
|
|
property();
|
|
} else {
|
|
element.style[ property ] = value;
|
|
}
|
|
|
|
// Force reflow
|
|
reflow = element.offsetWidth;
|
|
|
|
// Put the duration back
|
|
element.style[ durationName ] = duration;
|
|
},
|
|
|
|
_addItems : function( $newItems, animateIn, isSequential ) {
|
|
var self = this,
|
|
$passed,
|
|
passed;
|
|
|
|
if ( !self.supported ) {
|
|
animateIn = false;
|
|
}
|
|
|
|
$newItems.addClass('shuffle-item');
|
|
self._initItems( $newItems );
|
|
self._setTransitions( $newItems );
|
|
self.$items = self._getItems();
|
|
|
|
// Hide all items
|
|
$newItems.css('opacity', 0);
|
|
|
|
// Get ones that passed the current filter
|
|
$passed = self._filter( undefined, $newItems );
|
|
passed = $passed.get();
|
|
|
|
// How many filtered elements?
|
|
self._updateItemCount();
|
|
|
|
if ( animateIn ) {
|
|
self._layout( passed, null, true, true );
|
|
|
|
if ( isSequential ) {
|
|
self._setSequentialDelay( $passed );
|
|
}
|
|
|
|
self._revealAppended( $passed );
|
|
} else {
|
|
self._layout( passed );
|
|
}
|
|
},
|
|
|
|
_revealAppended : function( $newFilteredItems ) {
|
|
var self = this;
|
|
|
|
setTimeout(function() {
|
|
$newFilteredItems.each(function(i, el) {
|
|
self.transition({
|
|
from: 'reveal',
|
|
$this: $(el),
|
|
opacity: 1
|
|
});
|
|
});
|
|
}, self.revealAppendedDelay);
|
|
},
|
|
|
|
/**
|
|
* Public Methods
|
|
*/
|
|
|
|
/**
|
|
* The magic. This is what makes the plugin 'shuffle'
|
|
* @param {String|Function} category category to filter by. Can be a function
|
|
* @param {Object} [sortObj] A sort object which can sort the filtered set
|
|
*/
|
|
shuffle : function( category, sortObj ) {
|
|
var self = this;
|
|
|
|
if ( !self.enabled ) {
|
|
return;
|
|
}
|
|
|
|
if ( !category ) {
|
|
category = 'all';
|
|
}
|
|
|
|
self._filter( category );
|
|
// Save the last filter in case elements are appended.
|
|
self.lastFilter = category;
|
|
|
|
// How many filtered elements?
|
|
self._updateItemCount();
|
|
|
|
self._resetCols();
|
|
|
|
// Shrink each concealed item
|
|
self._shrink();
|
|
|
|
// If given a valid sort object, save it so that _reLayout() will sort the items
|
|
if ( sortObj ) {
|
|
self.lastSort = sortObj;
|
|
}
|
|
// Update transforms on .filtered elements so they will animate to their new positions
|
|
self._reLayout();
|
|
},
|
|
|
|
/**
|
|
* Gets the .filtered elements, sorts them, and passes them to layout
|
|
*
|
|
* @param {object} opts the options object for the sorted plugin
|
|
* @param {Boolean} [fromFilter] was called from Shuffle.filter method.
|
|
*/
|
|
sort: function( opts, fromFilter, isOnlyPosition ) {
|
|
var self = this,
|
|
items = self.$items.filter('.filtered').sorted(opts);
|
|
|
|
if ( !fromFilter ) {
|
|
self._resetCols();
|
|
}
|
|
|
|
self._layout(items, function() {
|
|
if (fromFilter) {
|
|
self.filterEnd();
|
|
}
|
|
self.sortEnd();
|
|
}, isOnlyPosition);
|
|
|
|
self.lastSort = opts;
|
|
},
|
|
|
|
/**
|
|
* Relayout everything
|
|
*/
|
|
resized: function( isOnlyLayout ) {
|
|
if ( this.enabled ) {
|
|
|
|
if ( !isOnlyLayout ) {
|
|
// Get updated colCount
|
|
this._setColumns();
|
|
}
|
|
|
|
// Layout items
|
|
this._reLayout();
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Use this instead of `update()` if you don't need the columns and gutters updated
|
|
* Maybe an image inside `shuffle` loaded (and now has a height), which means calculations
|
|
* could be off.
|
|
*/
|
|
layout : function() {
|
|
this.update( true );
|
|
},
|
|
|
|
update : function( isOnlyLayout ) {
|
|
this.resized( isOnlyLayout );
|
|
},
|
|
|
|
/**
|
|
* New items have been appended to shuffle. Fade them in sequentially
|
|
* @param {jQuery} $newItems jQuery collection of new items
|
|
* @param {Boolean} [animateIn] If false, the new items won't animate in
|
|
* @param {Boolean} [isSequential] If false, new items won't sequentially fade in
|
|
*/
|
|
appended : function( $newItems, animateIn, isSequential ) {
|
|
// True if undefined
|
|
animateIn = animateIn === false ? false : true;
|
|
isSequential = isSequential === false ? false : true;
|
|
|
|
this._addItems( $newItems, animateIn, isSequential );
|
|
},
|
|
|
|
/**
|
|
* Disables shuffle from updating dimensions and layout on resize
|
|
*/
|
|
disable : function() {
|
|
this.enabled = false;
|
|
},
|
|
|
|
/**
|
|
* Enables shuffle again
|
|
* @param {Boolean} isUpdateLayout if undefined, shuffle will update columns and gutters
|
|
*/
|
|
enable : function( isUpdateLayout ) {
|
|
this.enabled = true;
|
|
if ( isUpdateLayout !== false ) {
|
|
this.update();
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Remove 1 or more shuffle items
|
|
* @param {jQuery} $collection a jQuery object containing one or more element in shuffle
|
|
* @return {Shuffle} the shuffle object
|
|
*/
|
|
remove : function( $collection ) {
|
|
|
|
// If this isn't a jquery object, exit
|
|
if ( !$collection.length || !$collection.jquery ) {
|
|
return;
|
|
}
|
|
|
|
var self = this;
|
|
|
|
// Hide collection first
|
|
self._shrink( $collection, function() {
|
|
var shuffle = this;
|
|
|
|
// Remove the collection in the callback
|
|
$collection.remove();
|
|
|
|
// Update the items, layout, count and fire off `removed` event
|
|
setTimeout(function() {
|
|
shuffle.$items = shuffle._getItems();
|
|
shuffle.layout();
|
|
shuffle._updateItemCount();
|
|
shuffle._fire( 'removed', [ $collection, shuffle ] );
|
|
|
|
// Let it get garbage collected
|
|
$collection = null;
|
|
}, 0);
|
|
});
|
|
|
|
// Process changes
|
|
self._processStyleQueue();
|
|
|
|
return self;
|
|
},
|
|
|
|
destroy: function() {
|
|
var self = this;
|
|
|
|
self.$window.off('.' + self.unique);
|
|
self.$container
|
|
.removeClass('shuffle')
|
|
.removeAttr('style')
|
|
.removeData('shuffle');
|
|
self.$items
|
|
.removeAttr('style')
|
|
.removeClass('concealed filtered shuffle-item');
|
|
self.destroyed = true;
|
|
}
|
|
|
|
};
|
|
|
|
// Overrideable options
|
|
Shuffle.options = {
|
|
group : 'all', // Filter group
|
|
speed : 250, // Transition/animation speed (milliseconds)
|
|
easing : 'ease-out', // css easing function to use
|
|
itemSelector: '', // e.g. '.gallery-item'
|
|
sizer: null, // sizer element. Can be anything columnWidth is
|
|
gutterWidth : 0, // a static number or function that tells the plugin how wide the gutters between columns are (in pixels)
|
|
columnWidth : 0,// a static number or function that returns a number which tells the plugin how wide the columns are (in pixels)
|
|
showInitialTransition : false, // If set to false, the shuffle-items will only have a transition applied to them after the first layout
|
|
delimeter : null, // if your group is not json, and is comma delimeted, you could set delimeter to ','
|
|
buffer: 0, // useful for percentage based heights when they might not always be exactly the same (in pixels)
|
|
throttle: $.throttle || null,
|
|
throttleTime: 300,
|
|
sequentialFadeDelay: 150,
|
|
supported: Modernizr.csstransforms && Modernizr.csstransitions // supports transitions and transforms
|
|
};
|
|
|
|
// Not overrideable
|
|
Shuffle.settings = {
|
|
$sizer: null,
|
|
useSizer: false,
|
|
itemCss : { // default CSS for each item
|
|
position: 'absolute',
|
|
top: 0,
|
|
left: 0
|
|
},
|
|
revealAppendedDelay: 300,
|
|
keepSorted : true, // Keep sorted when shuffling/layout
|
|
enabled: true,
|
|
destroyed: false,
|
|
styleQueue: [],
|
|
prefixed: Modernizr.prefixed,
|
|
threeD: Modernizr.csstransforms3d // supports 3d transforms
|
|
};
|
|
|
|
|
|
// Plugin definition
|
|
$.fn.shuffle = function( opts ) {
|
|
var args = Array.prototype.slice.call( arguments, 1 );
|
|
return this.each(function() {
|
|
var $this = $(this),
|
|
shuffle = $this.data('shuffle');
|
|
|
|
// If we don't have a stored shuffle, make a new one and save it
|
|
if ( !shuffle ) {
|
|
shuffle = new Shuffle($this, opts);
|
|
$this.data('shuffle', shuffle);
|
|
}
|
|
|
|
if ( typeof opts === 'string' && shuffle[ opts ] ) {
|
|
shuffle[ opts ].apply( shuffle, args );
|
|
}
|
|
});
|
|
};
|
|
|
|
})(jQuery, Modernizr); |