123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368 |
- /**
- * Mosaic Flow
- *
- * Pinterest like responsive image grid that doesnt suck
- *
- * @requires jQuery
- * @author Artem Sapegin
- * @copyright 2012 Artem Sapegin, http://sapegin.me
- * @license MIT
- */
- /* taken at commit https://github.com/sapegin/jquery.mosaicflow/commit/20cea9ae73bab6bfeda54434564aaf0f86bed9ac
- * which includes fn.load()
- /*
- /*jshint browser:true, jquery:true, white:false, smarttabs:true */
- /*global jQuery:false, define:false*/
- (function(factory) { // Try to register as an anonymous AMD module
- if (typeof define === 'function' && define.amd) {
- define(['jquery'], factory);
- }
- else {
- factory(jQuery);
- }
- }(function($) {
- 'use strict';
- var cnt = 0;
- $.fn.mosaicflow = function(options) {
- var args = Array.prototype.slice.call(arguments, 0);
- return this.each(function() {
- var elm = $(this);
- var data = elm.data('mosaicflow');
- if (!data) {
- options = $.extend({}, $.fn.mosaicflow.defaults, options, dataToOptions(elm));
- data = new Mosaicflow(elm, options);
- elm.data('mosaicflow', data);
- }
- else if (typeof options === 'string') {
- data[options](args[1]);
- }
- });
- };
- $.fn.mosaicflow.defaults = {
- itemSelector: '> *',
- columnClass: 'mosaicflow__column',
- minItemWidth: 240,
- minColumns: 2,
- itemHeightCalculation: 'auto',
- threshold: 40
- };
- function Mosaicflow(container, options) {
- this.container = container;
- this.options = options;
- this.container.trigger('mosaicflow-start');
- this.init();
- this.container.trigger('mosaicflow-ready');
- }
- Mosaicflow.prototype = {
- init: function() {
- this.__uid = cnt++;
- this.__uidItemCounter = 0;
- this.items = this.container.find(this.options.itemSelector);
- this.columns = $([]);
- this.columnsHeights = [];
- this.itemsHeights = {};
- this.tempContainer = $('<div>').css({'visibility': 'hidden', 'width': '100%'});
- this.workOnTemp = false;
- this.autoCalculation = this.options.itemHeightCalculation === 'auto';
- this.container.append(this.tempContainer);
- var that = this;
- this.items.each(function() {
- var elm = $(this);
- var id = elm.attr('id');
- if (!id) {
- // Generate an unique id
- id = that.generateUniqueId();
- elm.attr('id', id);
- }
- });
- this.container.css('visibility', 'hidden');
- if (this.autoCalculation) {
- $(window).on('load', $.proxy(this.refill, this));
- }
- else {
- this.refill();
- }
- $(window).resize($.proxy(this.refill, this));
- },
- refill: function() {
- this.container.trigger('mosaicflow-fill');
- this.numberOfColumns = Math.floor(this.container.width() / this.options.minItemWidth);
- // Always keep min columns number
- if (this.numberOfColumns < this.options.minColumns)
- this.numberOfColumns = this.options.minColumns;
- var needToRefill = this.ensureColumns();
- if (needToRefill) {
- this.fillColumns();
- // Remove excess columns, only if there are visible columns remaining
- if (this.columns.filter(':visible').length > 0) {
- this.columns.filter(':hidden').remove();
- }
- }
- this.container.css('visibility', 'visible');
- this.container.trigger('mosaicflow-filled');
- },
- ensureColumns: function() {
- var createdCnt = this.columns.filter(':visible').length;
- var calculatedCnt = this.numberOfColumns;
- this.workingContainer = createdCnt === 0 ? this.tempContainer : this.container;
- if (calculatedCnt > createdCnt) {
- var neededCnt = calculatedCnt - createdCnt;
- for (var columnIdx = 0; columnIdx < neededCnt; columnIdx++) {
- var column = $('<div>', {
- 'class': this.options.columnClass
- });
- this.workingContainer.append(column);
- }
- }
- else if (calculatedCnt < createdCnt) {
- var lastColumn = createdCnt;
- while (calculatedCnt <= lastColumn) {
- // We can't remove columns here becase it will remove items to. So we hide it and will remove later.
- this.columns.eq(lastColumn).hide();
- lastColumn--;
- }
- var diff = createdCnt - calculatedCnt;
- this.columnsHeights.splice(this.columnsHeights.length - diff, diff);
- }
- if (calculatedCnt !== createdCnt) {
- this.columns = this.workingContainer.find('.' + this.options.columnClass);
- this.columns.css('width', (100 / calculatedCnt) + '%');
- return true;
- }
- return false;
- },
- fillColumns: function() {
- var columnsCnt = this.numberOfColumns;
- var itemsCnt = this.items.length;
- for (var columnIdx = 0; columnIdx < columnsCnt; columnIdx++) {
- var column = this.columns.eq(columnIdx);
- this.columnsHeights[columnIdx] = 0;
- for (var itemIdx = columnIdx; itemIdx < itemsCnt; itemIdx += columnsCnt) {
- var item = this.items.eq(itemIdx);
- var height = 0;
- column.append(item);
- if (this.autoCalculation) {
- // Check height after being placed in its column
- height = item.outerHeight();
- }
- else {
- // Read img height attribute
- height = parseInt(item.find('img').attr('height'), 10);
- }
- this.itemsHeights[item.attr('id')] = height;
- this.columnsHeights[columnIdx] += height;
- }
- }
- this.levelBottomEdge(this.itemsHeights, this.columnsHeights);
- if (this.workingContainer === this.tempContainer) {
- this.container.append(this.tempContainer.children());
- }
- this.container.trigger('mosaicflow-layout');
- },
- levelBottomEdge: function(itemsHeights, columnsHeights) {
- while (true) {
- var lowestColumn = $.inArray(Math.min.apply(null, columnsHeights), columnsHeights);
- var highestColumn = $.inArray(Math.max.apply(null, columnsHeights), columnsHeights);
- if (lowestColumn === highestColumn) return;
- var lastInHighestColumn = this.columns.eq(highestColumn).children().last();
- var lastInHighestColumnHeight = itemsHeights[lastInHighestColumn.attr('id')];
- var lowestHeight = columnsHeights[lowestColumn];
- var highestHeight = columnsHeights[highestColumn];
- var newLowestHeight = lowestHeight + lastInHighestColumnHeight;
- if (newLowestHeight >= highestHeight) return;
- if (highestHeight - newLowestHeight < this.options.threshold) return;
- this.columns.eq(lowestColumn).append(lastInHighestColumn);
- columnsHeights[highestColumn] -= lastInHighestColumnHeight;
- columnsHeights[lowestColumn] += lastInHighestColumnHeight;
- }
- },
- add: function(elm) {
- this.container.trigger('mosaicflow-item-add', [elm]);
- var lowestColumn = $.inArray(Math.min.apply(null, this.columnsHeights), this.columnsHeights);
- var height = 0;
- if (this.autoCalculation) {
- // Get height of elm
- elm.css({
- position: 'static',
- visibility: 'hidden',
- display: 'block'
- }).appendTo(this.columns.eq(lowestColumn));
- height = elm.outerHeight();
- var inlineImages = elm.find('img');
- if (inlineImages.length !== 0) {
- inlineImages.each(function() {
- var image = $(this);
- var imageSizes = getImageSizes(image);
- var actualHeight = (image.width() * imageSizes.height) / imageSizes.width;
- height += actualHeight;
- });
- }
- elm.detach().css({
- position: 'static',
- visibility: 'visible'
- });
- }
- else {
- height = parseInt(elm.find('img').attr('height'), 10);
- }
- if (!elm.attr('id')) {
- // Generate a unique id
- elm.attr('id', this.generateUniqueId());
- }
- // Update item collection.
- // Item needs to be placed at the end of this.items to keep order of elements
- var itemsArr = this.items.toArray();
- itemsArr.push(elm);
- this.items = $(itemsArr);
- this.itemsHeights[elm.attr('id')] = height;
- this.columnsHeights[lowestColumn] += height;
- this.columns.eq(lowestColumn).append(elm);
- this.levelBottomEdge(this.itemsHeights, this.columnsHeights);
- this.container.trigger('mosaicflow-layout');
- this.container.trigger('mosaicflow-item-added', [elm]);
- },
- remove: function(elm) {
- this.container.trigger('mosaicflow-item-remove', [elm]);
- var column = elm.parents('.' + this.options.columnClass);
- // Update column height
- this.columnsHeights[column.index() - 1] -= this.itemsHeights[elm.attr('id')];
- elm.detach();
- // Update item collection
- this.items = this.items.not(elm);
- this.levelBottomEdge(this.itemsHeights, this.columnsHeights);
- this.container.trigger('mosaicflow-layout');
- this.container.trigger('mosaicflow-item-removed', [elm]);
- },
- empty: function() {
- var columnsCnt = this.numberOfColumns;
- this.items = $([]);
- this.itemsHeights = {};
- for (var columnIdx = 0; columnIdx < columnsCnt; columnIdx++) {
- var column = this.columns.eq(columnIdx);
- this.columnsHeights[columnIdx] = 0;
- column.empty();
- }
- this.container.trigger('mosaicflow-layout');
- },
- recomputeHeights: function() {
- function computeHeight(idx, item) {
- item = $(item);
- var height = 0;
- if (that.autoCalculation) {
- // Check height after being placed in its column
- height = item.outerHeight();
- }
- else {
- // Read img height attribute
- height = parseInt(item.find('img').attr('height'), 10);
- }
- that.itemsHeights[item.attr('id')] = height;
- that.columnsHeights[columnIdx] += height;
- }
- var that = this;
- var columnsCnt = this.numberOfColumns;
- for (var columnIdx = 0; columnIdx < columnsCnt; columnIdx++) {
- var column = this.columns.eq(columnIdx);
- this.columnsHeights[columnIdx] = 0;
- column.children().each(computeHeight);
- }
- },
- generateUniqueId: function() {
- // Increment the counter
- this.__uidItemCounter++;
- // Return an unique ID
- return 'mosaic-' + this.__uid + '-itemid-' + this.__uidItemCounter;
- }
- };
- // Camelize data-attributes
- function dataToOptions(elem) {
- function upper(m, l) {
- return l.toUpper();
- }
- var options = {};
- var data = elem.data();
- for (var key in data) {
- options[key.replace(/-(\w)/g, upper)] = data[key];
- }
- return options;
- }
- function getImageSizes(image) {
- var sizes = {};
- sizes.height = parseInt(image.attr('height'), 10);
- sizes.width = parseInt(image.attr('width'), 10);
- if (sizes.height === 0 || sizes.width === 0) {
- var utilImage = new Image();
- utilImage.src = image.attr('src');
- sizes.width = utilImage.width;
- sizes.height = utilImage.height;
- }
- return sizes;
- }
- // Auto init
- $(function() { $('.mosaicflow').mosaicflow(); });
- }));
|