Placing Content in a Randomly Generated Mosaic Effect with jQuery

May 15, 2012 at 5:00 pm By
Download Demo

If you’ve ever visited Pinterest or 500px, one of the most striking features is how content is loaded. In this tutorial we’re going to be taking a bunch of normal div’s and placing them into a mosaic of different sizes and shapes. It won’t even be that hard!

The main problem I have with the Pinterest site is when you reach the end of the content you end up with a jagged selection of boxes, rather than a more attractive flat ending. For that reason I’ve decided to take the same approach as 500px in order to create a flat surface at the bottom of the page. We’ll end up with a mosaic effect as is displayed in the demo, but the pattern will change every time the user refreshes.

Setting up the HTML

What we’re going to have initially is a set of div’s inside a holder. The div’s are set up in such a way that they randomly choose a shape or mosaic mould to fit into. I’ve decided though that the initial 5 div’s should be the same every time so as to act as a sort of header for everything else. It would be easy enough to change this though. So our HTML looks a bit like this:

<div id="holder">
	
	<!-- INITIAL BOXES, ALWAYS THE SAME -->
	
	<div class="box blue"> 
		<span>&#9733; &#9733; &#9733;</span> 
		<span>"Wonderful Execution!"</span>	
	</div>
	<div class="box box-1"><span>CSS</span></div>
	<div class="box box-2"></div>
	<div class="box box-3"></div>
	<div class="box box-4"><span>jQuery</span></div>
	
	<!-- END -->
	
	<div class="box"></div>
	<div class="box"></div>
	<div class="box"></div>
	<div class="box"></div>
	<div class="box"></div>
	<div class="box"></div>
	<div class="box"></div>
	<div class="box"></div>
	<div class="box"></div>
	<div class="box"></div>
	<div class="box"></div>
	<div class="box"></div>
	<div class="box"></div>
	<div class="box"></div>
	<div class="box"></div>
	<div class="box"></div>
	<div class="box"></div>
	<div class="box"></div>
	<div class="box"></div>


</div>

You can change the content of each box as you wish, however remember to take into account that they change size each time, so the shape of the content will change too. The best application of this style of layout is with images of some kind, although text can work good too!

Javascript!

Now it’s time for some Javascript. What we’re going to do is pick a number between 2 and 5 randomly, which will usually never be the same two times in a row, and select that number of .boxes. Then we’re going to wrap these boxes in a div with the class .wrapper. After that we select the .wrappers based on how many divs are in them and apply another class which will determine the way the boxes are styled using CSS. We may then further wrap the boxes so we can achieve the correct result. First off, include jQuery:

<script type="text/javascript" src="jquery.js"></script>

Then paste this into your Javascript area. I’ve added some comments to make it easier to understand.

$(document).ready(function() {

	// Some Variables
	var noBoxes = $('#holder .box');
	var randNumber, prev;
	var i = 0;
	
	//Run a while loop to go through all the boxes
	while(i < noBoxes.length) {
		
		// ensure the random number is different every time
	    do {
	        randNumber = Math.floor(Math.random() * (6 - 2) + 2); // the random number
		} while(randNumber === prev);
		prev = randNumber;
	    
	    // If i is 0, it's the first set of boxes, so wrap the first 5 in a wrapper (the header)
	    if(i == 0) {
			noBoxes.slice(0, 5).wrapAll("<div class='wrapper'></div>");
			i+=5;
		}
		// Otherwise..
		else {
			// If i is 5 it's the second set of boxes, so ensure that the random number isn't 5.
			if(i == 5) {
				newRand = Math.floor(Math.random() * (5 - 2) + 2);	
				noBoxes.slice(i, i+newRand).wrapAll("<div class='wrapper'></div>");
				i+=newRand;
			}
			// Then just run the loop normally 
			else {
				noBoxes.slice(i, i+randNumber).wrapAll("<div class='wrapper'></div>");
				i+=randNumber;
			}
			
		}
	}
	
	// Since the wrappers have been made, run this function for each
	$('.wrapper').each(function() {
	
		// Get the number of children in this wrapper
		var noChildren = $(this).children('.box').length;
		
		// An array of the classes we're going to use
		var cssClass = [
			'single', // 1 IMAGE PER WRAPPER
			'left-big', 'left-small', // 2 IMAGES PER WRAPPER
			'small-middle', 'big-middle', // 3 IMAGES PER WRAPPER
			'middle-two-rows', 'right-two-rows', // 4 IMAGES PER WRAPPER
			'four-block' // 4 IMAGES PER WRAPPER
		];
		
		// If there is only 1 child then add this class
		if(noChildren == 1) {
		
			$(this).addClass(cssClass[0]);
			
		}
		else if(noChildren == 2) {
		
			// 2 per wrapper, so get a number between 1 and 2, and add this class to this wrapper
			var rand = Math.floor(Math.random() * (3 - 1) + 1);
			$(this).addClass(cssClass[rand]);
			
		}
		else if(noChildren == 3) {
		
			// 3 per wrapper, same as before
			var rand = Math.floor(Math.random() * (5 - 3) + 3);
			$(this).addClass(cssClass[rand]);
			
		}
		else if(noChildren == 4) {
		
			// 4 per wrapper, same as before
			var rand = Math.floor(Math.random() * (7 - 5) + 5);
			$(this).addClass(cssClass[rand]);
			
			// if the CSS class is this particular class then wrap the divs accordingly. 
			// This is simply for layout reasons.
			if(cssClass[rand] == 'middle-two-rows') {
				$(this).children().slice(1, 3).wrapAll("<div class='middle'></div>");
			} else {
				$(this).children().slice(2, 5).wrapAll("<div class='middle'></div>");
			}
			
		}
		else if(noChildren == 5) {
			// 5 per wrapper, there is only one type of layout, so no random classes
			$(this).addClass(cssClass[7]);
			// Wrap the last 4 div's so we can lay them out appropriately.
			$(this).children().slice(1, 5).wrapAll("<div class='block'></div>");	
		}
		
	});
});

