Thumbnail Grid with Expanding Preview

A tutorial on how to create a thumbnail grid with an expanding image preview similar to the effect seen on Google Images.

Thumbnail Grid with Expanding Preview

If you have searched images on Google recently, you might have noticed the interesting expanding preview for a larger image when you click on a thumbnail. It’s a really nice effect and it is very practical, making a search much easier. Today we want to show you how to create a similar effect on a thumbnail grid. The idea is to open a preview when clicking on a thumbnail and to show a larger image and some other content like a title, a description and a link.

The interesting part is to calculate the correct preview height and to scroll the page to the right position. We’ll expand the preview in a way so that we can see the respective thumbnail row and cover the rest of the remaining page. Note that we don’t use very large images for the preview in the demo so you might see a lot of empty space on large monitors.

The demo features some amazing artwork by Jaime Martinez.

So let’s start!

The Markup

Initially, we need a thumbnail grid for which we will use an unordered list. Each list item will contain an anchor which will have several data attributes:

<ul id="og-grid" class="og-grid">
	<li>
		<a href="http://cargocollective.com/jaimemartinez/" data-largesrc="images/1.jpg" data-title="Azuki bean" data-description="Swiss chard pumpkin bunya nuts maize plantain aubergine napa cabbage soko coriander sweet pepper water spinach winter purslane shallot tigernut lentil beetroot.">
			<img src="images/thumbs/1.jpg" alt="img01"/>
		</a>
	</li>
	<li>
		<a href="http://cargocollective.com/jaimemartinez/" data-largesrc="images/2.jpg" data-title="Veggies sunt bona vobis" data-description="Komatsuna prairie turnip wattle seed artichoke mustard horseradish taro rutabaga ricebean carrot black-eyed pea turnip greens beetroot yarrow watercress kombu.">
			<img src="images/thumbs/2.jpg" alt="img02"/>
		</a>
	</li>
	<li><!-- ... --></li>
	<!-- ... -->
</ul>

The href value will be used to construct the link in the preview description (this also comes in handy when JavaScript is disabled). The data-largesrc attribute contains the path to the larger image. data-title and data-description contain the title and description, respectively.

When we click on a thumbnail, we want a preview element to appear under the list item. For that we will need to insert an element into the grid. In fact, we will use the list item itself and add the preview element after the anchor:

<li>

	<a href="http://cargocollective.com/jaimemartinez/" data-largesrc="images/2.jpg" data-title="Veggies sunt bona vobis" data-description="Komatsuna prairie turnip wattle seed artichoke mustard horseradish taro rutabaga ricebean carrot black-eyed pea turnip greens beetroot yarrow watercress kombu.">
		<img src="images/thumbs/2.jpg" alt="img02"/>
	</a>
	
	<div class="og-expander">
		<div class="og-expander-inner">
			<span class="og-close"></span>
			<div class="og-fullimg">
				<div class="og-loading"></div>
				<img src="images/2.jpg">
			</div>
			<div class="og-details">
				<h3>Veggies sunt bona vobis</h3>
				<p>Komatsuna prairie turnip wattle seed artichoke mustard horseradish taro rutabaga ricebean carrot black-eyed pea turnip greens beetroot yarrow watercress kombu.</p>
				<a href="http://cargocollective.com/jaimemartinez/">Visit website</a>
			</div>
		</div>
	</div>

</li>

Let’s style everything!

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 CSS

Note that the CSS will not contain any vendor prefixes, but you will find them in the files.

So, let’s start with the thumbnail grid. It will be full width and we’ll center the text. In this case this will mean that it will center the thumbnails because we’ll set them to display: inline-block:

.og-grid {
	list-style: none;
	padding: 20px 0;
	margin: 0 auto;
	text-align: center;
	width: 100%;
}

.og-grid li {
	display: inline-block;
	margin: 10px 5px 0 5px;
	vertical-align: top;
	height: 250px;
}

The links and images will be displayed as block elements and we’ll remove some default styling:

.og-grid li > a,
.og-grid li > a img {
	border: none;
	outline: none;
	display: block;
	position: relative;
}

When we click on an item, we will give a special class to the respective list item which will be called og-expanded. We’ll add a little arrow as pseudo-element to the anchor:

.og-grid li.og-expanded > a::after {
	top: auto;
	border: solid transparent;
	content: " ";
	height: 0;
	width: 0;
	position: absolute;
	pointer-events: none;
	border-bottom-color: #ddd;
	border-width: 15px;
	left: 50%;
	margin: -20px 0 0 -15px;
}

The preview itself will have the class og-expander and we’ll position that element absolutely. The initial height of the preview will be 0 and we’ll set the overflow to hidden:

.og-expander {
	position: absolute;
	background: #ddd;
	top: auto;
	left: 0;
	width: 100%;
	margin-top: 10px;
	text-align: left;
	height: 0;
	overflow: hidden;
}

