UNPKG

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