masonry.js 7.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239
  1. /*!
  2. * Masonry v4.2.0
  3. * Cascading grid layout library
  4. * http://masonry.desandro.com
  5. * MIT License
  6. * by David DeSandro
  7. */
  8. ( function( window, factory ) {
  9. // universal module definition
  10. /* jshint strict: false */ /*globals define, module, require */
  11. if ( typeof define == 'function' && define.amd ) {
  12. // AMD
  13. define( [
  14. 'outlayer/outlayer',
  15. 'get-size/get-size'
  16. ],
  17. factory );
  18. } else if ( typeof module == 'object' && module.exports ) {
  19. // CommonJS
  20. module.exports = factory(
  21. require('outlayer'),
  22. require('get-size')
  23. );
  24. } else {
  25. // browser global
  26. window.Masonry = factory(
  27. window.Outlayer,
  28. window.getSize
  29. );
  30. }
  31. }( window, function factory( Outlayer, getSize ) {
  32. 'use strict';
  33. // -------------------------- masonryDefinition -------------------------- //
  34. // create an Outlayer layout class
  35. var Masonry = Outlayer.create('masonry');
  36. // isFitWidth -> fitWidth
  37. Masonry.compatOptions.fitWidth = 'isFitWidth';
  38. var proto = Masonry.prototype;
  39. proto._resetLayout = function() {
  40. this.getSize();
  41. this._getMeasurement( 'columnWidth', 'outerWidth' );
  42. this._getMeasurement( 'gutter', 'outerWidth' );
  43. this.measureColumns();
  44. // reset column Y
  45. this.colYs = [];
  46. for ( var i=0; i < this.cols; i++ ) {
  47. this.colYs.push( 0 );
  48. }
  49. this.maxY = 0;
  50. this.horizontalColIndex = 0;
  51. };
  52. proto.measureColumns = function() {
  53. this.getContainerWidth();
  54. // if columnWidth is 0, default to outerWidth of first item
  55. if ( !this.columnWidth ) {
  56. var firstItem = this.items[0];
  57. var firstItemElem = firstItem && firstItem.element;
  58. // columnWidth fall back to item of first element
  59. this.columnWidth = firstItemElem && getSize( firstItemElem ).outerWidth ||
  60. // if first elem has no width, default to size of container
  61. this.containerWidth;
  62. }
  63. var columnWidth = this.columnWidth += this.gutter;
  64. // calculate columns
  65. var containerWidth = this.containerWidth + this.gutter;
  66. var cols = containerWidth / columnWidth;
  67. // fix rounding errors, typically with gutters
  68. var excess = columnWidth - containerWidth % columnWidth;
  69. // if overshoot is less than a pixel, round up, otherwise floor it
  70. var mathMethod = excess && excess < 1 ? 'round' : 'floor';
  71. cols = Math[ mathMethod ]( cols );
  72. this.cols = Math.max( cols, 1 );
  73. };
  74. proto.getContainerWidth = function() {
  75. // container is parent if fit width
  76. var isFitWidth = this._getOption('fitWidth');
  77. var container = isFitWidth ? this.element.parentNode : this.element;
  78. // check that this.size and size are there
  79. // IE8 triggers resize on body size change, so they might not be
  80. var size = getSize( container );
  81. this.containerWidth = size && size.innerWidth;
  82. };
  83. proto._getItemLayoutPosition = function( item ) {
  84. item.getSize();
  85. // how many columns does this brick span
  86. var remainder = item.size.outerWidth % this.columnWidth;
  87. var mathMethod = remainder && remainder < 1 ? 'round' : 'ceil';
  88. // round if off by 1 pixel, otherwise use ceil
  89. var colSpan = Math[ mathMethod ]( item.size.outerWidth / this.columnWidth );
  90. colSpan = Math.min( colSpan, this.cols );
  91. // use horizontal or top column position
  92. var colPosMethod = this.options.horizontalOrder ?
  93. '_getHorizontalColPosition' : '_getTopColPosition';
  94. var colPosition = this[ colPosMethod ]( colSpan, item );
  95. // position the brick
  96. var position = {
  97. x: this.columnWidth * colPosition.col,
  98. y: colPosition.y
  99. };
  100. // apply setHeight to necessary columns
  101. var setHeight = colPosition.y + item.size.outerHeight;
  102. var setMax = colSpan + colPosition.col;
  103. for ( var i = colPosition.col; i < setMax; i++ ) {
  104. this.colYs[i] = setHeight;
  105. }
  106. return position;
  107. };
  108. proto._getTopColPosition = function( colSpan ) {
  109. var colGroup = this._getTopColGroup( colSpan );
  110. // get the minimum Y value from the columns
  111. var minimumY = Math.min.apply( Math, colGroup );
  112. return {
  113. col: colGroup.indexOf( minimumY ),
  114. y: minimumY,
  115. };
  116. };
  117. /**
  118. * @param {Number} colSpan - number of columns the element spans
  119. * @returns {Array} colGroup
  120. */
  121. proto._getTopColGroup = function( colSpan ) {
  122. if ( colSpan < 2 ) {
  123. // if brick spans only one column, use all the column Ys
  124. return this.colYs;
  125. }
  126. var colGroup = [];
  127. // how many different places could this brick fit horizontally
  128. var groupCount = this.cols + 1 - colSpan;
  129. // for each group potential horizontal position
  130. for ( var i = 0; i < groupCount; i++ ) {
  131. colGroup[i] = this._getColGroupY( i, colSpan );
  132. }
  133. return colGroup;
  134. };
  135. proto._getColGroupY = function( col, colSpan ) {
  136. if ( colSpan < 2 ) {
  137. return this.colYs[ col ];
  138. }
  139. // make an array of colY values for that one group
  140. var groupColYs = this.colYs.slice( col, col + colSpan );
  141. // and get the max value of the array
  142. return Math.max.apply( Math, groupColYs );
  143. };
  144. // get column position based on horizontal index. #873
  145. proto._getHorizontalColPosition = function( colSpan, item ) {
  146. var col = this.horizontalColIndex % this.cols;
  147. var isOver = colSpan > 1 && col + colSpan > this.cols;
  148. // shift to next row if item can't fit on current row
  149. col = isOver ? 0 : col;
  150. // don't let zero-size items take up space
  151. var hasSize = item.size.outerWidth && item.size.outerHeight;
  152. this.horizontalColIndex = hasSize ? col + colSpan : this.horizontalColIndex;
  153. return {
  154. col: col,
  155. y: this._getColGroupY( col, colSpan ),
  156. };
  157. };
  158. proto._manageStamp = function( stamp ) {
  159. var stampSize = getSize( stamp );
  160. var offset = this._getElementOffset( stamp );
  161. // get the columns that this stamp affects
  162. var isOriginLeft = this._getOption('originLeft');
  163. var firstX = isOriginLeft ? offset.left : offset.right;
  164. var lastX = firstX + stampSize.outerWidth;
  165. var firstCol = Math.floor( firstX / this.columnWidth );
  166. firstCol = Math.max( 0, firstCol );
  167. var lastCol = Math.floor( lastX / this.columnWidth );
  168. // lastCol should not go over if multiple of columnWidth #425
  169. lastCol -= lastX % this.columnWidth ? 0 : 1;
  170. lastCol = Math.min( this.cols - 1, lastCol );
  171. // set colYs to bottom of the stamp
  172. var isOriginTop = this._getOption('originTop');
  173. var stampMaxY = ( isOriginTop ? offset.top : offset.bottom ) +
  174. stampSize.outerHeight;
  175. for ( var i = firstCol; i <= lastCol; i++ ) {
  176. this.colYs[i] = Math.max( stampMaxY, this.colYs[i] );
  177. }
  178. };
  179. proto._getContainerSize = function() {
  180. this.maxY = Math.max.apply( Math, this.colYs );
  181. var size = {
  182. height: this.maxY
  183. };
  184. if ( this._getOption('fitWidth') ) {
  185. size.width = this._getContainerFitWidth();
  186. }
  187. return size;
  188. };
  189. proto._getContainerFitWidth = function() {
  190. var unusedCols = 0;
  191. // count unused columns
  192. var i = this.cols;
  193. while ( --i ) {
  194. if ( this.colYs[i] !== 0 ) {
  195. break;
  196. }
  197. unusedCols++;
  198. }
  199. // fit container to columns that have been used
  200. return ( this.cols - unusedCols ) * this.columnWidth - this.gutter;
  201. };
  202. proto.needsResizeLayout = function() {
  203. var previousWidth = this.containerWidth;
  204. this.getContainerWidth();
  205. return previousWidth != this.containerWidth;
  206. };
  207. return Masonry;
  208. }));