.og-expander-inner {
	padding: 50px 30px;
	height: 100%;
}

The inner division will have some paddings and a height of 100%.

The cross for closing the preview will be created using pseudo-elements, i.e. two rotated lines:

.og-close {
	position: absolute;
	width: 40px;
	height: 40px;
	top: 20px;
	right: 20px;
	cursor: pointer;
}

.og-close::before,
.og-close::after {
	content: '';
	position: absolute;
	width: 100%;
	top: 50%;
	height: 1px;
	background: #888;
	transform: rotate(45deg);
}

.og-close::after {
	transform: rotate(-45deg);
}

.og-close:hover::before,
.og-close:hover::after {
	background: #333;
}

The wrappers for the image and for the details will be 50% wide and we’ll make them float next to each other:

.og-fullimg,
.og-details {
	width: 50%;
	float: left;
	height: 100%;
	overflow: hidden;
	position: relative;
}

The details wrapper will have some padding and we’ll center the image inside of the image wrapper by setting the text-align to center and the image itself to display: inline-block. The image will also have a max-height and max-width of 100% so that it adjusts its size to the surrounding container:

.og-details {
	padding: 0 40px 0 20px;
}

.og-fullimg {
	text-align: center;
}

.og-fullimg img {
	display: inline-block;
	max-height: 100%;
	max-width: 100%;
}

Let’s style the text elements and the link:

.og-details h3 {
	font-weight: 300;
	font-size: 52px;
	padding: 40px 0 10px;
	margin-bottom: 10px;
}

.og-details p {
	font-weight: 400;
	font-size: 16px;
	line-height: 22px;
	color: #999;
}

.og-details a {
	font-weight: 700;
	font-size: 16px;
	color: #333;
	text-transform: uppercase;
	letter-spacing: 2px;
	padding: 10px 20px;
	border: 3px solid #333;
	display: inline-block;
	margin: 30px 0 0;
	outline: none;
}

.og-details a::before {
	content: '2192';
	display: inline-block;
	margin-right: 10px;
}

.og-details a:hover {
	border-color: #999;
	color: #999;
}

The loading element will be in the same container as the image and we’ll not use any images but this CSS-only technique. We’ll create a little circle and set three box shadows: one for making the circle itself look a bit smoother and two for “copying” the element. Then we create an animation that will change the background color and the box shadow colors sequentially:

.og-loading {
	width: 20px;
	height: 20px;
	border-radius: 50%;
	background: #ddd;
	box-shadow: 0 0 1px #ccc, 15px 30px 1px #ccc, -15px 30px 1px #ccc;
	position: absolute;
	top: 50%;
	left: 50%;
	margin: -25px 0 0 -25px;
	animation: loader 0.5s infinite ease-in-out both;
}

