Last active
December 19, 2015 08:29
-
-
Save soyuka/5925673 to your computer and use it in GitHub Desktop.
Bin-Packing adaptation for isotope. This plugin also resize images container to fit the given grid, see comments. It performs a perfect-masonry like, by sorting elements through height.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/*! | |
* Bin-Packing by jakesgordon | |
* https://github.com/jakesgordon/bin-packing/ | |
* Demo : http://codeincomplete.com/posts/2011/5/7/bin_packing/example/ | |
* Licence : https://github.com/jakesgordon/bin-packing/blob/master/LICENSE | |
*/ | |
Packer = function(w, h) { | |
this.init(w, h); | |
}; | |
Packer.prototype = { | |
init: function(w, h) { | |
this.root = { x: 0, y: 0, w: w, h: h }; | |
}, | |
fit: function(blocks) { | |
var n, node, block; | |
for (n = 0; n < blocks.length; n++) { | |
block = blocks[n]; | |
if (node = this.findNode(this.root, block.w, block.h)) | |
block.fit = this.splitNode(node, block.w, block.h); | |
} | |
}, | |
findNode: function(root, w, h) { | |
if (root.used) | |
return this.findNode(root.right, w, h) || this.findNode(root.down, w, h); | |
else if ((w <= root.w) && (h <= root.h)) | |
return root; | |
else | |
return null; | |
}, | |
splitNode: function(node, w, h) { | |
node.used = true; | |
node.down = { x: node.x, y: node.y + h, w: node.w, h: node.h - h }; | |
node.right = { x: node.x + w, y: node.y, w: node.w - w, h: h }; | |
return node; | |
} | |
} | |
/*! | |
* Packer extension for Isotope | |
* | |
* Adaptation from bin-packing to isotope | |
* | |
* Usage : | |
* $('#container').isotope({ | |
* layoutMode: "packer", | |
* packer: { | |
* 'realWidth': '80%' //real width | |
* ,'nbColumns': 12 //Nb of cols | |
* ,'nbMaxCols': 4 //Max cols for the picture container | |
* ,'nbMaxRows': 6 //Max row for the picture container | |
* ,'picturesClass': '.miniature' //Pictures class | |
* } | |
* }); | |
* Add this css to the picture container : | |
* .miniature { | |
* position: relative; | |
* overflow:hidden; | |
* } | |
* | |
* @author Soyuka for EzSeed - https://github.com/soyuka/EzSeed | |
*/ | |
;(function($, undefined) { | |
$.extend($.Isotope.prototype, { | |
/** | |
* Reset layout properties | |
* | |
* Runs before any layout change | |
* -------------------------------------------------------------------------------------------------------- */ | |
_packerReset: function() { | |
// Setup layout properties | |
var that = this | |
,prop = that.packer = { | |
nbColumns : 12, | |
realWidth : '80%', | |
nbMaxCols : 3, | |
nbMaxRows : 2 | |
}; | |
prop = $.extend(prop, that.options.packer); | |
// Calculate cols & rows, resizes DOM elements | |
that.element.imagesLoaded(function() { | |
that._packerGetSegments(); | |
}); | |
}, | |
/** | |
* Create layout | |
* -------------------------------------------------------------------------------------------------------- */ | |
_packerLayout: function($elems) { | |
var prop = this.packer; | |
//setting the grid and resize DOM to fit the grid | |
this._packerSetGrid(); | |
var elements = []; | |
//Getting each elements heights/widths | |
$elems.each(function(i, e) { | |
var element = { | |
$el : $(e), | |
w : $(e).outerWidth(), | |
h : $(e).outerHeight() | |
}; | |
elements.push(element); | |
}); | |
//Calcs the max height to make sure all fits | |
var height = 0; | |
for (var i = 0; i < elements.length; i++) | |
height += parseInt(elements[i].h); | |
prop.rowHeight = height; | |
var packer = new Packer(this.element.width(), prop.rowHeight); | |
//Sort functions from bin-packing js | |
var sort = { | |
random : function (a,b) { return Math.random() - 0.5; }, | |
w : function (a,b) { return b.w - a.w; }, | |
h : function (a,b) { return b.h - a.h; }, | |
a : function (a,b) { return b.area - a.area; }, | |
max : function (a,b) { return Math.max(b.w, b.h) - Math.max(a.w, a.h); }, | |
min : function (a,b) { return Math.min(b.w, b.h) - Math.min(a.w, a.h); }, | |
height : function (a,b) { return sort.msort(a, b, ['h', 'w']); }, | |
width : function (a,b) { return sort.msort(a, b, ['w', 'h']); }, | |
area : function (a,b) { return sort.msort(a, b, ['a', 'h', 'w']); }, | |
maxside : function (a,b) { return sort.msort(a, b, ['max', 'min', 'h', 'w']); }, | |
/* sort by multiple criteria */ | |
msort: function(a, b, criteria) { | |
var diff, n; | |
for (n = 0 ; n < criteria.length ; n++) { | |
diff = sort[criteria[n]](a,b); | |
if (diff != 0) | |
return diff; | |
} | |
return 0; | |
} | |
} | |
//Could change but it seems to be the better sort | |
elements.sort(sort.width); | |
packer.fit(elements); | |
var newHeight = 0; | |
for(var n = 0 ; n < elements.length ; n++) { | |
var block = elements[n]; | |
if (block.fit) { | |
if(block.fit.x == 0) | |
newHeight += block.h; | |
this._pushPosition( block.$el, block.fit.x, block.fit.y ); | |
} else { | |
//should not append cause the height has been calculated with all elements height | |
console.log('No fit', block); | |
} | |
} | |
this.packer.rowHeight = newHeight; | |
}, | |
/** | |
* Get container size | |
* | |
* Resizes the container | |
* -------------------------------------------------------------------------------------------------------- */ | |
_packerGetContainerSize: function() { | |
return { | |
width: this.packer.realWidth, | |
height: this.packer.rowHeight | |
}; | |
}, | |
/** | |
* Resize changed | |
* | |
* Figure out if layout changed | |
* -------------------------------------------------------------------------------------------------------- */ | |
_packerResizeChanged: function() { | |
var prop = this.packer; | |
this._packerGetSegments(); | |
return true; | |
}, | |
/** | |
* Get segments | |
* -------------------------------------------------------------------------------------------------------- */ | |
_packerGetSegments: function() { | |
var prop = this.packer; | |
this._getSegments(); | |
this._getSegments(true); | |
}, | |
/** | |
* Sets a grid by the maxCols/maxRows | |
* It's used for resizing the pictures container | |
*/ | |
_packerSetGrid: function() { | |
var prop = this.packer | |
,grid = [] | |
,n = 0; | |
prop.columnWidth = this.element.width() / prop.nbColumns; | |
for(var i = 1; i < prop.nbMaxCols; i++) { | |
for(var j = 1; j < prop.nbMaxRows; j++) { | |
grid[n] = { | |
w : prop.columnWidth * i, | |
h : prop.columnWidth * j, | |
i : i, | |
j : j | |
}; | |
n++; | |
} | |
} | |
prop.grid = grid; | |
this._resizeDOMGrid(); | |
}, | |
/** | |
* Resizes pictures containers | |
* It's used for resizing the pictures container | |
*/ | |
_resizeDOMGrid: function() { | |
var that = this, prop = this.packer; | |
prop.columnWidth = this.element.width() / prop.nbColumns; | |
var elements = []; | |
this.element.find(prop.picturesClass).each( | |
function(i, e) { | |
var $titre = $(e).find('div.titre'), | |
$min = $(e).find('img'); | |
//This parts works with entypo icons <i class=""></i> and sets a proper font-size | |
if($min.width() == undefined) { | |
$(e).css({'width': prop.columnWidth*2, 'height': prop.columnWidth*2}).find('i').css({'font-size': prop.columnWidth*2}); | |
//$(e).attr('data-x', 2).attr('data-y', 2); | |
var newImage = {}; | |
newImage.w = prop.columnWidth*2; | |
newImage.h = prop.columnWidth*2; | |
//It's a picture | |
} else { | |
var image = { | |
w : $min.prop('naturalWidth'), | |
h : $min.prop('naturalHeight') | |
}; | |
var newImage = that._imageDimensions(image); //see below | |
$(e).css({'width': newImage.w, 'height': newImage.h}); | |
if(newImage.square) | |
$(e).find('img').css({'max-width': '100%'}); | |
if(image.h > newImage.h + prop.columnWidth || (image.h > newImage.h + prop.columnWidth && image.w > newImage.w + prop.columnWidth)) | |
$(e).find('img').css({'max-height': '100%'}); | |
} | |
} | |
); | |
}, | |
/* | |
* Calculates the grid elements dimensions through pictures widths and heights | |
* Works with a grid see set grid | |
* @param image : MAP {width, height} | |
* @param grid : grid | |
* -------------------------------------------------------------------------------------------------------- */ | |
_imageDimensions: function(image) { | |
var grid = this.packer.grid; | |
var newImage = function(w, h, x, y) { | |
return { | |
'w': w, | |
'h' : h, | |
'grid' : { | |
'x' : x, | |
'y' : y | |
} | |
}; | |
}, | |
n = grid.length, | |
square = image.h / image.w == 1 ? true : false; | |
for (var i = 0; i < n; i++) { | |
//not last | |
if(i != n - 1) { | |
if(image.w >= grid[i].w && image.w < grid[i+1].w) { | |
if(square) { | |
var container = newImage(grid[i].w, grid[i].w, grid[i].i, grid[i].i); | |
i = n; | |
} else { | |
for (var j = 0; j < n; j++) { | |
if(j != n - 1) { | |
if(image.h >= grid[j].h && image.h < grid[j+1].h) { | |
var container = newImage(grid[i].w, grid[j].h, grid[i].i, grid[j].j); | |
i = n; j = n; | |
} | |
//choice left | |
} else { | |
var container = newImage(grid[i].w, grid[j].h, grid[i].i, grid[j].j); | |
i = n; | |
} | |
} | |
} | |
} | |
//last width | |
} else { | |
if(square) { | |
var container = newImage(grid[i].w, grid[i].w, grid[i].i, grid[i].i); | |
i = n; | |
} else { | |
for (var j = 0; j < n; j++) { | |
if(j != n - 1) { | |
if(image.h >= grid[j].h && image.h < grid[j+1].h) { | |
var container = newImage(grid[i].w, grid[j].h, grid[i].i, grid[j].j); | |
i = n; j = n; | |
} | |
//choice left | |
} else { | |
var container = newImage(grid[i].w, grid[j].h, grid[i].i, grid[j].j); | |
i = n; | |
} | |
} | |
} | |
} | |
} | |
return container; | |
} | |
}); | |
})(jQuery); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment