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