1 | /** |
---|
2 | * jQuery Masonry v2.0.110517 beta |
---|
3 | * The flip-side of CSS Floats. |
---|
4 | * jQuery plugin that rearranges item elements to a grid. |
---|
5 | * http://masonry.desandro.com |
---|
6 | * |
---|
7 | * Licensed under the MIT license. |
---|
8 | * Copyright 2011 David DeSandro |
---|
9 | */ |
---|
10 | |
---|
11 | (function( window, $, undefined ){ |
---|
12 | |
---|
13 | /* |
---|
14 | * smartresize: debounced resize event for jQuery |
---|
15 | * |
---|
16 | * latest version and complete README available on Github: |
---|
17 | * https://github.com/louisremi/jquery.smartresize.js |
---|
18 | * |
---|
19 | * Copyright 2011 @louis_remi |
---|
20 | * Licensed under the MIT license. |
---|
21 | */ |
---|
22 | |
---|
23 | var $event = $.event, |
---|
24 | resizeTimeout; |
---|
25 | |
---|
26 | $event.special.smartresize = { |
---|
27 | setup: function() { |
---|
28 | $(this).bind( "resize", $event.special.smartresize.handler ); |
---|
29 | }, |
---|
30 | teardown: function() { |
---|
31 | $(this).unbind( "resize", $event.special.smartresize.handler ); |
---|
32 | }, |
---|
33 | handler: function( event, execAsap ) { |
---|
34 | // Save the context |
---|
35 | var context = this, |
---|
36 | args = arguments; |
---|
37 | |
---|
38 | // set correct event type |
---|
39 | event.type = "smartresize"; |
---|
40 | |
---|
41 | if ( resizeTimeout ) { clearTimeout( resizeTimeout ); } |
---|
42 | resizeTimeout = setTimeout(function() { |
---|
43 | jQuery.event.handle.apply( context, args ); |
---|
44 | }, execAsap === "execAsap"? 0 : 100 ); |
---|
45 | } |
---|
46 | }; |
---|
47 | |
---|
48 | $.fn.smartresize = function( fn ) { |
---|
49 | return fn ? this.bind( "smartresize", fn ) : this.trigger( "smartresize", ["execAsap"] ); |
---|
50 | }; |
---|
51 | |
---|
52 | |
---|
53 | |
---|
54 | // ========================= Masonry =============================== |
---|
55 | |
---|
56 | |
---|
57 | // our "Widget" object constructor |
---|
58 | $.Mason = function( options, element ){ |
---|
59 | this.element = $( element ); |
---|
60 | |
---|
61 | this._create( options ); |
---|
62 | this._init(); |
---|
63 | }; |
---|
64 | |
---|
65 | // styles of container element we want to keep track of |
---|
66 | var masonryContainerStyles = [ 'position', 'height' ]; |
---|
67 | |
---|
68 | $.Mason.settings = { |
---|
69 | isResizable: true, |
---|
70 | isAnimated: false, |
---|
71 | animationOptions: { |
---|
72 | queue: false, |
---|
73 | duration: 500 |
---|
74 | }, |
---|
75 | gutterWidth: 0, |
---|
76 | isRTL: false, |
---|
77 | isFitWidth: false |
---|
78 | }; |
---|
79 | |
---|
80 | $.Mason.prototype = { |
---|
81 | |
---|
82 | _getBricks: function( $elems ) { |
---|
83 | var selector = this.options.itemSelector, |
---|
84 | // if there is a selector |
---|
85 | // filter/find appropriate item elements |
---|
86 | $bricks = !selector ? $elems : |
---|
87 | $elems.filter( selector ).add( $elems.find( selector ) ); |
---|
88 | $bricks |
---|
89 | .css({ position: 'absolute' }) |
---|
90 | .addClass('masonry-brick'); |
---|
91 | return $bricks; |
---|
92 | }, |
---|
93 | |
---|
94 | // sets up widget |
---|
95 | _create : function( options ) { |
---|
96 | |
---|
97 | this.options = $.extend( true, {}, $.Mason.settings, options ); |
---|
98 | |
---|
99 | this.styleQueue = []; |
---|
100 | // need to get bricks |
---|
101 | this.reloadItems(); |
---|
102 | |
---|
103 | |
---|
104 | // get original styles in case we re-apply them in .destroy() |
---|
105 | var elemStyle = this.element[0].style; |
---|
106 | this.originalStyle = {}; |
---|
107 | for ( var i=0, len = masonryContainerStyles.length; i < len; i++ ) { |
---|
108 | var prop = masonryContainerStyles[i]; |
---|
109 | this.originalStyle[ prop ] = elemStyle[ prop ] || null; |
---|
110 | } |
---|
111 | |
---|
112 | this.element.css({ |
---|
113 | position : 'relative' |
---|
114 | }); |
---|
115 | |
---|
116 | this.horizontalDirection = this.options.isRTL ? 'right' : 'left'; |
---|
117 | this.offset = {}; |
---|
118 | |
---|
119 | // get top left position of where the bricks should be |
---|
120 | var $cursor = $( document.createElement('div') ); |
---|
121 | this.element.prepend( $cursor ); |
---|
122 | this.offset.y = Math.round( $cursor.position().top ); |
---|
123 | // get horizontal offset |
---|
124 | if ( this.options.isRTL ) { |
---|
125 | this.offset.x = Math.round( $cursor.position().left ); |
---|
126 | } else { |
---|
127 | $cursor.css({ 'float': 'right', display: 'inline-block'}); |
---|
128 | this.offset.x = Math.round( this.element.outerWidth() - $cursor.position().left ); |
---|
129 | } |
---|
130 | $cursor.remove(); |
---|
131 | |
---|
132 | // add masonry class first time around |
---|
133 | var instance = this; |
---|
134 | setTimeout( function() { |
---|
135 | instance.element.addClass('masonry'); |
---|
136 | }, 0 ); |
---|
137 | |
---|
138 | // bind resize method |
---|
139 | if ( this.options.isResizable ) { |
---|
140 | $(window).bind( 'smartresize.masonry', function() { |
---|
141 | instance.resize(); |
---|
142 | }); |
---|
143 | } |
---|
144 | |
---|
145 | }, |
---|
146 | |
---|
147 | // _init fires when your instance is first created |
---|
148 | // (from the constructor above), and when you |
---|
149 | // attempt to initialize the widget again (by the bridge) |
---|
150 | // after it has already been initialized. |
---|
151 | _init : function( callback ) { |
---|
152 | |
---|
153 | this.reLayout( callback ); |
---|
154 | |
---|
155 | }, |
---|
156 | |
---|
157 | option: function( key, value ){ |
---|
158 | |
---|
159 | // get/change options AFTER initialization: |
---|
160 | // you don't have to support all these cases, |
---|
161 | // but here's how: |
---|
162 | |
---|
163 | // signature: $('#foo').bar({ cool:false }); |
---|
164 | if ( $.isPlainObject( key ) ){ |
---|
165 | this.options = $.extend(true, this.options, key); |
---|
166 | |
---|
167 | // signature: $('#foo').option('cool'); - getter |
---|
168 | } else if ( key && typeof value === "undefined" ){ |
---|
169 | return this.options[ key ]; |
---|
170 | |
---|
171 | // signature: $('#foo').bar('option', 'baz', false); |
---|
172 | } else { |
---|
173 | this.options[ key ] = value; |
---|
174 | } |
---|
175 | |
---|
176 | return this; // make sure to return the instance! |
---|
177 | }, |
---|
178 | |
---|
179 | // ====================== General Layout ====================== |
---|
180 | |
---|
181 | // used on collection of atoms (should be filtered, and sorted before ) |
---|
182 | // accepts atoms-to-be-laid-out to start with |
---|
183 | layout : function( $bricks, callback ) { |
---|
184 | |
---|
185 | // layout logic |
---|
186 | var $brick, colSpan, groupCount, groupY, groupColY, j; |
---|
187 | |
---|
188 | for (var i=0, len = $bricks.length; i < len; i++) { |
---|
189 | $brick = $( $bricks[i] ); |
---|
190 | //how many columns does this brick span |
---|
191 | colSpan = Math.ceil( $brick.outerWidth(true) / this.columnWidth ); |
---|
192 | colSpan = Math.min( colSpan, this.cols ); |
---|
193 | |
---|
194 | if ( colSpan === 1 ) { |
---|
195 | // if brick spans only one column, just like singleMode |
---|
196 | this._placeBrick( $brick, this.cols, this.colYs ); |
---|
197 | } else { |
---|
198 | // brick spans more than one column |
---|
199 | // how many different places could this brick fit horizontally |
---|
200 | groupCount = this.cols + 1 - colSpan; |
---|
201 | groupY = []; |
---|
202 | |
---|
203 | // for each group potential horizontal position |
---|
204 | for ( j=0; j < groupCount; j++ ) { |
---|
205 | // make an array of colY values for that one group |
---|
206 | groupColY = this.colYs.slice( j, j+colSpan ); |
---|
207 | // and get the max value of the array |
---|
208 | groupY[j] = Math.max.apply( Math, groupColY ); |
---|
209 | } |
---|
210 | |
---|
211 | this._placeBrick( $brick, groupCount, groupY ); |
---|
212 | } |
---|
213 | } |
---|
214 | |
---|
215 | // set the size of the container |
---|
216 | var containerSize = {}; |
---|
217 | containerSize.height = Math.max.apply( Math, this.colYs ) - this.offset.y; |
---|
218 | if ( this.options.isFitWidth ) { |
---|
219 | containerSize.width = this.cols * this.columnWidth - this.options.gutterWidth; |
---|
220 | } |
---|
221 | this.styleQueue.push({ $el: this.element, style: containerSize }); |
---|
222 | |
---|
223 | // are we animating the layout arrangement? |
---|
224 | // use plugin-ish syntax for css or animate |
---|
225 | var styleFn = !this.isLaidOut ? 'css' : ( |
---|
226 | this.options.isAnimated ? 'animate' : 'css' |
---|
227 | ), |
---|
228 | animOpts = this.options.animationOptions; |
---|
229 | |
---|
230 | // process styleQueue |
---|
231 | var obj; |
---|
232 | for (i=0, len = this.styleQueue.length; i < len; i++) { |
---|
233 | obj = this.styleQueue[i]; |
---|
234 | obj.$el[ styleFn ]( obj.style, animOpts ); |
---|
235 | } |
---|
236 | |
---|
237 | // clear out queue for next time |
---|
238 | this.styleQueue = []; |
---|
239 | |
---|
240 | // provide $elems as context for the callback |
---|
241 | if ( callback ) { |
---|
242 | callback.call( $bricks ); |
---|
243 | } |
---|
244 | |
---|
245 | this.isLaidOut = true; |
---|
246 | |
---|
247 | return this; |
---|
248 | }, |
---|
249 | |
---|
250 | // calculates number of columns |
---|
251 | // i.e. this.columnWidth = 200 |
---|
252 | _getColumns : function() { |
---|
253 | var container = this.options.isFitWidth ? this.element.parent() : this.element, |
---|
254 | containerWidth = container.width(); |
---|
255 | |
---|
256 | this.columnWidth = this.options.columnWidth || |
---|
257 | // or use the size of the first item |
---|
258 | this.$bricks.outerWidth(true) || |
---|
259 | // if there's no items, use size of container |
---|
260 | containerWidth; |
---|
261 | |
---|
262 | this.columnWidth += this.options.gutterWidth; |
---|
263 | |
---|
264 | this.cols = Math.floor( ( containerWidth + this.options.gutterWidth ) / this.columnWidth ); |
---|
265 | this.cols = Math.max( this.cols, 1 ); |
---|
266 | |
---|
267 | return this; |
---|
268 | |
---|
269 | }, |
---|
270 | |
---|
271 | _placeBrick : function( $brick, setCount, setY ) { |
---|
272 | // get the minimum Y value from the columns |
---|
273 | var minimumY = Math.min.apply( Math, setY ), |
---|
274 | setHeight = minimumY + $brick.outerHeight(true), |
---|
275 | i = setY.length, |
---|
276 | shortCol = i, |
---|
277 | setSpan = this.cols + 1 - i, |
---|
278 | position = {}; |
---|
279 | // Which column has the minY value, closest to the left |
---|
280 | while (i--) { |
---|
281 | if ( setY[i] === minimumY ) { |
---|
282 | shortCol = i; |
---|
283 | } |
---|
284 | } |
---|
285 | |
---|
286 | // position the brick |
---|
287 | position.top = minimumY; |
---|
288 | // position.left or position.right |
---|
289 | position[ this.horizontalDirection ] = this.columnWidth * shortCol + this.offset.x; |
---|
290 | this.styleQueue.push({ $el: $brick, style: position }); |
---|
291 | |
---|
292 | // apply setHeight to necessary columns |
---|
293 | for ( i=0; i < setSpan; i++ ) { |
---|
294 | this.colYs[ shortCol + i ] = setHeight; |
---|
295 | } |
---|
296 | |
---|
297 | }, |
---|
298 | |
---|
299 | |
---|
300 | resize : function() { |
---|
301 | var prevColCount = this.cols; |
---|
302 | // get updated colCount |
---|
303 | this._getColumns('masonry'); |
---|
304 | if ( this.cols !== prevColCount ) { |
---|
305 | // if column count has changed, do a new column cound |
---|
306 | this._reloadLayout(); |
---|
307 | } |
---|
308 | }, |
---|
309 | |
---|
310 | |
---|
311 | reLayout : function( callback ) { |
---|
312 | this._getColumns('masonry'); |
---|
313 | this._reloadLayout( callback ); |
---|
314 | }, |
---|
315 | |
---|
316 | _reloadLayout : function( callback ) { |
---|
317 | // reset columns |
---|
318 | var i = this.cols; |
---|
319 | this.colYs = []; |
---|
320 | while (i--) { |
---|
321 | this.colYs.push( this.offset.y ); |
---|
322 | } |
---|
323 | // apply layout logic to all bricks |
---|
324 | this.layout( this.$bricks, callback ); |
---|
325 | }, |
---|
326 | |
---|
327 | // ====================== Convenience methods ====================== |
---|
328 | |
---|
329 | // goes through all children again and gets bricks in proper order |
---|
330 | reloadItems : function() { |
---|
331 | this.$bricks = this._getBricks( this.element.children() ); |
---|
332 | }, |
---|
333 | |
---|
334 | |
---|
335 | reload : function( callback ) { |
---|
336 | this.reloadItems(); |
---|
337 | this.reLayout( callback ); |
---|
338 | }, |
---|
339 | |
---|
340 | |
---|
341 | // convienence method for working with Infinite Scroll |
---|
342 | appended : function( $content, callback ) { |
---|
343 | var $newBricks = this._getBricks( $content ); |
---|
344 | // add new bricks to brick pool |
---|
345 | this.$bricks = this.$bricks.add( $newBricks ); |
---|
346 | this.layout( $newBricks, callback ); |
---|
347 | }, |
---|
348 | |
---|
349 | // removes elements from Masonry widget |
---|
350 | remove : function( $content ) { |
---|
351 | this.$bricks = this.$bricks.not( $content ); |
---|
352 | $content.remove(); |
---|
353 | }, |
---|
354 | |
---|
355 | // destroys widget, returns elements and container back (close) to original style |
---|
356 | destroy : function() { |
---|
357 | |
---|
358 | this.$bricks |
---|
359 | .removeClass('masonry-brick') |
---|
360 | .each(function(){ |
---|
361 | this.style.position = null; |
---|
362 | this.style.top = null; |
---|
363 | this.style.left = null; |
---|
364 | }); |
---|
365 | |
---|
366 | // re-apply saved container styles |
---|
367 | var elemStyle = this.element[0].style; |
---|
368 | for ( var i=0, len = masonryContainerStyles.length; i < len; i++ ) { |
---|
369 | var prop = masonryContainerStyles[i]; |
---|
370 | elemStyle[ prop ] = this.originalStyle[ prop ]; |
---|
371 | } |
---|
372 | |
---|
373 | this.element |
---|
374 | .unbind('.masonry') |
---|
375 | .removeClass('masonry') |
---|
376 | .removeData('masonry'); |
---|
377 | |
---|
378 | $(window).unbind('.masonry'); |
---|
379 | |
---|
380 | } |
---|
381 | |
---|
382 | }; |
---|
383 | |
---|
384 | |
---|
385 | // ======================= imagesLoaded Plugin =============================== |
---|
386 | // A fork of http://gist.github.com/268257 by Paul Irish |
---|
387 | |
---|
388 | // mit license. paul irish. 2010. |
---|
389 | // webkit fix from Oren Solomianik. thx! |
---|
390 | |
---|
391 | $.fn.imagesLoaded = function(callback){ |
---|
392 | var elems = this.find('img'), |
---|
393 | len = elems.length, |
---|
394 | blank = "data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///ywAAAAAAQABAAACAUwAOw==", |
---|
395 | _this = this; |
---|
396 | |
---|
397 | // if no images, trigger immediately |
---|
398 | if ( !len ) { |
---|
399 | callback.call( this ); |
---|
400 | return this; |
---|
401 | } |
---|
402 | |
---|
403 | elems.bind('load', function() { |
---|
404 | if ( --len <= 0 && this.src !== blank ) { |
---|
405 | callback.call( _this ); |
---|
406 | } |
---|
407 | }).each(function(){ |
---|
408 | // cached images don't fire load sometimes, so we reset src. |
---|
409 | if (this.complete || this.complete === undefined){ |
---|
410 | var src = this.src; |
---|
411 | // webkit hack from http://groups.google.com/group/jquery-dev/browse_thread/thread/eee6ab7b2da50e1f |
---|
412 | // data uri bypasses webkit log warning (thx doug jones) |
---|
413 | this.src = blank; |
---|
414 | this.src = src; |
---|
415 | } |
---|
416 | }); |
---|
417 | |
---|
418 | return this; |
---|
419 | }; |
---|
420 | |
---|
421 | |
---|
422 | // ======================= Plugin bridge =============================== |
---|
423 | // leverages data method to either create or return $.Mason constructor |
---|
424 | // A bit from jQuery UI |
---|
425 | // https://github.com/jquery/jquery-ui/blob/master/ui/jquery.ui.widget.js |
---|
426 | // A bit from jcarousel |
---|
427 | // https://github.com/jsor/jcarousel/blob/master/lib/jquery.jcarousel.js |
---|
428 | |
---|
429 | $.fn.masonry = function( options ) { |
---|
430 | if ( typeof options === 'string' ) { |
---|
431 | // call method |
---|
432 | var args = Array.prototype.slice.call( arguments, 1 ); |
---|
433 | |
---|
434 | return this.each(function(){ |
---|
435 | var instance = $.data( this, 'masonry' ); |
---|
436 | if ( !instance ) { |
---|
437 | return $.error( "cannot call methods on masonry prior to initialization; " + |
---|
438 | "attempted to call method '" + options + "'" ); |
---|
439 | } |
---|
440 | if ( !$.isFunction( instance[options] ) || options.charAt(0) === "_" ) { |
---|
441 | return $.error( "no such method '" + options + "' for masonry instance" ); |
---|
442 | } |
---|
443 | // apply method |
---|
444 | instance[ options ].apply( instance, args ); |
---|
445 | }); |
---|
446 | } else { |
---|
447 | return this.each(function() { |
---|
448 | var instance = $.data( this, 'masonry' ); |
---|
449 | if ( instance ) { |
---|
450 | // apply options & init |
---|
451 | instance.option( options || {} )._init(); |
---|
452 | } else { |
---|
453 | // initialize new instance |
---|
454 | $.data( this, 'masonry', new $.Mason( options, this ) ); |
---|
455 | } |
---|
456 | }); |
---|
457 | } |
---|
458 | }; |
---|
459 | |
---|
460 | })( window, jQuery ); |
---|