masonry work.

Lots o' stuff because this plugin is used in another project and all
minor updates go there.
pull/56/head
Glen Cheney 12 years ago
parent 0aefb10d65
commit 7ff9b2c728

@ -12,30 +12,52 @@
* Inspired by Isotope http://isotope.metafizzy.co/
* Use it for whatever you want!
* @author Glen Cheney (http://glencheney.com)
* @version 1.6
* @date 11/3/12
* @version 1.6.1
* @date 12/06/12
*/
;(function($, Modernizr) {
;(function($, Modernizr, undefined) {
'use strict';
// You can return `undefined` from the `by` function to revert to DOM order
$.fn.sorted = function(options) {
var opts = $.extend({}, $.fn.sorted.defaults, options),
arr = this.get();
arr = this.get(),
revert = false;
// 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) {
var valA = opts.by($(a));
var valB = opts.by($(b));
return (valA < valB) ? -1 : (valA > valB) ? 1 : 0;
// 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;
}
return (valA < valB) ? -1 :
(valA > valB) ? 1 : 0;
});
}
if (opts.reverse) {
// Revert to the original array if necessary
if ( revert ) {
return this.get();
}
if ( opts.reverse ) {
arr.reverse();
}
return arr;
};
@ -46,15 +68,18 @@
};
var Shuffle = function( $container, options ) {
var self = this;
var self = this,
$window = $(window);
$.extend(self, $.fn.shuffle.options, options, $.fn.shuffle.settings);
self.$container = $container.addClass('shuffle');
self.$items = self._getItems();
self.$items = self._getItems().addClass('shuffle-item');
self.transitionName = self.prefixed('transition'),
self.transform = self.getPrefixed('transform');
self.fire('loading');
// Get offset from container
self.offset = {
left: parseInt( ( self.$container.css('padding-left') || 0 ), 10 ),
@ -76,27 +101,19 @@
self.itemCss = {
position: 'absolute',
top: 0,
left: 0,
opacity: 1
left: 0
};
// Set up css for transitions
self.$container.css('position', 'relative')[0].style[ self.transitionName ] = 'height ' + self.speed + 'ms ' + self.easing;
self.$items.each(function() {
$(this).css(self.itemCss);
// Set CSS transition for transforms and opacity
if (self.supported) {
this.style[self.transitionName] = self.transform + ' ' + self.speed + 'ms ' + self.easing + ', opacity ' + self.speed + 'ms ' + self.easing;
}
});
self._initItems( !self.showInitialTransition );
// http://stackoverflow.com/questions/1852751/window-resize-event-firing-in-internet-explorer
self.windowHeight = $(window).height();
self.windowWidth = $(window).width();
$(window).on('resize.shuffle', function () {
var height = $(window).height(),
width = $(window).width();
self.windowHeight = $window.height();
self.windowWidth = $window.width();
$window.on('resize.shuffle', function () {
var height = $window.height(),
width = $window.width();
if (width !== self.windowWidth || height !== self.windowHeight) {
self.resized();
@ -108,6 +125,12 @@
self._setColumns();
self._resetCols();
self.shuffle( self.group );
if ( !self.showInitialTransition ) {
self._initItems();
}
self.fire('done');
};
Shuffle.prototype = {
@ -117,23 +140,53 @@
/**
* The magic. This is what makes the plugin 'shuffle'
*/
shuffle : function(category) {
shuffle : function( category ) {
var self = this;
if (!category) {
category = 'all';
}
self.filter( category );
// Save the last filter in case elements are appended.
self.lastFilter = category;
// How many filtered elements?
self.visibleItems = self.$items.filter('.filtered').length;
self._resetCols();
// Shrink each concealed item
self.shrink();
// Update transforms on .filtered elements so they will animate to their new positions
self._reLayout();
},
filter : function( category, $collection ) {
var self = this,
isPartialSet = $collection !== undefined,
$items = isPartialSet ? $collection : self.$items,
$filtered = $();
category = category || self.lastFilter;
self.fire('filter');
// Default is to show all items
self.$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)) {
self.$items.each(function() {
$items.each(function() {
var $item = $(this),
passes = category.call($item[0], $item, self);
$item.addClass(passes ? 'filtered' : 'concealed');
if ( passes ) {
$filtered = $filtered.add($item);
}
});
}
@ -141,35 +194,63 @@
else {
self.group = category;
if (category !== 'all') {
self.$items.each(function() {
var keys = $(this).data('groups');
if ($.inArray(category, keys) === -1) {
$(this).addClass('concealed');
return;
} else {
$(this).addClass('filtered');
$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,
theClass = passes ? 'concealed' : 'filtered';
$this.addClass( theClass );
if ( passes ) {
$filtered = $filtered.add($this);
}
});
}
// category === 'all', add filtered class to everything
else {
self.$items.addClass('filtered');
$filtered = $items.addClass('filtered');
}
}
// How many filtered elements?
// self.visibleItems = self.$items.filter('.filtered').length;
self._resetCols();
return $filtered;
},
// Shrink each concealed item
self.fire('shrink');
self.shrink();
_initItems : function( withoutTransition ) {
var self = this;
// Update transforms on .filtered elements so they will animate to their new positions
self.fire('filter');
self.filter();
self.$items.each(function() {
$(this).css(self.itemCss);
// Set CSS transition for transforms and opacity
if ( self.supported && !withoutTransition ) {
self._setTransition(this);
}
});
},
_setTransition : function( element ) {
var self = this;
element.style[self.transitionName] = self.transform + ' ' + self.speed + 'ms ' + self.easing + ', opacity ' + self.speed + 'ms ' + self.easing;
},
_setSequentialDelay : function( $collection ) {
var self = this;
if ( !self.supported ) {
return;
}
$collection.each(function(i) {
this.style[self.transitionName + 'Delay'] = ((i + 1) * 150) + 'ms';
// Set the delay back to zero after one transition
$(this).one($.support.transition.end, function() {
this.style[self.transitionName + 'Delay'] = '0ms';
});
});
},
_getItems : function() {
@ -194,7 +275,8 @@
self.colWidth += gutter;
self.cols = Math.floor( ( containerWidth + gutter ) / self.colWidth );
// Was flooring 4.999999999999999 to 4 :(
self.cols = Math.floor( ( containerWidth + gutter + 0.000000000001 ) / self.colWidth );
self.cols = Math.max( self.cols, 1 );
// This can happen when .shuffle is called on something hidden (e.g. display:none for tabs)
@ -228,14 +310,12 @@
* @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} onlyPosition set this to true to only trigger positioning of the items
*/
layout: function( items, fn ) {
layout: function( items, fn, onlyPosition ) {
var self = this;
// Abort if no items
if ( items.length === 0 ) {
return;
}
fn = fn || self.filterEnd;
self.layoutTransitionEnded = false;
$.each(items, function(index) {
@ -245,25 +325,25 @@
colSpan = Math.min( colSpan, self.cols );
if ( colSpan === 1 ) {
// if brick spans only one column, just like singleMode
self._placeItem( $this, self.colYs, fn );
// if brick spans only one column, just like singleMode
self._placeItem( $this, self.colYs, fn );
} 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 );
// 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, onlyPosition );
}
});
@ -281,14 +361,20 @@
},
_reLayout : function( callback ) {
callback = callback || this.filterEnd;
this._resetCols();
// apply layout logic to all bricks
this.layout( this.$items.get(), callback );
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 );
} else {
self.layout( self.$items.filter('.filtered').get(), self.filterEnd );
}
},
// worker method that places brick in the columnSet with the the minY
_placeItem : function( $item, setY, callback ) {
_placeItem : function( $item, setY, callback, onlyPosition ) {
// get the minimum Y value from the columns
var self = this,
minimumY = Math.min.apply( Math, setY ),
@ -312,21 +398,34 @@
$item.data( {x: x, y: y} );
// Apply setHeight to necessary columns
var setHeight = minimumY + ( $item.outerHeight(true) || $item.data('height') ),
var setHeight = minimumY + $item.outerHeight(true),
setSpan = self.cols + 1 - len;
for ( i = 0; i < setSpan; i++ ) {
self.colYs[ shortCol + i ] = setHeight;
}
self.transition({
from: 'layout',
$this: $item,
x: x,
y: y,
scale : 1,
opacity: 1,
callback: callback
});
if ( onlyPosition ) {
self._skipTransition($item[0], function() {
self.transition({
from: 'layout',
$this: $item,
x: x,
y: y,
// scale : 1,
opacity: 0
});
});
} else {
self.transition({
from: 'layout',
$this: $item,
x: x,
y: y,
scale : 1,
opacity: 1,
callback: callback
});
}
},
@ -342,6 +441,8 @@
return;
}
self.fire('shrink');
self.shrinkTransitionEnded = false;
$concealed.each(function() {
var $this = $(this),
@ -364,20 +465,6 @@
});
},
/**
* Grabs the .filtered elements and passes them to layout
*/
filter : function() {
var self = this;
// If we've already sorted the elements, keep them sorted
if (self.keepSorted && self.lastSort) {
self.sort(self.lastSort, true);
} else {
var items = self.$items.filter('.filtered').get();
self.layout(items, self.filterEnd);
}
},
/**
* Gets the .filtered elements, sorts them, and passes them to layout
*
@ -410,7 +497,7 @@
/**
* Returns things like -webkit-transition or -moz-box-sizing
* Returns things like webkitTransition or boxSizing
*
* @param {string} property to be prefixed.
* @return {string} the prefixed css property
@ -449,8 +536,16 @@
}
};
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 {
@ -459,7 +554,11 @@
// Update css to trigger CSS Animation
opts.$this.css('opacity' , opts.opacity);
self.setPrefixedCss(opts.$this, 'transform', transform);
if ( opts.x !== undefined ) {
self.setPrefixedCss(opts.$this, 'transform', transform);
}
opts.$this.one(self.transitionEndName, complete);
} else {
// Use jQuery to animate left/top
@ -497,13 +596,81 @@
self.$container.removeAttr('style').removeData('shuffle');
$(window).off('.shuffle');
self.$items.removeAttr('style').removeClass('concealed filtered');
self.$items.removeAttr('style').removeClass('concealed filtered shuffle-item');
},
update: function() {
var self = this;
_skipTransition : function(element, property, value) {
var self = this,
reflow,
durationName = self.getPrefixed('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;
}
reflow = element.offsetWidth; // Force reflow
// Put the duration back
element.style[ durationName ] = duration;
},
appended : function( $newItems, animateIn, isSequential ) {
// True if undefined
animateIn = animateIn === false ? false : true;
isSequential = isSequential === false ? false : true;
this._addItems( $newItems, animateIn, isSequential );
},
_addItems : function( $newItems, animateIn, isSequential ) {
var self = this,
$passed;
$newItems.addClass('shuffle-item');
self.$items = self._getItems();
self.resized();
self._initItems();
$newItems.not($passed).css('opacity', 0);
$passed = self.filter( undefined, $newItems );
// How many filtered elements?
self.visibleItems = self.$items.filter('.filtered').length;
if ( animateIn ) {
self.layout( $passed, null, 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
});
});
}, 100);
},
update: function() {
this.resized();
}
};
@ -511,6 +678,7 @@
// Plugin definition
$.fn.shuffle = function(opts, sortObj) {
var args = Array.prototype.slice.call( arguments, 1 );
return this.each(function() {
var $this = $(this),
shuffle = $this.data('shuffle');
@ -530,11 +698,13 @@
if (opts === 'sort') {
shuffle.sort(sortObj);
} else if (opts === 'destroy') {
shuffle.destroy();
shuffle.destroy.apply( shuffle, args );
} else if (opts === 'update') {
shuffle.update();
shuffle.update.apply( shuffle, args );
} else if (opts === 'appended') {
shuffle.appended.apply( shuffle, args );
} else if (opts === 'layout') {
shuffle._reLayout();
shuffle._reLayout.apply( shuffle, args );
} else {
shuffle.shuffle(opts);
}
@ -544,20 +714,22 @@
// Overrideable options
$.fn.shuffle.options = {
group : 'all',
speed : 600,
easing : 'ease-out',
itemSelector: '',
gutterWidth : 0,
columnWidth : 0,
group : 'all', // Filter group
speed : 600, // Transition/animation speed (milliseconds)
easing : 'ease-out', // css easing function to use
itemSelector: '', // e.g. '.gallery-item'
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 : true, // 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 ','
keepSorted : true
};
// Not overrideable
$.fn.shuffle.settings = {
supported: Modernizr.csstransforms && Modernizr.csstransitions,
supported: Modernizr.csstransforms && Modernizr.csstransitions, // supports transitions and transforms
prefixed: Modernizr.prefixed,
threeD: Modernizr.csstransforms3d
threeD: Modernizr.csstransforms3d // supports 3d transforms
};
})(jQuery, Modernizr);
Loading…
Cancel
Save