So we add some classes to the wrappers to lay the blocks out differently. We can then change this with CSS. It’s possible to add even more styles, but I’ve done 8. You are free to change this as you wish! The individual layouts are styled with CSS, using percentages and floats, so nothing too difficult.


body {
	padding: 1%; margin: 0;
	background: #f9f9f9;
}

.box {
	height: 100%;
	float: left;
	background: #fff;
	overflow: hidden;
	border-radius: 4px;
	box-shadow: 0px 1px 3px rgba(0,0,0,0.2);
}

.wrapper {
	display: block;
	width: 97%;
	padding: 1.5% 1.5% 0 1.5%; 
	height: 350px;
	float: left;
}

.single .box {
	width: 100%;
}

a {
	border: 0;
	text-decoration: none;
}

/* 2 IMAGES PER WRAPPER */
.left-big .box:last-child { width: 35%; }
.left-small .box, .left-big .box { width: 65%; }
.left-small .box:first-child { margin: 0 1.5% 0 0; width: 33.5%; }
.left-big .box:first-child { margin: 0 1.5% 0 0; width: 63.5%; }

/* 3 IMAGES PER WRAPPER */
.big-middle .box:nth-child(2) { width: 47%; margin: 0 1.5%; }
.big-middle .box:first-child, .big-middle .box:last-child { width: 25%; }
.small-middle .box:nth-child(2) { margin: 0 1.5%; width: 22%; }
.small-middle .box:first-child, .small-middle .box:last-child { width: 37.5%; }

/* 4 IMAGES PER WRAPPER */

.middle-two-rows .box, .right-two-rows .box  { width: 25%; }

.right-two-rows .box:nth-of-type(2) {
	margin: 0 0 0 1.5%;
	width: 23.5%;
}

.middle {
	float: left;
	width: 47%;
	height: 100%;
	margin: 0 1.5%;
}

.middle .box {
	height: 165px;
	width: 100%;
	clear: both;
	margin: 0;
}

.right-two-rows .middle .box:last-child {
	margin: 0;
}
.right-two-rows .middle {
	margin: 0 0 0 1.5%;
	width: 48.5%;
}

.right-two-rows .middle .box {
	width: 100%;
}

.middle .box:first-child {
	margin: 0 0 20px 0;
}


/* 5 IMAGES PER WRAPPER */

.four-block .box { width: 48.5%; margin: 0 1.5% 0 0; }

.block {
	width: 50%;
	height: 100%;
	float: left;
}
.block .box {
	margin: 0;
	height: 165px;
	margin: 0 3% 0 0;
}

.block .box:nth-of-type(3), .block .box:nth-of-type(4) { margin-top: 20px; }
.block .box:nth-child(2), .block .box:nth-child(4) { margin-right: 0; }

I’ve also added some extra styles for the first 12 boxes in my specific rendition of the layout, which you can download below, or check out in the demo! I’m going to be covering how to load content while scrolling (like Facebook and Pinterest) in the future, so stay tuned!