Tuesday, October 16, 2012

Image wall with jQuery

Image wall with jQuery

Today we want to show you how to create a neat image wall with jQuery. The idea is to scatter some thumbnails with different sizes on the page and make a ribbon slide in when we click on the picture. The ribbon will show some description next to the picture and when clicking again on the thumbnail, the ribbon will close and open again with a large version of the image.

To scatter the images we will be using the CSS3 child selector property and the jQuery Masonry plugin by David DeSandro.

The beautiful photos are by Mark Sebastian and you can see his Flickr photostream here. The images are licensed under the Creative Commons Attribution-ShareAlike 2.0 Generic License.

Let's start with the HTML structure.

The Markup

The HTML structure is pretty straightforward: we will have a wrapper for our unordered list of images and their descriptions and a ribbon element where we will add a closing span and a help text span:

<div class="iw_wrapper">
    <ul class="iw_thumbs" id="iw_thumbs">
            <img src="images/thumbs/1.jpg" data-img="images/full/1.jpg" alt="Thumb1"/>
                <h2>Description Heading</h2>
                <p>Some description text...</p>
<div id="iw_ribbon" class="iw_ribbon">
    <span class="iw_close"></span>
    <span class="iw_zoom">Click thumb to zoom</span>

The data-img will tell us the path to the full image which will insert dynamically into the ribbon div.
Let's take a look at the style.


First, we will define the style of the wrapper. We want it to be centered on the page and to have a width that will adapt to the window size, so we give it a width of 70%:

    width: 70%;
    margin: 30px auto 100px auto;
    position: relative;

Since we will be using the Masonry Plugin to minimizes vertical gaps between the images we will have a neat repositioning animation when we i.e. resize the window.

Next, we will define the style for the list elements. (I am assuming that you are including some sort of reset css that will set the margins and paddings of unordered lists to 0).
By default we want all the list elements to have a margin of 5px and to float:

