jquery.masonry.js 9.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298
  1. /*************************************************
  2. ** jQuery Masonry version 1.3.2
  3. ** Copyright David DeSandro, licensed MIT
  4. ** http://desandro.com/resources/jquery-masonry
  5. **************************************************/
  6. ;(function($){
  7. /*!
  8. * smartresize: debounced resize event for jQuery
  9. * http://github.com/lrbabe/jquery-smartresize
  10. *
  11. * Copyright (c) 2009 Louis-Remi Babe
  12. * Licensed under the GPL license.
  13. * http://docs.jquery.com/License
  14. *
  15. */
  16. var event = $.event,
  17. resizeTimeout;
  18. event.special.smartresize = {
  19. setup: function() {
  20. $(this).bind( "resize", event.special.smartresize.handler );
  21. },
  22. teardown: function() {
  23. $(this).unbind( "resize", event.special.smartresize.handler );
  24. },
  25. handler: function( event, execAsap ) {
  26. // Save the context
  27. var context = this,
  28. args = arguments;
  29. // set correct event type
  30. event.type = "smartresize";
  31. if (resizeTimeout) { clearTimeout(resizeTimeout); }
  32. resizeTimeout = setTimeout(function() {
  33. jQuery.event.handle.apply( context, args );
  34. }, execAsap === "execAsap"? 0 : 100);
  35. }
  36. };
  37. $.fn.smartresize = function( fn ) {
  38. return fn ? this.bind( "smartresize", fn ) : this.trigger( "smartresize", ["execAsap"] );
  39. };
  40. // masonry code begin
  41. $.fn.masonry = function(options, callback) {
  42. // all my sweet methods
  43. var msnry = {
  44. getBricks : function($wall, props, opts) {
  45. var hasItemSelector = (opts.itemSelector === undefined);
  46. if ( opts.appendedContent === undefined ) {
  47. // if not appendedContent
  48. props.$bricks = hasItemSelector ?
  49. $wall.children() :
  50. $wall.find(opts.itemSelector);
  51. } else {
  52. // if appendedContent...
  53. props.$bricks = hasItemSelector ?
  54. opts.appendedContent :
  55. opts.appendedContent.filter( opts.itemSelector );
  56. }
  57. },
  58. placeBrick : function($brick, setCount, setY, props, opts) {
  59. // get the minimum Y value from the columns...
  60. var minimumY = Math.min.apply(Math, setY),
  61. setHeight = minimumY + $brick.outerHeight(true),
  62. i = setY.length,
  63. shortCol = i,
  64. setSpan = props.colCount + 1 - i;
  65. // Which column has the minY value, closest to the left
  66. while (i--) {
  67. if ( setY[i] == minimumY ) {
  68. shortCol = i;
  69. }
  70. }
  71. var position = {
  72. left: props.colW * shortCol + props.posLeft,
  73. top: minimumY
  74. };
  75. // position the brick
  76. $brick.applyStyle(position, $.extend(true,{},opts.animationOptions) );
  77. // apply setHeight to necessary columns
  78. for ( i=0; i < setSpan; i++ ) {
  79. props.colY[ shortCol + i ] = setHeight;
  80. }
  81. },
  82. setup : function($wall, opts, props) {
  83. msnry.getBricks($wall, props, opts);
  84. if ( props.masoned ) {
  85. props.previousData = $wall.data('masonry');
  86. }
  87. if ( opts.columnWidth === undefined) {
  88. props.colW = props.masoned ?
  89. props.previousData.colW :
  90. props.$bricks.outerWidth(true);
  91. } else {
  92. props.colW = opts.columnWidth;
  93. }
  94. props.colCount = Math.floor( $wall.width() / props.colW ) ;
  95. props.colCount = Math.max( props.colCount, 1 );
  96. },
  97. arrange : function($wall, opts, props) {
  98. var i;
  99. if ( !props.masoned || opts.appendedContent !== undefined ) {
  100. // just the new bricks
  101. props.$bricks.css( 'position', 'absolute' );
  102. }
  103. // if masonry hasn't been called before
  104. if ( !props.masoned ) {
  105. $wall.css( 'position', 'relative' );
  106. // get top left position of where the bricks should be
  107. var $cursor = $( document.createElement('div') );
  108. $wall.prepend( $cursor );
  109. props.posTop = Math.round( $cursor.position().top );
  110. props.posLeft = Math.round( $cursor.position().left );
  111. $cursor.remove();
  112. } else {
  113. props.posTop = props.previousData.posTop;
  114. props.posLeft = props.previousData.posLeft;
  115. }
  116. // set up column Y array
  117. if ( props.masoned && opts.appendedContent !== undefined ) {
  118. // if appendedContent is set, use colY from last call
  119. props.colY = props.previousData.colY;
  120. /*
  121. * in the case that the wall is not resizeable,
  122. * but the colCount has changed from the previous time
  123. * masonry has been called
  124. */
  125. for ( i = props.previousData.colCount; i < props.colCount; i++) {
  126. props.colY[i] = props.posTop;
  127. }
  128. } else {
  129. // start new colY array, with starting values set to posTop
  130. props.colY = [];
  131. i = props.colCount;
  132. while (i--) {
  133. props.colY.push(props.posTop);
  134. }
  135. }
  136. // are we animating the rearrangement?
  137. // use plugin-ish syntax for css or animate
  138. $.fn.applyStyle = ( props.masoned && opts.animate ) ? $.fn.animate : $.fn.css;
  139. // layout logic
  140. if ( opts.singleMode ) {
  141. props.$bricks.each(function(){
  142. var $brick = $(this);
  143. msnry.placeBrick($brick, props.colCount, props.colY, props, opts);
  144. });
  145. } else {
  146. props.$bricks.each(function() {
  147. var $brick = $(this),
  148. //how many columns does this brick span
  149. colSpan = Math.ceil( $brick.outerWidth(true) / props.colW);
  150. colSpan = Math.min( colSpan, props.colCount );
  151. if ( colSpan === 1 ) {
  152. // if brick spans only one column, just like singleMode
  153. msnry.placeBrick($brick, props.colCount, props.colY, props, opts);
  154. } else {
  155. // brick spans more than one column
  156. //how many different places could this brick fit horizontally
  157. var groupCount = props.colCount + 1 - colSpan,
  158. groupY = [];
  159. // for each group potential horizontal position
  160. for ( i=0; i < groupCount; i++ ) {
  161. // make an array of colY values for that one group
  162. var groupColY = props.colY.slice(i, i+colSpan);
  163. // and get the max value of the array
  164. groupY[i] = Math.max.apply(Math, groupColY);
  165. }
  166. msnry.placeBrick($brick, groupCount, groupY, props, opts);
  167. }
  168. }); // /props.bricks.each(function() {
  169. } // /layout logic
  170. // set the height of the wall to the tallest column
  171. props.wallH = Math.max.apply(Math, props.colY);
  172. var wallCSS = { height: props.wallH - props.posTop };
  173. $wall.applyStyle( wallCSS, $.extend(true,[],opts.animationOptions) );
  174. // add masoned class first time around
  175. if ( !props.masoned ) {
  176. // wait 1 millisec for quell transitions
  177. setTimeout(function(){
  178. $wall.addClass('masoned');
  179. }, 1);
  180. }
  181. // provide props.bricks as context for the callback
  182. callback.call( props.$bricks );
  183. // set all data so we can retrieve it for appended appendedContent
  184. // or anyone else's crazy jquery fun
  185. $wall.data('masonry', props );
  186. }, // /msnry.arrange
  187. resize : function($wall, opts, props) {
  188. props.masoned = !!$wall.data('masonry');
  189. var prevColCount = $wall.data('masonry').colCount;
  190. msnry.setup($wall, opts, props);
  191. if ( props.colCount != prevColCount ) {
  192. msnry.arrange($wall, opts, props);
  193. }
  194. }
  195. };
  196. /*
  197. * let's begin
  198. * IN A WORLD...
  199. */
  200. return this.each(function() {
  201. var $wall = $(this),
  202. props = {};
  203. // checks if masonry has been called before on this object
  204. props.masoned = !!$wall.data('masonry');
  205. var previousOptions = props.masoned ? $wall.data('masonry').options : {},
  206. opts = $.extend(
  207. {},
  208. $.fn.masonry.defaults,
  209. previousOptions,
  210. options
  211. ),
  212. resizeOn = previousOptions.resizeable;
  213. // should we save these options for next time?
  214. props.options = opts.saveOptions ? opts : previousOptions;
  215. //picked up from Paul Irish
  216. callback = callback || function(){};
  217. msnry.getBricks($wall, props, opts);
  218. // if brickParent is empty, do nothing, go back home and eat chips
  219. if ( !props.$bricks.length ) {
  220. return this;
  221. }
  222. // call masonry layout
  223. msnry.setup($wall, opts, props);
  224. msnry.arrange($wall, opts, props);
  225. // binding window resizing
  226. if ( !resizeOn && opts.resizeable ) {
  227. $(window).bind('smartresize.masonry', function() { msnry.resize($wall, opts, props); } );
  228. }
  229. if ( resizeOn && !opts.resizeable ) {
  230. $(window).unbind('smartresize.masonry');
  231. }
  232. }); // /return this.each(function()
  233. }; // /$.fn.masonry = function(options)
  234. // Default plugin options
  235. $.fn.masonry.defaults = {
  236. singleMode: false,
  237. columnWidth: undefined,
  238. itemSelector: undefined,
  239. appendedContent: undefined,
  240. saveOptions: true,
  241. resizeable: true,
  242. animate: false,
  243. animationOptions: {}
  244. };
  245. })(jQuery);