From our sponsor: Meco is a distraction-free space for reading and discovering newsletters, separate from the inbox.
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.
Tiny break: 📬 Want to stay up to date with frontend and trends in web design? Subscribe and get our Collective newsletter twice a tweek.
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"> <li> <img src="images/thumbs/1.jpg" data-img="images/full/1.jpg" alt="Thumb1"/> <div> <h2>Description Heading</h2> <p>Some description text...</p> </div> </li> <li>...</li> ... </ul> </div> <div id="iw_ribbon" class="iw_ribbon"> <span class="iw_close"></span> <span class="iw_zoom">Click thumb to zoom</span> </div>
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.
The CSS
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%:
.iw_wrapper{ 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(1){ margin-left:50px; }
Then we take all the even list elements and give them a different top margin:
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%.
.iw_ribbon{ position:fixed; height:126px; width:0px; left:0px; top:0px; background:#000; opacity:0.8; z-index:10; overflow:hidden; display:none; }
The close and the zoom text will have the following style:
.iw_close{ position:absolute; top:10px; right:10px; background:#f0f0f0 url(../images/close.gif) no-repeat center center; width:18px; height:18px; display:none; cursor:pointer; } .iw_zoom{ 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:
.iw_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; }
And that’s all the style!
Let’s add some juice!
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() { ... })(); ImageWall.init();
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() { $iw_thumbs.imagesLoaded(function(){ $iw_thumbs.masonry({ isAnimated : true }); }); getWindowsDim(); initEventsHandler(); },
“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')) { showFullImage($el); } else if(!isRibbonShown) { isRibbonShown = true; $el.data('ribbon',true); // set the current current = $el.index(); showRibbon($el); } }); // click ribbon close $iw_ribbon_close.bind('click', closeRibbon); // on window resize we need to recalculate the window dimentions $(window).bind('resize', function() { getWindowsDim(); if($iw_ribbon.is(':animated')) return false; closeRibbon(); }) .bind('scroll', function() { if($iw_ribbon.is(':animated')) return false; closeRibbon(); }); },
“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) .data('originalHeight',$img.height()) .stop() .animate({ 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' }, descriptionCssParam, dir; 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'; } $iw_ribbon.css(ribbonCssParam) .show() .stop() .animate({width : '100%'}, ribbonAnim.speed, ribbonAnim.easing, function() { switch(dir) { case 'left' : descriptionCssParam = { 'left' : $img.outerWidth(true) + 'px', 'text-align' : 'left' }; break; case 'right' : descriptionCssParam = { 'left' : '-200px', 'text-align' : 'right' }; break; }; $descrp.css(descriptionCssParam).fadeIn(); // show close button and zoom $iw_ribbon_close.show(); $iw_ribbon_zoom.show(); }); },
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 $iw_ribbon_close.hide(); $iw_ribbon_zoom.hide(); if(!isFullMode) { // current wall image var $el = $iw_thumbs.children('li').eq(current); resetWall($el); // slide out ribbon $iw_ribbon.stop() .animate({width : '0%'}, ribbonAnim.speed, ribbonAnim.easing); } else { $iw_ribbon.stop().animate({ opacity : 0.8, height : '0px', marginTop : w_dim.height/2 + 'px' // half of window height }, ribbonAnim.speed, function() { $iw_ribbon.css({ 'width' : '0%', 'height' : '126px', 'margin-top': '0px' }).children('img').remove(); }); 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(); $el.data('ribbon',false); // reset the image z-index and height $img.css('z-index',1).stop().animate({ height : $img.data('originalHeight') }, imgAnim.speed,imgAnim.easing); // fadeOut the description $descrp.fadeOut(); // 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;
$iw_ribbon_close.hide();
var $img = $el.children('img'),
large = $img.data('img'),
// add a loading span on top of the image
$loading = $(' ');
$el.append($loading);
// preload large image
$('').load(function() {
var $largeImage = $(this);
$loading.remove();
$iw_ribbon_zoom.hide();
resizeImage($largeImage);
// reset the current image in the wall
resetWall($el);
// animate ribbon in and out
$iw_ribbon.stop().animate({
opacity : 1,
height : '0px',
marginTop : '63px' // half of ribbons height
}, ribbonAnim.speed, function() {
// add the large image to the DOM
$iw_ribbon.prepend($largeImage);
$iw_ribbon_close.show();
$iw_ribbon.animate({
height : '100%',
marginTop : '0px',
top : '0px'
}, ribbonAnim.speed);
});
}).attr('src',large);
},
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; } } } $image.css({ 'width' : theImage.width + 'px', 'height' : theImage.height + 'px', 'margin-left' : -theImage.width / 2 + 'px', 'margin-top' : -theImage.height / 2 + 'px' }); }; return {init : init};
And that’s it!
We hope you enjoyed this tutorial and find it useful!
gonna use this for sure
I just starting this tutorial. Do I need to resize the images for thumbs myself? Giving me two sets of images?..full and thumb? I’m a noob :/
This is brilliant thank you!
One question, currently using your JS the images are in the exact same pattern and size, is there any way of changing the image sizes?
Thanks!
Great script !
How can I add two additional smaller thumbnails into the rubbon without having strange position of the elements ?
Thanx.