Thursday, July 12, 2012

How To Create a Pinterest Inspired Shuffling Effect With jQuery

How To Create a Pinterest Inspired Shuffling Effect With jQuery:
Pinterest is a website that lets you organise and share all your favourite things using pinboards. As a developer, what I find even more interesting is how these pinboards are laid out. I’m certain you’ve seen something similar before with jQuery Isotope where blocks are perfectly positioned into a series of columns, and as the screen size changes, the columns re-shuffle to make sure they fit inside the screen.
Today, we’re going to try and re-create this responsive block effect from scratch.







The Theory

Before jumping into the code, we need to get the theory sorted in our heads. It’s a relatively simple system once you grasp the concept. To make this system work, we need to know three things:
  • the container width,
  • the width our block and
  • the margin between these blocks.
From this data, we can calculate the number of possible blocks that will fit in the container. This gives us the number of columns for that instance, i.e.in the diagram above, the number of columns is 4.
So we create an array with four values in. The default value for this is the margin between the blocks. This array is used to store the running heights of each column, i.e. a block of height 120px is added to column 1, so that value in the array is increased by 120.
We also use the index number of each value to calculate the position from the left of the screen. Here’s a flow diagram of how the function will work. This will run every time the window is resized so the blocks can update themselves.


Setting Up The Blocks

There’s some information about the theory behind both Pinterest and jQuery Isotope. If it didn’t make much sense then, hopefully looking at some code will help. To start, we need a simple HTML structure. From this HTML, we need to grab three vital values:
  • the container width,
  • the width our block and
  • the margin between these blocks.

The HTML Structure

<body>
    <!-- Create multiple versions of this with different content -->
    <div class="block">
        <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Ut in dui velit. Curabitur purus odio, adipiscing ut vehicula at, pulvinar eu justo. Suspendisse potenti. Suspendisse dictum massa non mi posuere ac convallis nisi pellentesque. Morbi posuere mauris elementum metus intlla faProin et malesuada arcu. Quisque sed nulla odio, at interdum diam. Proin mollis, dui eget tristique dictum, diam purus hendrerit urna, lacinia interdum sem justo sit amet justo. Morbi a neque ut velit tempus auctor. Sed condimentum dolor in est facilisis id malesuad</p>
    </div>
</body>


The CSS

.block {
    position: absolute;
    background: #eee;
    padding: 20px;
    width: 300px;
    border: 1px solid #ddd;
}


The jQuery

var colCount = 0;
var colWidth = 0;
var margin = 20;
var windowWidth = 0;
var blocks = [];

function positionBlocks() {
    windowWidth = $(window).width();
    colWidth = $('.block').outerWidth();
    colCount = Math.floor(windowWidth/(colWidth+margin));
    for(var i=0;i<colCount;i++) {
        blocks.push(margin);
    }
    $('.block').each(function(){
        var min = Array.min(blocks);
        var index = $.inArray(min, blocks);
        var leftPos = margin+(index*(colWidth+margin));
        $(this).css({
            'left':leftPos+'px',
            'top':min+'px'
        });
        blocks[index] = min+block.outerHeight()+margin;
    });
}
// Function to get the Min value in Array
Array.min = function(array) {
    return Math.min.apply(Math, array);
};
We start by setting some global variables, along with the array that will be storing the heights of each column. Create a function called positionBlocks. This function will host all the code for this tutorial.
In the setup, we need to grab the width of the container. In this case, it’s the window, along with the width of each block. These are set as the variables windowWidth and colWidth.
The next line calculates the number of columns that will fit in current window. Here we add the margin to colWidth and then divide the windowWidth by this number. Finally, the number is rounded down to give us a whole integer.
We then loop through many times, setting a new value in our empty array. So if there are four possible columns that could fit in the window, there will be as many values set in the array.





Positioning The Blocks

Now that we’ve set up our blocks and have an array of the starting heights of each column, we can move on to calculating each blocks position.
Firstly, we loop through each element on the page with the class block. Two variables are then set. The first is min, which represents the lowest value in the array, which is also the column with the lowest height. The next is index which represents the index number of this value. This provides us with the column number we need to place the next block in.
Let’s look at an example. If the array contains 20, 95 and 75, the value of min will be 20 and that of index will be 0. We can then calculate the position from the left of the screen based on these numbers.
$('.block').each(function(){
        var min = Array.min(blocks);
        var index = $.inArray(min, blocks);
To calculate the left position, we add the width of each column to the margin, multiple that by our index and finally add another margin. This additional margin acts as padding for the next column of blocks. The new leftPos value and min is applied to that block.
The last line simply updates the array with the new height. This height is calculated by adding the height of the current block and the margin to the existing value in the array for that index.
var leftPos = margin+(index*(colWidth+margin));
$(this).css({
    'left':leftPos+'px',
    'top':min+'px'
});
blocks[index] = min+$(this).outerHeight()+margin; 
To get the minimum value from an array, we use John Resig’s function which can be found here.
The last addition to the code is to add a listener so when the window is resized, the function runs again.
$(window).load(function() {
    positionBlocks ();
    $(window).resize(positionBlocks);
});
So that is all there is to it really. You should now have a working system that looks and works just like Pinterest and jQuery Isotope.



Finishing Touches

CSS3 Animations

Another good addition is to add this CSS to the block class. It will animate blocks instead of making them jump.
-webkit-transition: all 1s ease-in-out;
-moz-transition: all 1s ease-in-out;
-o-transition: all 1s ease-in-out;
-ms-transition: all 1s ease-in-out;
transition: all 1s ease-in-out;

Animate Instead of CSS

In the code example above, the blocks CSS changes, making them jump. If you want to make the animations more compatible (not just CSS3 capable browsers), you can switch out the code as shown below:
$(this).css({
    'left':leftPos+'px',
    'top':min+'px'
});
$(this).stop().animate({
    'left':leftPos+'px',
    'top':min+'px'
});

Adding a Footer

Due to the blocks being positioned absolutely, all other elements won’t adjust to the height of the containers. With a bit of tweaking, we can make this work.
#footer {
    height: 50px;
    width: 100%;
    position: absolute;
    left:0;
    bottom:0;
    text-align: center;
    line-height: 50px;
    background: rgba(0,0,0,0.4);
}
Add this code to the end of the function:
// Set footer position
var maxHeight = Array.max(blocks);
$('#footer').css({'top':maxHeight+150});
The extra 150px adds as a padding to the bottom of the blocks. For this to work, you’ll also need to include this code so we can get the largest number from our array blocks. That way, the footer’s height should adjust to the maximum height of all the blocks.
// Function to get the Min value in Array
Array.min = function(array) {
    return Math.min.apply(Math, array);
};

Wrapping Up

That’s all there is to it, really. I hope you’ve enjoyed this tutorial! Make sure you check out the source code and let me know what you think in the comment section below.



No comments:

Post a Comment