I'm Dudley Storey, the author of Pro CSS3 Animation. This is my blog, where I talk about web design and development with HTML, CSS and SVG. To receive more information, including news, updates, and tips, you should follow me on Twitter or add me on Google+.

my books

Pro CSS3 Animation book coverPro CSS3 Animation, Apress, 2013

my other blogs

Massive Head CanonMassive Head Canon: Intelligent discussion of movies, books, games, and technology.

my projects

The New DefaultsThe New Defaults — A Sass color keyword system for designers.

CSSslidyCSSslidy — an auto-generated #RWD image slider. 3.8K of JS, no JQuery.

Modern Masonry with Flexbox and JavaScript

Arrange images of any aspect ratio into a grid with no plugins or frameworks required

For some time I have been attempting to recreate “masonry” effects in flexbox, where images are arranged like bricks in a wall. My previous attempt was moderately successful, but it ran ragged and lacked the dynamism I wanted.

Then, after working on the recent “Random Images With Flexbox and JavaScript” article, I had an epiphany: why not use JavaScript to read the image’s aspect ratios, and use that to determine the correct flex value for each element?

The solution – also available on CodePen – allows designers to load images of any dimension and aspect ratio into a container element, apply a class, and have a seamless image masonry effect generated automatically on the page using modern web standards, with no plugins or frameworks required.

The Loading Challenge

The script automatically rescales images of different sizes and aspect ratios to fit perfectly together

The first challenge is that an image must be completely loaded onto a page before JavaScript can determine anything about it: just having the <img> tag present is not enough. Historically, there are three main ways of dealing with this:

For the purposes of illustration I’ll use the first two techniques, although it should be noted that the second runs counter to the principles of progressive enhancement.

I’ll start with an empty <div>. The images to be inserted inside the <div> will have <figure> elements wrapped around them, so I’ll set up the styles for the expected DOM content:

* { box-sizing: border-box; }
.quantize { display: flex; flex-flow: row wrap; font-size: 0; width: 80%; margin: 0 auto; }
.quantize figure { margin: 0; }
.quantize figure img { width: 100%; height: auto; }

Then the first part of the JavaScript:

var container = document.getElementsByClassName('quantize')[0];
var butterflies = [ "orange-butterfly.jpg", "butterfly-on-yellow-flower.jpg", "butterfly-on-petal.jpg", "albino-butterfly.jpg", "blue-butterfly.jpg"];
function preloadImage(filename){
	var img=new Image();
	img.onload = function(){
	img.aspectRatio = img.naturalWidth / img.naturalHeight;
	var fig = document.createElement('figure');
	fig.appendChild(img);
	container.appendChild(fig);
	};
	img.src= filename;
	img.alt = "";
}
function loadImages() {
	for (var i = 0; i < butterflies.length; ++i) {
		var filename = butterflies[i];
		preloadImage(filename);
		}
	}

The first two lines of code identify the element into which the images will be inserted, and lists the images I want to load as an array. The preloadImage function will be fed the filenames from the array and create new image elements in the DOM from that information. Within that lies an onload method for each image, which creates a new property representing the ratio between the image’s natural width and height. With this information added to the image, the function wraps the image in a <figure> element and adds it inside the container. Note that the order is important: img.onload must be placed before the image source is assigned.

Next, we need a function that sorts the images by their aspect ratios, i.e. the number that was calculated earlier. I’ve created a fitFlex function to do just that:

function fitFlex() {
var flexGroup = container.querySelectorAll("figure");
var flexArray = Array.prototype.slice.call(flexGroup, 0);
flexArray.sort(function (a, b) { 
	imageAspectRatioA = a.firstElementChild.aspectRatio;
	imageAspectRatioB = b.firstElementChild.aspectRatio;
	if (imageAspectRatioA < imageAspectRatioB) { return 1; }
	if (imageAspectRatioA > imageAspectRatioB) { return -1; }
	return 0;
	});
	var widest = flexArray[0].firstElementChild.aspectRatio;
	var smallestWidth = "300";
	flexArray.forEach(function(box) {
		var flex = 1 / (widest / box.firstElementChild.aspectRatio);
		if (flex == 0) { flex = 1; }
		boxWidth = smallestWidth * flex;
		box.style.cssText = "flex: "+flex+"; min-width: "+boxWidth+"px;
		}); 
}

In brief, this code grabs all the created <figure> elements and places them into an array, sorting them so that the figure with the widest image appears first. (Note that this does not change the order in which the images actually appear on the page).

This element will be given a flex value of 1 and the min-width decided in the code, with every other image provided with a flex value and min-width relative to that. For example, the generated code for the first image in the series looks like this:

<figure style="flex: 0.938 1 0px; min-width: 281.425px;"><img src="orange-butterfly.jpg" alt></figure>

The result means that every image, no matter what its height and width, will fit neatly into a grid, once the two functions are called:

loadImages();
window.addEventListener("load", function() {
fitFlex(); }
);

I’m very pleased with the result (although it does have layout problems in Firefox, due to a browser bug) and intend to publish the script on GitHub with more options in the very near future.

Images by Tony Hisgett, plancas67, Alain Picard, Bill Gracey and Peter Weemeeuw, licensed under Creative Commons.

Explore the code for Modern Masonry on CodePen

This site helps millions of visitors while remaining ad-free. For less than the price of a cup of coffee, you can help pay for bandwidth and server costs while encouraging further articles.