-
Notifications
You must be signed in to change notification settings - Fork 49
Expand file tree
/
Copy pathjquery.isotope.perfectmasonry.js
More file actions
278 lines (221 loc) · 10.4 KB
/
Copy pathjquery.isotope.perfectmasonry.js
File metadata and controls
278 lines (221 loc) · 10.4 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
/*!
* PerfectMasonry extension for Isotope
*
* Does similar things as the Isotopes "masonry" layoutmode, except that this one will actually go back and plug the holes
* left by bigger elements, thus making a perfect brick wall. Profit!
*
* Usage:
* $('#grid').isotope({
* layoutMode: 'perfectMasonry',
* perfectMasonry: {
* layout: 'horizontal', // Set layout as vertical/horizontal (default: vertical)
* columnWidth: 200, // Set/Prefer columns to x wide (default: width of first tile)
* rowHeight: 200, // Set/Prefer rows to y high (default: height of first tile)
*
* liquid: true, // Set layout as liquid (default: false)
* cols: 3, // Force to have x columns (default: null)
* rows: 3, // Force to have y rows (default: null)
* minCols: 3, // Set min col count (default: 1)
* minRows: 3, // Set min row count (default: 1)
* maxCols: 5, // Set max col count (default: 9999)
* maxRows: 4 // Set max row count (default: 9999)
* }
* });
*
*
* @author Mikko Tikkanen, Zonear Ltd. <contact@zonear.com>
*/
;(function($, undefined) {
var version = '1.2.1';
var isotope = null,
$context = null,
$container = null;
$.extend($.Isotope.prototype, {
isFirstRun: true,
/**
* Reset layout properties
*
* Runs before any layout change
* -------------------------------------------------------------------------------------------------------- */
_perfectMasonryReset: function() {
this.options.perfectMasonry = this.options.perfectMasonry|| {};
var isVertical = this.options.perfectMasonry.layout != 'horizontal',
isLiquid = this.options.perfectMasonry.liquid == true;
// Do things on a first run
if(this.isFirstRun) {
this.isFirstRun = false;
isotope = this;
$context = $(this.element.context);
$container = $context.parent();
// Make sure we have min/maxCols & rows
this.options.perfectMasonry.minCols = this.options.perfectMasonry.minCols || 1;
this.options.perfectMasonry.minRows = this.options.perfectMasonry.minRows || 1;
this.options.perfectMasonry.maxCols = this.options.perfectMasonry.maxCols || 9999;
this.options.perfectMasonry.maxRows = this.options.perfectMasonry.maxRows || 9999;
}
// For liquid layout, replace default resize handler with forced relayout (outside firstRun as Isotope grid can be destroyed and created on the fly)
if(isLiquid && $.data(window, 'events') && $._data(window, 'events').smartresize) {
$(window).off('smartresize.isotope');
$(window).on('resize.isotope.perfectmasonry', function() {
if(!$context.hasClass('isotope')) { return; }
$context.isotope('reLayout');
});
}
// Setup layout properties ---------------------------------------------------
var properties = this.perfectMasonry = {};
// Fill properties with columnWidth and rowHeight (true argument)
this._getSegments();
this._getSegments(true);
// ...and with cols & rows
this._perfectMasonryGetSegments();
// Handle liquid layout (cols, rows & sizes must be calculated on the fly)
if(isLiquid) {
var width = $container.width(),
height = $container.height();
// Make sure we have colwidth & rowheight (get it from the calculated )
this.options.perfectMasonry.columnWidth = this.options.perfectMasonry.columnWidth || properties.columnWidth;
this.options.perfectMasonry.rowHeight = this.options.perfectMasonry.rowHeight || properties.rowHeight;
// Figure out how many cols & rows either have been set or can be fit into the container (also make sure we're still between min/max)
properties.cols = this.options.perfectMasonry.cols || Math.floor(width / this.options.perfectMasonry.columnWidth);
properties.rows = this.options.perfectMasonry.rows || Math.floor(height / this.options.perfectMasonry.rowHeight);
properties.cols = Math.min(Math.max(properties.cols, this.options.perfectMasonry.minCols), this.options.perfectMasonry.maxCols);
properties.rows = Math.min(Math.max(properties.rows, this.options.perfectMasonry.minRows), this.options.perfectMasonry.maxRows);
// Recalculate accurate width/height so that the whole available space is used
var diff = (isVertical ? properties.columnWidth / (width / properties.cols) : properties.rowHeight / (height / properties.rows));
properties.columnWidth = Math.floor(properties.columnWidth / diff);
properties.rowHeight = Math.floor(properties.rowHeight / diff);
}
// Create top row of the grid
properties.grid = new Array(this.perfectMasonry.cols);
// Set container dimensions to 0
properties.containerHeight = 0;
properties.containerWidth = 0;
},
/**
* Create layout
* -------------------------------------------------------------------------------------------------------- */
_perfectMasonryLayout: function($elems) {
var instance = this,
properties = this.perfectMasonry,
isVertical = instance.options.perfectMasonry.layout != 'horizontal',
isLiquid = instance.options.perfectMasonry.liquid == true;
// Create first set of the grid
properties.grid = new Array(properties[(isVertical ? 'cols' : 'rows')]);
if(!properties.grid || !properties.grid.length) { return; }
// Loop each element
$elems.each(function() {
var $this = $(this);
// Calculate col & row spans (with liquid layouts, store desired width as element data)
var colSpan = (isLiquid ? $this.data('colSpan') : Math.ceil($this.outerWidth() / (properties.columnWidth + 1))),
rowSpan = (isLiquid ? $this.data('rowSpan') : Math.ceil($this.outerHeight() / (properties.rowHeight + 1)));
// For the first run with liquid layout, calculate sizes
if(!colSpan) {
colSpan = Math.ceil($this.outerWidth(true) / (properties.columnWidth + 1));
rowSpan = Math.ceil($this.outerHeight(true) / (properties.rowHeight + 1));
$this.data('colSpan', colSpan);
$this.data('rowSpan', rowSpan);
}
/* Do the layout
* -------------------------------------------------------------------------------- */
// Set spans
var aSpan = (isVertical ? colSpan : rowSpan);
var bSpan = (isVertical ? rowSpan : colSpan);
// Bigger tiles can't fit into the last primary (though keep it still at least as 1)
var max = Math.max((isVertical ? properties.cols - colSpan : properties.rows - rowSpan) + 1, 1);
// Loop through/create primaries (set hard limit of 10.000 to prevent endless loop)
var a = -1, x = 0, y = 0;
while(++a < 10000) {
properties.grid[a] = properties.grid[a] || [];
// Go through the secondaries in the primary, set secondary and tile
for (var b = 0; b < max; b++) {
var tile = properties.grid[a][b];
// If the tile is not free, move to the next one immediately
if(tile) { continue; }
// Tiles spanning to multiple rows/columns - Check if it'll fit
var doesFit = true;
if(colSpan > 1 || rowSpan > 1) {
for (var i = 0; i < aSpan; i++) {
for (var j = 0; j < bSpan; j++) {
properties.grid[a+j] = properties.grid[a+j] || [];
if(properties.grid[a+j][b+i]) { doesFit = false; break; }
}
// If it doesn't fit, don't waste our time looping
if(!doesFit) { break; }
}
}
if(!doesFit) { continue }
// Set all the cells as occupied
for (var i = 0; i < aSpan; i++) {
for (var j = 0; j < bSpan; j++) {
properties.grid[a+j][b+i] = true;
}
}
// Set x & y values
var x = a, y = b;
if(isVertical) { var x = b, y = a; }
// Update container dimensions
properties.containerWidth = Math.max(properties.containerWidth, (x + aSpan) * properties.columnWidth);
properties.containerHeight = Math.max(properties.containerHeight, (y + bSpan) * properties.rowHeight);
// In case of liquid layout, set element size full width/height
if(instance.options.perfectMasonry.liquid == true) {
$this.css({
width: properties.columnWidth * colSpan,
height: properties.rowHeight * rowSpan
});
}
// Set the element location and GTFO
instance._pushPosition($this, x*properties.columnWidth, y*properties.rowHeight);
return;
}
}
// If we got all the way down to here, the element doesn't fit - Hide it
instance._pushPosition($this, -9999, -9999);
});
// Set row & column count to container
var rows = (isVertical ? properties.grid.length : properties.grid[0] && properties.grid[0].length),
cols = (isVertical ? properties.grid[0] && properties.grid[0].length : properties.grid.length);
$(this.element.context).attr('data-isotope-rows', rows).attr('data-isotope-cols', cols);
},
/**
* Get container size
*
* For resizing the container
* -------------------------------------------------------------------------------------------------------- */
_perfectMasonryGetContainerSize: function() {
return {
width: this.perfectMasonry.containerWidth,
height: this.perfectMasonry.containerHeight
};
},
/**
* Resize changed
*
* Figure out if layout changed
* -------------------------------------------------------------------------------------------------------- */
_perfectMasonryResizeChanged: function() {
var properties = this.perfectMasonry;
// Store old values and calculate new numbers
var oldCols = properties.cols,
oldRows = properties.rows;
this._perfectMasonryGetSegments();
// If new count was different, force layout change
if(this.options.perfectMasonry.layout == 'horizontal' && oldRows !== properties.rows) { return true; }
if(oldCols !== properties.cols) { return true; }
return false;
},
/**
* Private
* Do segment calculations by hand
* -------------------------------------------------------------------------------------------------------- */
_perfectMasonryGetSegments: function() {
var properties = this.perfectMasonry;
var parent = this.options.perfectMasonry.parent || this.element.parent();
// Calculate columns
var parentWidth = parent.width();
properties.cols = Math.floor(parentWidth / properties.columnWidth) || 1;
// Calculate rows
var parentHeight = parent.height();
properties.rows = Math.floor(parentHeight / properties.rowHeight) || 1;
}
});
})(jQuery);