PageSourceSearch

https://aurav.org/contents/js/mosaic-flow/jquery.mosaicflow.js

js aurav.org collected 2026-10-04 07:43:17 UTC 10,648 bytes, 388 lines download raw bytes

1/**
2 * Mosaic Flow
3 *
4 * Pinterest like responsive image grid that doesn’t sucks
5 *
6 * @requires jQuery
7 * @author Artem Sapegin
8 * @copyright 2012 Artem Sapegin, http://sapegin.me
9 * @license MIT
10 */
11
12/*jshint browser:true, jquery:true, white:false, smarttabs:true */
13/*global jQuery:false, define:false*/
14(function(factory) {  // Try to register as an anonymous AMD module
15	if (typeof define === 'function' && define.amd) {
16		define(['jquery'], factory);
17	}
18	else {
19		factory(jQuery);
20	}
21}(function($) {
22	'use strict';
23	var cnt = 0;
24
25	$.fn.mosaicflow = function(options) {
26		var args = Array.prototype.slice.call(arguments, 0);
27
28		return this.each(function() {
29			var elm = $(this);
30			var data = elm.data('mosaicflow');
31
32			if (!data) {
33				options = $.extend({}, $.fn.mosaicflow.defaults, options, dataToOptions(elm));
34				data = new Mosaicflow(elm, options);
35				elm.data('mosaicflow', data);
36			}
37			else if (typeof options === 'string') {
38				data[options](args[1]);
39			}
40		});
41	};
42
43	$.fn.mosaicflow.defaults = {
44		itemSelector: '> *',
45		columnClass: 'mosaicflow__column',
46		minItemWidth: 240,
47		minColumns: 1,
48		itemHeightCalculation: 'auto',
49		threshold: 40,
50		lg:5,
51		md:4,
52		sm:2,
53		xs:1
54	};
55
56	function Mosaicflow(container, options) {
57		this.container = container;
58		this.options = options;
59
60		this.container.trigger('mosaicflow-start');
61		this.init();
62		this.container.trigger('mosaicflow-ready');
63	}
64
65	Mosaicflow.prototype = {
66		init: function() {
67			this.__uid = cnt++;
68			this.__uidItemCounter = 0;
69			this.items = this.container.find(this.options.itemSelector);
70			this.columns = $([]);
71			this.columnsHeights = [];
72			this.itemsHeights = {};
73			this.tempContainer = $('<div>').css({'visibility': 'hidden', 'width': '100%'});
74			this.workOnTemp = false;
75			this.autoCalculation = this.options.itemHeightCalculation === 'auto';
76
77			this.container.append(this.tempContainer);
78
79			var that = this;
80			this.items.each(function() {
81				var elm = $(this);
82				var id = elm.attr('id');
83				if (!id) {
84					// Generate an unique id
85					id = that.generateUniqueId();
86					elm.attr('id', id);
87				}
88			});
89
90			this.container.css('visibility', 'hidden');
91			if (this.autoCalculation) {
92				$(window).on('load', $.proxy(this.refill, this));
93			}
94			else {
95				this.refill();
96			}
97			$(window).resize($.proxy(this.refill, this));
98		},
99
100		refill: function() {
101			this.container.trigger('mosaicflow-fill');
102			//this.numberOfColumns = Math.floor(this.container.width() / this.options.minItemWidth);
103			
104			if($( document ).width() >= 1200)
105			{
106				this.numberOfColumns = this.options.lg;
107			} else {
108				if($( document ).width() >= 992)
109				{
110					this.numberOfColumns = this.options.md;
111				} else {
112					if($( document ).width() >= 768)
113					{
114						this.numberOfColumns = this.options.sm;
115					} else {
116						this.numberOfColumns = this.options.xs;
117					}
118				}
119			}
120			
121			// Always keep min columns number
122			if (this.numberOfColumns < this.options.minColumns)
123				this.numberOfColumns = this.options.minColumns;
124
125			var needToRefill = this.ensureColumns();
126			if (needToRefill) {
127				this.fillColumns();
128
129				// Remove excess columns, only if there are visible columns remaining
130				if (this.columns.filter(':visible').length > 0) {
131					this.columns.filter(':hidden').remove();
132				}
133			}
134			this.container.css('visibility', 'visible');
135			this.container.trigger('mosaicflow-filled');
136		},
137
138		ensureColumns: function() {
139			var createdCnt = this.columns.filter(':visible').length;
140			var calculatedCnt = this.numberOfColumns;
141
142			this.workingContainer = createdCnt === 0 ? this.tempContainer : this.container;
143
144			if (calculatedCnt > createdCnt) {
145				var neededCnt = calculatedCnt - createdCnt;
146				for (var columnIdx = 0; columnIdx < neededCnt; columnIdx++) {
147					var column = $('<div>', {
148						'class': this.options.columnClass
149					});
150
151					this.workingContainer.append(column);
152				}
153			}
154			else if (calculatedCnt < createdCnt) {
155				var lastColumn = createdCnt;
156				while (calculatedCnt <= lastColumn) {
157					// We can't remove columns here becase it will remove items to. So we hide it and will remove later.
158					this.columns.eq(lastColumn).hide();
159					lastColumn--;
160				}
161
162				var diff = createdCnt - calculatedCnt;
163				this.columnsHeights.splice(this.columnsHeights.length - diff, diff);
164			}
165
166			if (calculatedCnt !== createdCnt) {
167				this.columns = this.workingContainer.find('.' + this.options.columnClass);
168				this.columns.css('width', (100 / calculatedCnt) + '%');
169				return true;
170			}
171
172			return false;
173		},
174
175		fillColumns: function() {
176			var columnsCnt = this.numberOfColumns;
177			var itemsCnt = this.items.length;
178
179			for (var columnIdx = 0; columnIdx < columnsCnt; columnIdx++) {
180				var column = this.columns.eq(columnIdx);
181				this.columnsHeights[columnIdx] = 0;
182				for (var itemIdx = columnIdx; itemIdx < itemsCnt; itemIdx += columnsCnt) {
183					var item = this.items.eq(itemIdx);
184					var height = 0;
185					column.append(item);
186
187					if (this.autoCalculation) {
188						// Check height after being placed in its column
189						height = item.outerHeight();
190					}
191					else {
192						// Read img height attribute
193						height = parseInt(item.find('img').attr('height'), 10);
194					}
195
196					this.itemsHeights[item.attr('id')] = height;
197					this.columnsHeights[columnIdx] += height;
198				}
199			}
200
201			this.levelBottomEdge(this.itemsHeights, this.columnsHeights);
202
203			if (this.workingContainer === this.tempContainer) {
204				this.container.append(this.tempContainer.children());
205			}
206			this.container.trigger('mosaicflow-layout');
207		},
208
209		levelBottomEdge: function(itemsHeights, columnsHeights) {
210			while (true) {
211				var lowestColumn = $.inArray(Math.min.apply(null, columnsHeights), columnsHeights);
212				var highestColumn = $.inArray(Math.max.apply(null, columnsHeights), columnsHeights);
213				if (lowestColumn === highestColumn) return;
214
215				var lastInHighestColumn = this.columns.eq(highestColumn).children().last();
216				var lastInHighestColumnHeight = itemsHeights[lastInHighestColumn.attr('id')];
217				var lowestHeight = columnsHeights[lowestColumn];
218				var highestHeight = columnsHeights[highestColumn];
219				var newLowestHeight = lowestHeight + lastInHighestColumnHeight;
220
221				if (newLowestHeight >= highestHeight) return;
222
223				if (highestHeight - newLowestHeight < this.options.threshold) return;
224
225				this.columns.eq(lowestColumn).append(lastInHighestColumn);
226				columnsHeights[highestColumn] -= lastInHighestColumnHeight;
227				columnsHeights[lowestColumn] += lastInHighestColumnHeight;
228			}
229		},
230
231		add: function(elm) {
232			this.container.trigger('mosaicflow-item-add', [elm]);
233			var lowestColumn = $.inArray(Math.min.apply(null, this.columnsHeights), this.columnsHeights);
234			var height = 0;
235
236			if (this.autoCalculation) {
237
238				// Get height of elm
239				elm.css({
240					position: 'static',
241					visibility: 'hidden',
242					display: 'block'
243				}).appendTo(this.columns.eq(lowestColumn));
244
245				height = elm.outerHeight();
246
247				var inlineImages = elm.find('img');
248				if (inlineImages.length !== 0) {
249					inlineImages.each(function() {
250						var image = $(this);
251						var imageSizes = getImageSizes(image);
252						var actualHeight = (image.width() * imageSizes.height) / imageSizes.width;
253
254						height += actualHeight;
255					});
256				}
257
258				elm.detach().css({
259					position: 'static',
260					visibility: 'visible'
261				});
262			}
263			else {
264				height = parseInt(elm.find('img').attr('height'), 10);
265			}
266
267			if (!elm.attr('id')) {
268				// Generate a unique id
269				elm.attr('id', this.generateUniqueId());
270			}
271
272			// Update item collection.
273			// Item needs to be placed at the end of this.items to keep order of elements
274			var itemsArr = this.items.toArray();
275			itemsArr.push(elm);
276			this.items = $(itemsArr);
277
278			this.itemsHeights[elm.attr('id')] = height;
279			this.columnsHeights[lowestColumn] += height;
280			this.columns.eq(lowestColumn).append(elm);
281
282			this.levelBottomEdge(this.itemsHeights, this.columnsHeights);
283			this.container.trigger('mosaicflow-layout');
284			this.container.trigger('mosaicflow-item-added', [elm]);
285		},
286
287		remove: function(elm) {
288			this.container.trigger('mosaicflow-item-remove', [elm]);
289			var column = elm.parents('.' + this.options.columnClass);
290
291			// Update column height
292			this.columnsHeights[column.index() - 1] -= this.itemsHeights[elm.attr('id')];
293
294			elm.detach();
295
296			// Update item collection
297			this.items = this.items.not(elm);
298			this.levelBottomEdge(this.itemsHeights, this.columnsHeights);
299			this.container.trigger('mosaicflow-layout');
300			this.container.trigger('mosaicflow-item-removed', [elm]);
301		},
302
303		empty: function() {
304			var columnsCnt = this.numberOfColumns;
305
306			this.items = $([]);
307			this.itemsHeights = {};
308
309			for (var columnIdx = 0; columnIdx < columnsCnt; columnIdx++) {
310				var column = this.columns.eq(columnIdx);
311				this.columnsHeights[columnIdx] = 0;
312				column.empty();
313			}
314			this.container.trigger('mosaicflow-layout');
315		},
316
317		recomputeHeights: function() {
318			function computeHeight(idx, item) {
319				item = $(item);
320				var height = 0;
321				if (that.autoCalculation) {
322					// Check height after being placed in its column
323					height = item.outerHeight();
324				}
325				else {
326					// Read img height attribute
327					height = parseInt(item.find('img').attr('height'), 10);
328				}
329
330				that.itemsHeights[item.attr('id')] = height;
331				that.columnsHeights[columnIdx] += height;
332			}
333
334			var that = this;
335			var columnsCnt = this.numberOfColumns;
336
337			for (var columnIdx = 0; columnIdx < columnsCnt; columnIdx++) {
338				var column = this.columns.eq(columnIdx);
339
340				this.columnsHeights[columnIdx] = 0;
341				column.children().each(computeHeight);
342			}
343		},
344
345		generateUniqueId: function() {
346			// Increment the counter
347			this.__uidItemCounter++;
348
349			// Return an unique ID
350			return 'mosaic-' + this.__uid + '-itemid-' + this.__uidItemCounter;
351		}
352	};
353
354	// Camelize data-attributes
355	function dataToOptions(elem) {
356		function upper(m, l) {
357			return l.toUpper();
358		}
359		var options = {};
360		var data = elem.data();
361		for (var key in data) {
362			options[key.replace(/-(\w)/g, upper)] = data[key];
363		}
364		return options;
365	}
366
367	function getImageSizes(image) {
368		var sizes = {};
369
370		sizes.height = parseInt(image.attr('height'), 10);
371		sizes.width = parseInt(image.attr('width'), 10);
372
373		if (sizes.height === 0 || sizes.width === 0) {
374			var utilImage = new Image();
375			utilImage.src = image.attr('src');
376
377			sizes.width = utilImage.width;
378			sizes.height = utilImage.height;
379		}
380
381		return sizes;
382	}
383
384	// Auto init
385	/*$(function() { $('#mo1').mosaicflow({lg: 5, md:4, sm:3, xs:1}); });
386	$(function() { $('#mo2').mosaicflow({lg: 4, md:4, sm:2, xs:2}); });*/
387
388}));

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.