@keyframes loader {
	0% { background: #ddd; }
	33% { background: #ccc; box-shadow: 0 0 1px #ccc, 15px 30px 1px #ccc, -15px 30px 1px #ddd; }
	66% { background: #ccc; box-shadow: 0 0 1px #ccc, 15px 30px 1px #ddd, -15px 30px 1px #ccc; }
}

Last, but not least, we’ll add two media queries for adjusting the text a bit and for hiding the full image once the screen gets so small that the preview image is not really useful anymore (we’ll also not load it in the JavaScript then).


@media screen and (max-width: 830px) {

	.og-expander h3 { font-size: 32px; }
	.og-expander p { font-size: 13px; }
	.og-expander a { font-size: 12px; }

}

@media screen and (max-width: 650px) {

	.og-fullimg { display: none; }
	.og-details { float: none; width: 100%; }
	
}

That’s all the style. Now, let’s take a look at the JavaScript.

The JavaScript

Let’s start by caching some elements and initializing some variables:

	// list of items
var $grid = $( '#og-grid' ),
	// the items
	$items = $grid.children( 'li' ),
	// current expanded item´s index
	current = -1,
	// position (top) of the expanded item
	// used to know if the preview will expand in a different row
	previewPos = -1,
	// extra amount of pixels to scroll the window
	scrollExtra = 0,
	// extra margin when expanded (between the preview element and the next item row)
	marginExpanded = 10,
	$window = $( window ), winsize,
	$body = $( 'html, body' ),
	// transitionend events
	transEndEventNames = {
		'WebkitTransition' : 'webkitTransitionEnd',
		'MozTransition' : 'transitionend',
		'OTransition' : 'oTransitionEnd',
		'msTransition' : 'MSTransitionEnd',
		'transition' : 'transitionend'
	},
	transEndEventName = transEndEventNames[ Modernizr.prefixed( 'transition' ) ],
	// support for csstransitions
	support = Modernizr.csstransitions,
	// default settings
	settings = {
		minHeight : 500,
		speed : 350,
		easing : 'ease'
	};

We will start by preloading all the images (thumbnails) in the grid. We then save the offset top and height for each item in the grid, get the current window’s size, and initialize some events:

	function init( config ) {
		
		// the settings..
		settings = $.extend( true, {}, settings, config );

		// preload all images
		$grid.imagesLoaded( function() {

			// save item´s size and offset
			saveItemInfo( true );
			// get window´s size
			getWinSize();
			// initialize some events
			initEvents();

		} );

	}

	// saves the item´s offset top and height (if saveheight is true)
	function saveItemInfo( saveheight ) {
		$items.each( function() {
			var $item = $( this );
			$item.data( 'offsetTop', $item.offset().top );
			if( saveheight ) {
				$item.data( 'height', $item.height() );
			}
		} );
	}

	function getWinSize() {
		winsize = { width : $window.width(), height : $window.height() };
	}

We will bind the click event for each item (anchor) and for the close button (when the item is opened). When we click on an item, the preview with the large image source and the details will be revealed or hidden if already shown. If we click the close button (cross) on the preview then this preview will be closed too.
We are also binding the resize event for the window, where some values are reset and the preview gets closed (if opened).

	function initEvents() {
		
		// when clicking an item, show the preview with the item´s info and large image;
		// close the item if already expanded.
		// also close if clicking on the item´s cross
		$items.on( 'click', 'span.og-close', function() {
			hidePreview();
			return false;
		} ).children( 'a' ).on( 'click', function(e) {

			var $item = $( this ).parent();
			// check if item already opened
			current === $item.index() ? hidePreview() : showPreview( $item );
			return false;

		} );

		// on window resize get the window´s size again
		// reset some values..
		$window.on( 'debouncedresize', function() {
			
			scrollExtra = 0;
			previewPos = -1;
			// save item´s offset
			saveItemInfo();
			getWinSize();
			var preview = $.data( this, 'preview' );
			if( typeof preview != 'undefined' ) {
				hidePreview();
			}

		} );

	}

With the showPreview function we will basically initialize the Preview object, which in turn will expand and reveal the details and the large version of the image. If a Preview instance is already initialized then we will only update the preview with the new details (if the clicked item is in the same row as the current expanded item) or hide it and initialize / open a new one (if not in the same row).
In order to check if the items are in the same row as the current preview, we use the offset top value of the items.

	function showPreview( $item ) {

		var preview = $.data( this, 'preview' ),
			// item´s offset top
			position = $item.data( 'offsetTop' );

		scrollExtra = 0;

		// if a preview exists and previewPos is different (different row) from item´s top, then close it
		if( typeof preview != 'undefined' ) {

			// not in the same row
			if( previewPos !== position ) {
				// if position > previewPos then we need to take the current preview´s height in consideration when scrolling the window
				if( position > previewPos ) {
					scrollExtra = preview.height;
				}
				hidePreview();
			}
			// same row
			else {
				preview.update( $item );
				return false;
			}
			
		}

		// update previewPos
		previewPos = position;
		// initialize new preview for the clicked item
		preview = $.data( this, 'preview', new Preview( $item ) );
		// expand preview overlay
		preview.open();

	}

The Preview object will have a reference to the currently displayed item (Preview.$item), and the index of the expanded item (Preview.expandedIdx). Note that the expanded item is not necessarily the displayed item. For instance if we click on a second item that is on the same row as the one clicked before then the Preview will be “reused” and the Preview.expandedIdx will not be the index of the Preview.$item. We need to keep the reference to the expanded item so that when the Preview is closed we know which item to “collapse”.

	// the preview obj / overlay
	function Preview( $item ) {
		this.$item = $item;
		this.expandedIdx = this.$item.index();
		this.create();
		this.update();
	}

As the Preview object is initialized we create the necessary structure where the item´s details will be rendered and we append it to the item:

	create : function() {
		// create Preview structure:
		this.$title = $( '<h3></h3>' );
		this.$description = $( '<p></p>' );
		this.$href = $( '<a href="#">Visit website</a>' );
		this.$details = $( '<div class="og-details"></div>' ).append( this.$title, this.$description, this.$href );
		this.$loading = $( '<div class="og-loading"></div>' );
		this.$fullimage = $( '<div class="og-fullimg"></div>' ).append( this.$loading );
		this.$closePreview = $( '<span class="og-close"></span>' );
		this.$previewInner = $( '<div class="og-expander-inner"></div>' ).append( this.$closePreview, this.$fullimage, this.$details );
		this.$previewEl = $( '<div class="og-expander"></div>' ).append( this.$previewInner );
		// append preview element to the item
		this.$item.append( this.getEl() );
		// set the transitions for the preview and the item
		if( support ) {
			this.setTransition();
		}
	}

Then we fill the previous structure with the item´s details (stored in data attributes and the href).
The update function will also be used to just update the content of an existing preview.

	update : function( $item ) {

		// update with new item´s details 
		if( $item ) {
			this.$item = $item;
		}
		
		// if already expanded, remove class "og-expanded" from current item and add it to new item
		if( current !== -1 ) {
			var $currentItem = $items.eq( current );
			$currentItem.removeClass( 'og-expanded' );
			this.$item.addClass( 'og-expanded' );
			// position the preview correctly
			this.positionPreview();
		}

		// update current value
		current = this.$item.index();

		// update preview´s content
		var $itemEl = this.$item.children( 'a' ),
			eldata = {
				href : $itemEl.attr( 'href' ),
				largesrc : $itemEl.data( 'largesrc' ),
				title : $itemEl.data( 'title' ),
				description : $itemEl.data( 'description' )
			};

		this.$title.html( eldata.title );
		this.$description.html( eldata.description );
		this.$href.attr( 'href', eldata.href );

		var self = this;
		
		// remove the current image in the preview
		if( typeof self.$largeImg != 'undefined' ) {
			self.$largeImg.remove();
		}

		// preload large image and add it to the preview
		// for smaller screens we don´t display the large image (the last media query will hide the wrapper of the image)
		if( self.$fullimage.is( ':visible' ) ) {
			this.$loading.show();
			$( '<img/>' ).load( function() {
				self.$loading.hide();
				self.$largeImg = $( this ).fadeIn( 350 );
				self.$fullimage.append( self.$largeImg );
			} ).attr( 'src', eldata.largesrc );	
		}

	}

To reveal the preview we need to set the height of the Preview element and also of the item (to push down the items below). The height of the preview will be the window´s height minus the height of the grid item. To avoid cases where that height could be too small we add the option “minHeight” where we can specify the minimum height needed for the preview element.
As the preview opens we will want to scroll the window so that the preview is completely visible (and if possible, also the item).

	open : function() {

		setTimeout( $.proxy( function() {	
			// set the height for the preview and the item
			this.setHeights();
			// scroll to position the preview in the right place
			this.positionPreview();
		}, this ), 25 );

	}

	setHeights : function() {

		var self = this,
			onEndFn = function() {
				if( support ) {
					self.$item.off( transEndEventName );
				}
				self.$item.addClass( 'og-expanded' );
			};

		this.calcHeight();
		this.$previewEl.css( 'height', this.height );
		this.$item.css( 'height', this.itemHeight ).on( transEndEventName, onEndFn );

		if( !support ) {
			onEndFn.call();
		}

	}

	calcHeight : function() {

		var heightPreview = winsize.height - this.$item.data( 'height' ) - marginExpanded,
			itemHeight = winsize.height;

		if( heightPreview < settings.minHeight ) {
			heightPreview = settings.minHeight;
			itemHeight = settings.minHeight + this.$item.data( 'height' ) + marginExpanded;
		}

		this.height = heightPreview;
		this.itemHeight = itemHeight;

	}

	positionPreview : function() {

		// scroll page
		// case 1 : preview height + item height fits in window´s height
		// case 2 : preview height + item height does not fit in window´s height and preview height is smaller than window´s height
		// case 3 : preview height + item height does not fit in window´s height and preview height is bigger than window´s height
		var position = this.$item.data( 'offsetTop' ),
			previewOffsetT = this.$previewEl.offset().top - scrollExtra,
			scrollVal = this.height + this.$item.data( 'height' ) + marginExpanded <= winsize.height ? position : this.height < winsize.height ? previewOffsetT - ( winsize.height - this.height ) : previewOffsetT;
		
		$body.animate( { scrollTop : scrollVal }, settings.speed );

	}

When closing the preview we reset the heights of the preview element and the expanded item. Once this is done, the preview element / structure gets removed from the DOM.

	close : function() {

		var self = this,
			onEndFn = function() {
				if( support ) {
					$( this ).off( transEndEventName );
				}
				self.$item.removeClass( 'og-expanded' );
				self.$previewEl.remove();
			};

		setTimeout( $.proxy( function() {

			if( typeof this.$largeImg !== 'undefined' ) {
				this.$largeImg.fadeOut( 'fast' );
			}
			this.$previewEl.css( 'height', 0 );
			// the current expanded item (might be different from this.$item)
			var $expandedItem = $items.eq( this.expandedIdx );
			$expandedItem.css( 'height', $expandedItem.data( 'height' ) ).on( transEndEventName, onEndFn );

			if( !support ) {
				onEndFn.call();
			}

		}, this ), 25 );
		
		return false;

	}

And that's it! We hope you enjoyed this tutorial and find it inspiring!

Manoela Ilic

Manoela is the main tinkerer at Codrops. With a background in coding and passion for all things design, she creates web experiments and keeps frontend professionals informed about the latest trends.

Stay in the loop: Get your dose of frontend twice a week

Fresh news, inspo, code demos, and UI animations—zero fluff, all quality. Make your Mondays and Thursdays creative!

Feedback 477

Comments are closed.
    • I was also wondering inserting an image slider into the expanded view, but having trouble linking it inside.

      Please let me know if you figure a solution.

      Thanks!
      Josh

  1. Hey,

    Great tutorial – so awesome to use & modify.

    Has anyone solved the mobile issue yet? Namely that the .og-expanded closes automatically right after opening it. I read through all the comments but can’t seem to find any solution yet.

    Thanks!

    • Yes this has been solved. Open the grid.js file and replace the top piece of code between the comment sections with the following:

      var $event = $.event,
      $special,
      dummy = {_:0},
      frame = 0,
      wasResized, animRunning;

      $special = $event.special.throttledresize = {
      setup: function() {
      $( this ).on( “resize”, $special.handler );
      },
      teardown: function() {
      $( this ).off( “resize”, $special.handler );
      },
      handler: function( event, execAsap ) {
      // Save the context
      var context = this,
      args = arguments;

      wasResized = true;

      if ( !animRunning ) {
      setInterval(function(){
      frame++;

      if ( frame > $special.threshold && wasResized || execAsap ) {
      // set correct event type
      event.type = “throttledresize”;
      $event.dispatch.apply( context, args );
      wasResized = false;
      frame = 0;
      }
      if ( frame > 9 ) {
      $(dummy).stop();
      animRunning = false;
      frame = 0;
      }
      }, 30);
      animRunning = true;
      }
      },
      threshold: 0
      };

    • The above snippet provided by Dan has the wrong the of quotes which will break Javascript. Copy and paste below to fix the iOS open/close issue:


      var $event = $.event,
      $special,
      dummy = {_:0},
      frame = 0,
      wasResized, animRunning;
      $special = $event.special.throttledresize = {
      setup: function() {
      $( this ).on( 'resize', $special.handler );
      },
      teardown: function() {
      $( this ).off( 'resize', $special.handler );
      },
      handler: function( event, execAsap ) {
      // Save the context
      var context = this,
      args = arguments;
      wasResized = true;
      if ( !animRunning ) {
      setInterval(function(){
      frame++;
      if ( frame > $special.threshold && wasResized || execAsap ) {
      // set correct event type
      event.type = 'throttledresize';
      $event.dispatch.apply( context, args );
      wasResized = false;
      frame = 0;
      }
      if ( frame > 9 ) {
      $(dummy).stop();
      animRunning = false;
      frame = 0;
      }
      }, 30);
      animRunning = true;
      }
      },
      threshold: 0
      };

      John

    • Hi John,

      I replaced to top piece of the code in the grid.js file between the two first comment sections, but it still doesn’t work on my iPad 2 (the expander closes immediately)…. Any Idea? Could you give the whole grid.js file?

  2. I love the tutorial. Is there a way to add multiple images to the expansion?

    Right now there is only the “data-largesrc” for the image. I’m wanting it to display two smaller images instead of the single large image. Any solutions for this?

    Thanks

  3. Hey,

    I have seen some other people asking but I thought I’d asked again!

    Does anyone know how to include multiple thumbnail grids on a single page?

  4. Fantastic plugin!

    Have implemented this on my site and it looks great.

    Only issue is that when opening and closing a few images, after a while there is a great big empty space in between the rows, with the tiny arrow head still under the images that were clicked on… does anyone know how to fix this?

    Thanks

    • I am having this same issue. Are you trying to use more than one instance of the grid on your site?

      I changed the id “#og-grid” to a class ” .og-grid” in the grid.js file to try to allow for multiple instances of the grid on my site but now I have this problem with the empty spaces and the arrows remaining. I think the transitions aren’t correctly taking place. I have no idea how to fix it!

    • It happened to me also, when I tried to add some content inside the
      Try deleting all content that are not the from inside the list and this should fix your problem.

      In order to make it work I created a new li
      And to customize that class in the css.

    • Sorry about late reply.

      It seems Bachu is right. Remove any additional content that is outside of the list items and that should fix it.

  5. Amazing tutorial! There is still the iPad issue that I haven’t been able to figure out… I see some people have been able to do it. Could update the grid-js?

  6. I tried embedding a vimeo video in the data-largesrc html with no luck, any advice?

  7. Hi. have someone found a solution for using this awesome tutorial integrated with an image slider??. im having problems trying to get it working… By the way. thanks for this tutorial!!!! is just amazing!!

  8. Hi,
    Thanks for this script and tutorial, however, I have some issue trying to implement it to my Drupal 7 theme. At the best I’m getting the following error:
    Uncaught ReferenceError: Grid is not defined
    Can someone please help me to find the cause?

  9. Good Day!

    I do have a question. How can i add a facebook comment plugin below on the description area? Hope to hear from you soon.

    Thank you.

  10. It has a problem on iphone 4s. Preview doesnt work. If you touch thumbnail on iphone 4s screen preview opening and closing suddenly.

  11. Thank you much for providing your time to develop these wonderful tutorials.! I was experiencing a sizing issue on internet explorer for a dynamically loaded image when expanded. I fixed this issue by adding width: auto; height: auto; to .og-fullimg img in component.css.. FYI.

  12. Hi,

    Im trying to implement this script on a little project. I can get it to work but i want to use it with dynamicly loaded data.

    In my case i would like to fill the ul with images based on a search, the result comes back as json and i use jQuery template to fill the #og-grid.

    I find two problems:

    1. If i add data with jQuery.append like $(“.og-grid”).append(‘‘);
    i cannot bind click events to this objekt. I tried to append before i set Grid.Init and after, either way it does not work.
    2. If call the Grid.Init() at pageload and after the dynamic data is loaded i cannot se the expanding preview.

    Is there a way to init the the gridfunction after dynamicdata is loaded and multiple times soo it can be used with for example a infinit scroll solution?

  13. Hi there,

    Got this up and running fine though I wondered if anyone figured out how to add a next/previous to each of the list items. I have managed to dynamically add buttons from the grid.js but can figure how to make them work… I see on Google you can use the cursor keys to navigate too!

  14. Hi. Awesome script!

    I´m facing a height issue in my expanded view. I´d like to fix sizes for images (600x400px).
    I modified the css a bit to make it work, also added another text option, but since the beginning, I get a big blank under my image.

    Also, I was wondering if it´s possible for the web link to show, only if the image has a link.
    I´ll be using the script for my portfolio, where I have some branding images that should have no web link, and some other websites, which should have the web link.

    Help please!
    Thanks again!

    • For making the script able to post or not websites, something like whit is needed.
      First I changed the default button.
      this.$href = $( ‘‘ );
      Second, I need a programmer to fill, the space between and with the href var.
      Thanks

    • Hi Bachu,

      I have the same problem you described about having a link in the description only when you want it to.
      Did you by any chance find a solution yet?

      Thanks 🙂
      u*girl

    • Same thing here gang. Some of the samples I am showing do not have a link yet. I experimented with adding a class to the “href” in the .js and a class to the in the mark up for the sample that had no link. My plan was to target and style it differently (grey it out) and make it an inactive link to be like a “coming soon” button. It worked but if the next sample image is clicked without closing the expander the styles remain for the next image and thus making a potential active link, inactive. How can we just fill the expander with different content or different formats when everything is dependent on the mark up pulling from the .js???

      Help is greatly appreciated 🙂

      – Anthony

  15. Hi,
    very nice tutorial.
    But, how can I change height of “og-expander” ? There is in the file “grid.js” function something like :

    $body.animate( { scrollTop : scrollVal }, settings.speed );

    What I have to change, if I dont want (scrollTop – 100px) ? And the same 100px in the bottom of the window ?

    Again, thank You very much for this tutorial. 🙂

  16. Hello, thank you for this tutorial – it’s exactly what I was looking for and didn’t know how to create!
    I’m using this in a ‘responsive’ site layout, and was wondering.. is there a way to make the “expander” height adjust to only what text is in the box (which extends passed the image – so some text is getting ‘cut off’) – as well as adjust the expander height in mobile device views?

    Thank you for your time! I’ve greatly appreciated your work.
    Mary~

  17. Does anyone know how to give the images in the grid a border and also change colors on hover? Thanks!

    • I’m looking for the same effect. Image replacement isn’t an option since we are using for the grid image. Maybe a css3 filter effect? This definitely won’t work on every browser, but, in the name of progressive enhancement, I’ll be satisfied with only advanced browsers since it isn’t essential.

  18. I don’t know if i’m being stupid but i’ve tried integrating this a number of times and the expanding never works, when you click on the thumbnail it links straight to the page that the visit website button should go to, someone help, please, i’m getting really frustrated because i really want to use it

    • It’s inside the JS file,

      look for this line

      this.$href = $( '<a href="#" rel="nofollow">Visit Website</a>' );

      hope this helps, it's on line 331 in my document but i've added more functions so it won't be the same line for you however should be nearby

  19. Is it possible to set the preview height to auto? and to add additional contents like another image? Thanks!

  20. Hi,

    Thanks for the awesome tutorial. However, I have been facing issues trying to implement image filter with this. Somehow, either the image expanding works or the image filter. Couldn’t figure out the problem though. Is anyone else having a problem implementing an image filter? Or if anyone has used an image filter and it is working please let me know which one is it?

    Thanks.

  21. Thank you very much for your easy-to-follow tutorials. With this knowledge I built a website for my first paying client.

  22. Hello! It has emerged problema.Necesito change the phrase “Visit website” the button. How do I? Simply I need to change a button that says “Print”. Thank you very much.

    • It’s inside the javascript file,

      It’s in the following section

      Preview.prototype = { create : function() { // create Preview structure: this.$title = $( '' ); this.$description = $( '' ); this.$href = $( '<a href="#" rel="nofollow">Visit Website</a>' ); this.$details = $( '' ).append( this.$title, this.$description, this.$href ); this.$loading = $( '' ); this.$fullimage = $( '' ).append( this.$loading ); this.$closePreview = $( '' ); this.$previewInner = $( '' ).append( this.$closePreview, this.$fullimage, this.$details ); this.$previewEl = $( '' ).append( this.$previewInner ); // append preview element to the item this.$item.append( this.getEl() ); // set the transitions for the preview and the item if( support ) { this.setTransition(); } },

      this line here

      this.$href = $( '<a href="#" rel="nofollow">Visit Website</a>' );

      It’s on line 331 in my js file however ive made changes to mine so it won’t be the same line on yours but will be somewhere around there

  23. After some time, and not finding a solution for the ‘multiple grid’ problem, I found a solution. It’s clearly not the optimal way to do it but it gets the job done. Basically just duplicate (or triplicate, depending on how many grids you need) the Grid var inside grid.js and add a number or something to the var name (Grid2, Grid3, etc.) Then declare a counting variable ($i or whatever) and set it to 1. After that it’s just ‘if’ statements.

    $('a.next').click(function() { if( $i == '1' ){ Grid.init(); } if( $i == '2' ){ delete this.Grid; Grid2.init(); } if( $i == '3' ) { delete this.Grid; delete this.Grid2; Grid3.init(); } $i++; });

    PS: I add to $i on an <a> click event, you’ll need to adapt the event to your own site structure and needs. If you’re on a one page site, you could do it whenever you press the section’s <a> and call it with the id, but that would require several functions, ie:

    $('a#gallery1').click(function() { Grid.init(); } $('a#gallery2').click(function() { delete Grid.init(); Grid2.init(); }

    …and so on.

    Hope it helps someone!

  24. Hey. For some reason once I close the expanded box it leaves a white space where the content was. any ideas how to prevent this from happening. hope you can help thanks

  25. What a beautiful piece of coding. So nice and graceful. I know this is supposed to mimic Google, but is there a way to change expander size dynamically? My images are different sizes and I’d like the size of expander element to adapt to the image size.

  26. I have spent a whole day trying to get this to work with Infinite Scroll and it works beautifully in terms of the thumbnails being appended to the grid. I noticed many people in the comments asking how to load more thumbnails dynamically and the solution is quite simple. In the init function you just need to add $items = $grid.children( ‘li’ ); . Then run the init function after your ajax is complete.

    The problem is it goes all crazy after appending new items when you start expanding items. They either don’t work or it scrolls to the wrong position or it closes and leaves a big white gap. Please please could someone give any hints as to how to make this work with appended items? I have hacked and hacked and console.logged everything but i cannot find a solution. Mary Lou you are the queen of the internet, please help us out to make this truly function like googles version.

    • Hi Carlos,
      I added a function to add more items to the grid. The zip file is updated. Check out this example (on the bottom of the page you will see a “add more items” link): add more items example
      Hope it helps!
      Cheers, ML

    • Thank you for such a quick response Mary Lou, that is exactly what i needed. This site is hands down the best resource for web designers and i hope you are making a fortune from advertising because you deserve it!!

    • Thank you. I updated the JS, and while it now allows me to use multiple grids, the ‘data-largesrc’ images aren’t loading on expansion. This function was working fine in the initial installment of the script. Any thoughts are to why this could be happening?

    • To get the ‘data-largesrc’ back, in the new grid.js, change the 410 ~ 426:

      if( self.$fullimage.is( ‘:visible’ ) ) {
      this.$loading.show();
      $( ” ).load( function() {
      var $img = $( this );
      if( $img.attr( ‘src’ ) === self.$item.children(‘a’).data( ‘largesrc’ ) ) {
      self.$loading.hide();
      self.$fullimage.find( ‘img’ ).remove();
      self.$largeImg = $img.fadeIn( 350 );
      self.$fullimage.append( self.$largeImg );
      }
      } ).attr( ‘src’, eldata.largesrc );
      }

      },

      to:

      if( self.$fullimage.is( ‘:visible’ ) ) {
      this.$loading.show();
      $( ” ).load( function() {
      var $img = $( this );
      if( $img.attr( ‘src’ ) === self.$item.children(‘a’).data( ‘largesrc’ ) ) {
      self.$loading.hide();
      self.$fullimage.find( ‘img’ ).remove();
      self.$largeImg = $img.fadeIn( 350 );
      self.$fullimage.append( self.$largeImg );
      }
      } ).attr( ‘src’, eldata.largesrc );
      }

      },

      This solved my problem.

    • Hi, I tryed to use your fix for multiple instances but it doesn’n work 🙁
      On click nothing happens…
      In my page there are many list like this:
      <ul class="og-grid"> <li><a href ....

      And I initialize the script in this method:
      $(function() { Grid.init(); });

      It’s correct?

  27. There is an issue with IE 9. When you click over one of the grids, it open up perfectly. However when the expanded content, it leaves the space between grid rows.

    • One user suggested using throttledresize instead of debounced resize to fix IE issues…
      https://github.com/louisremi/jquery-smartresize/blob/master/jquery.throttledresize.js

      However I found that when I replaced it and also remembered to replace its event in the InitEvents function it did exactly the same. So i have come to the conclusion that this cannot be used in a responsive way without breaking IE 8/9. To make it work in IE in a fixed width scenario i simply removed the $window.on( 'debouncedresize', function() { function. Which left me with one last issue for some reason in IE clicking on a thumb lower than the current one caused it to scroll to the wrong spot. It turns out for some bizarre reason in IE the scrollExtra value always needs to be 0. So i used this little code to detect IE and forced it to always be 0 in the showPreview function. https://gist.github.com/padolsey/527683 Although a bit hacky I finally I think I have this working in IE 🙂

  28. @MarkyMark: I am not experiencing this issue. It works fine for me.
    @Steinberg: At some point i may fix this, considering im not getting payed for it, it may not be soon. 🙂

  29. can somebody please explain clearly to me how i remove the “visit website” icon in the expanded image – forgive me i know so little of webbuilding (…but i’m starting…), have mercy on my soul…

  30. Thanks for the Jquery
    i need to add multiple images like google is showing.
    How can i do that ?

  31. Hi,
    can somebody help me.
    I want do add more than just 1 picture into the expander and all of the items should be centered.
    maybe you can help me 🙂

    Dennis

  32. ok, i found out how to remove the visit website link…was already posted – silly me – anyway, any one an idea on how to resize the expanded image to it’s original size (mine are for example all 552x827px) instead of the fixed box height? – merci beaucoup 🙂

  33. Hi everyone,

    Is there any way to have the “Visit Website” link appear ONLY when I want it to? Basically, most images will be expanded previews without the “Visit Website” link. But I have 3-5 images that I would like to lead to other pages. I really want to use this, please someone help!

  34. Hi, this is fantastic, but I have a problem.

    Rather than going the tutorial route I picked apart the demo files and integrated them, the only problem is that for some reason linking the CSS files affects the font-weight of the text in my header and I’ve got no idea how to rectify this because it doesn’t look like there is any conflicting CSS classes or anything like that.

  35. Is there a way to integrate Previous and Next on the opened previews?

  36. Hello!

    Could you tell me how can i make the unraveling block is unfixed 500px and it can change its size according to height of content

    • You can find the function calcHeight on line 470 and below two line

      this.height = heightPreview;
      this.itemHeight = itemHeight;

      replace to

      this.height = settings.minHeight;
      this.itemHeight = settings.minHeight+ this.$item.data( ‘height’ );

      then it can change its size according to the height of content.

    • Thanks for your help! I have changed

      this.height = heightPreview;
      this.itemHeight = itemHeight;

      on

      this.height = settings.minHeight;
      this.itemHeight = settings.minHeight + this. $ item.data (‘height’);

      but the block og-expander is a fixed height 500px;

      May be i can add something else?

      http://sgustokmusic.org/Demo/index.html

  37. Marvelous! But it seems there’s one bug when viewing this work on an iPad (first version): the preview closes immediately after having been opened.
    Any idea how to solve the problem?

    • I had the same issue. It has to do with this line in the initEvents() function. I was able to comment this block out and everything still works fine. Not sure what the actual problem is, maybe it doesn’t know what to do with ‘typeof’.

      if( typeof preview != ‘undefined’ ) {
      hidePreview();
      }

  38. Hello!

    Could you tell me how can i make the unraveling block is unfixed 500px and it can change its size according to height of content?

    • .og-details { padding: 0 5px 0 10px; position: absolute; (Might need it) width: (Might need it) overflow:auto; <----(Most likely need so you get a scroller) }

      Try that and let everyone know how you got on.

  39. I’m working on implementing this with WordPress posts but am having some trouble filling the post image and the post content/excerpt into “data-largesrc” and “data-description”. I believe I have to use JS to fill those fields with the correct PHP but I am struggling with this. Has anyone tried to combine this with WordPress by chance?