ul.iw_thumbs li{
    float: left;
    margin: 5px;

The description will be placed absolutely and in our JavaScript function we will decide the left positioning (either it will be -200px in order to be shown left to the image or the width of the image in order to be shown to the right):

ul.iw_thumbs li div{
    position: absolute;
    top: 5px;
    width: 180px;
    padding: 0px 10px;
    display: none;
    color: #fff;
    z-index: 100;

Let’s style the heading and the text:

ul.iw_thumbs li div h2{
    font-family: 'Wire One', arial, serif;
    font-size: 38px;
    text-transform: uppercase;
    text-shadow: 0px 0px 1px #fff;

ul.iw_thumbs li div p{
    font-size: 11px;
    line-height: 16px;
    font-style: italic;

The images will have a thick white border and some box-shadow:

ul.iw_thumbs li img{
    border: 7px solid #fff;
    cursor: pointer;
    position: relative;
    -moz-box-shadow: 1px 1px 1px #aaa;
    -webkit-box-shadow: 1px 1px 1px #aaa;
    box-shadow: 1px 1px 1px #aaa;

ul.iw_thumbs li img:hover{
    -moz-box-shadow: 1px 1px 7px #777;
    -webkit-box-shadow: 1px 1px 7px #777;
    box-shadow: 1px 1px 7px #777;

Now we want to add some chaos to the list elements and the images. We will adjust the margin of some list elements by selecting specific children. We take the first list element and we add a margin-left of 50 px:

ul.iw_thumbs li:nth-child(even){ margin-top:30px; }

All multiples of 3 will have a left margin of 20 px:

ul.iw_thumbs li:nth-child(3n){ margin-left:20px; }

Now we will play with the heights of the images. Like that we will give the whole wall a scattered look and avoid making it look like a boring grid. We also want to make the images a little bit smaller or bigger because we want to animate them to the height of the ribbon when we click on them. So this will add an interesting effect, since some images will grow and some will shrink:

ul.iw_thumbs li:nth-child(even) img{ height:20px; }
ul.iw_thumbs li:nth-child(odd) img{ height:40px; }
ul.iw_thumbs li:nth-child(5n) img{ height:70px; }
ul.iw_thumbs li:nth-child(6n) img{ height:110px; }
ul.iw_thumbs li:nth-child(7n) img{ height:20px; }

The ribbon will be a fixed element that will come sliding out from the left or the right side depending on where the thumbnail is positioned. The width is 0 initially and we will animate it to 100%.

    position: fixed;
    height: 126px; width:0;
    left: 0; top: 0;
    background: #000;
    opacity: 0.8;
    z-index: 10;
    overflow: hidden;
    display: none;

The close and the zoom text will have the following style:

    position: absolute;
    top: 10px; right: 10px;    
    background: #f0f0f0 url(../images/close.gif) no-repeat center center;
    width: 18px; height: 18px;
    display: none;
    cursor: pointer;

    color: white;
    font-size: 8px;
    font-family: Arial, sans-serif;
    text-transform: uppercase;
    padding: 14px;
    display: none;
    float: right;
    margin-right: 30px;

When we click on a thumbnail while we are in the ribbon mode, we want the big image to appear inside of the ribbon and then we’ll expand the ribbon in order to reveal it. So we will dynamically add the image into our ribbon and apply the following style:

.iw_ribbon img{
    position: absolute;
    top: 50%; left: 50%;
    border:7px solid #fff;

We want the image to be centered, so we add a top and left of 50% and define the negative margins dynamically once we know the size of the image. The margins need to be the negative half of the width and height of the image.

And finally, we define the loading span style which is an element that we will add dynamically into the list element in order to indicate that the big image is loading:

    background: #fff url(../images/loader.gif) no-repeat center center;
    width: 28px; height: 28px;
    position: absolute;
    top: 50%; left: 50%;
    z-index: 10000;
    margin: -14px 0px 0px -14px;
    opacity: 0.8;

The Javascript

Let's first cache some elements and then define our function:

var $iw_thumbs          = $('#iw_thumbs'),
    $iw_ribbon          = $('#iw_ribbon'),
    $iw_ribbon_close    = $iw_ribbon.children('span.iw_close'),
    $iw_ribbon_zoom     = $iw_ribbon.children('span.iw_zoom');

    ImageWall   = (function() {


In our function we will start by defining some variables:

// window width and height
var w_dim,
    // index of current image
    current             = -1,
    isRibbonShown       = false,
    isFullMode          = false,
    // ribbon / images animation settings
    ribbonAnim          = {speed : 500, easing : 'easeOutExpo'},
    imgAnim             = {speed : 400, easing : 'jswing'},

Next, we'll define the init function which will first call the masonry plugin, calculate the windows dimensions and initialize some events:

init                = function() {
            isAnimated  : true

getWindowsDim will get the dimensions of the window:

getWindowsDim       = function() {
        w_dim = {
            width   : $(window).width(),
            height  : $(window).height()

Then we'll define the initializations of some events, like the click on the thumbnail image, the closing of the ribbon and the window resize:

initEventsHandler   = function() {

    // click on a image
    $iw_thumbs.delegate('li', 'click', function() {
        if($iw_ribbon.is(':animated')) return false;

        var $el = $(this);

        if($el.data('ribbon')) {
        else if(!isRibbonShown) {
            isRibbonShown = true;


            // set the current
            current = $el.index();


    // click ribbon close
    $iw_ribbon_close.bind('click', closeRibbon);

    // on window resize we need to recalculate the window dimentions
    $(window).bind('resize', function() {
                    return false;
             .bind('scroll', function() {
                    return false;


showRibbon will take care of the things that will happen when we show the ribbon:

showRibbon          = function($el) {
        var $img    = $el.children('img'),
            $descrp = $img.next();

        // fadeOut all the other images
        $iw_thumbs.children('li').not($el).animate({opacity : 0.2}, imgAnim.speed);

        // increase the image z-index, and set the height to 100px (default height)
        $img.css('z-index', 100)
                height      : '100px'
            }, imgAnim.speed, imgAnim.easing);

        // the ribbon will animate from the left or right
        // depending on the position of the image
        var ribbonCssParam      = {
                top : $el.offset().top - $(window).scrollTop() - 6 + 'px'

        if( $el.offset().left < (w_dim.width / 2) ) {
            dir = 'left';
            ribbonCssParam.left     = 0;
            ribbonCssParam.right    = 'auto';
        else {
            dir = 'right';
            ribbonCssParam.right    = 0;
            ribbonCssParam.left     = 'auto';

                  .animate({width : '100%'}, ribbonAnim.speed, ribbonAnim.easing, function() {
                        switch(dir) {
                            case 'left' :
                                descriptionCssParam     = {
                                    'left'          : $img.outerWidth(true) + 'px',
                                    'text-align'    : 'left'
                            case 'right' :  
                                descriptionCssParam     = {
                                    'left'          : '-200px',
                                    'text-align'    : 'right'
                        // show close button and zoom


Closing the ribbon will either animate the ribbon from one side or "close" it by decreasing its height when we are in full image mode:

closeRibbon         = function() {
    isRibbonShown   = false


    if(!isFullMode) {

        // current wall image
        var $el         = $iw_thumbs.children('li').eq(current);


        // slide out ribbon
                  .animate({width : '0%'}, ribbonAnim.speed, ribbonAnim.easing); 

    else {
            opacity     : 0.8,
            height      : '0px',
            marginTop   : w_dim.height/2 + 'px' // half of window height
        }, ribbonAnim.speed, function() {
                'width'     : '0%',
                'height'    : '126px',
                'margin-top': '0px'

        isFullMode  = false;

We also need to take care of the other things that are happening when we close the ribbon, like reset the z-index of the current image and fade out the description:

resetWall           = function($el) {
    var $img        = $el.children('img'),
        $descrp     = $img.next();


    // reset the image z-index and height
        height      : $img.data('originalHeight')
    }, imgAnim.speed,imgAnim.easing);

    // fadeOut the description

    // fadeIn all the other images
    $iw_thumbs.children('li').not($el).animate({opacity : 1}, imgAnim.speed);                               

Now we'll define what happens when we want to show the full image:

showFullImage       = function($el) {
    isFullMode  = true;


    var $img    = $el.children('img'),
        large   = $img.data('img'),

        // add a loading span on top of the image
        $loading = $('<span class="iw_loading"> </span>');


    // preload large image
    $('<img alt="">').load(function() {
        var $largeImage = $(this);




        // reset the current image in the wall

        // animate ribbon in and out
            opacity     : 1,
            height      : '0px',
            marginTop   : '63px' // half of ribbons height
        }, ribbonAnim.speed, function() {
            // add the large image to the DOM


                height      : '100%',
                marginTop   : '0px',
                top         : '0px'
            }, ribbonAnim.speed);


And finally, we will have a resize function that will care of the size of the full image, i.e. we want it to fit into the screen:

resizeImage         = function($image) {
        var widthMargin     = 100,
            heightMargin    = 100,

            windowH         = w_dim.height - heightMargin,
            windowW         = w_dim.width - widthMargin,
            theImage        = new Image();

        theImage.src        = $image.attr("src");

        var imgwidth        = theImage.width,
            imgheight       = theImage.height;

        if((imgwidth > windowW) || (imgheight > windowH)) {
            if(imgwidth > imgheight) {
                var newwidth    = windowW,
                    ratio       = imgwidth / windowW,
                    newheight   = imgheight / ratio;

                theImage.height = newheight;
                theImage.width  = newwidth;

                if(newheight > windowH) {
                    var newnewheight    = windowH,
                        newratio        = newheight/windowH,
                        newnewwidth     = newwidth/newratio;

                    theImage.width      = newnewwidth;
                    theImage.height     = newnewheight;
            else {
                var newheight   = windowH,
                    ratio       = imgheight / windowH,
                    newwidth    = imgwidth / ratio;

                theImage.height = newheight;
                theImage.width  = newwidth;

                if(newwidth > windowW) {
                    var newnewwidth     = windowW,
                        newratio        = newwidth/windowW,
                        newnewheight    = newheight/newratio;

                    theImage.height     = newnewheight;
                    theImage.width      = newnewwidth;

            'width'         : theImage.width + 'px',
            'height'        : theImage.height + 'px',
            'margin-left'   : -theImage.width / 2 + 'px',
            'margin-top'    : -theImage.height / 2 + 'px'

return {init : init};

View Demo