Thumbnail Proximity Effect with jQuery and CSS3

Today we want to show you how to create a neat thumbnail proximity effect with jQuery. The idea is to scale thumbnails when hovering over them and also scale their neighbouring thumbnails proportionally to their distance. We’ll also make a description appear.

Thumbnail Proximity Effect

Today we want to show you how to create a neat thumbnail proximity effect with jQuery. The idea is to scale thumbnails when hovering over them and also scale their neighbouring thumbnails proportionally to their distance. We’ll also make a description appear.

This is based on a request we once got from a reader who asked if the effect on http://porscheeveryday.com/ could be achieved with jQuery. This is our attempt to recreate the basics of that effect.

We will be using the jQuery Proximity plugin by James Padolsey.

The illustations in the demos are by Florian Nicolle and they are licensed under the Creative Commons Attribution-NonCommercial 3.0 Unported License.

The Markup

We’ll use an unordered list for the thumbnails and add a division for the description of each image (for this tutorial, we will be going through demo 2):

<ul id="pe-thumbs" class="pe-thumbs">
	<li>
		<a href="#">
			<img src="images/thumbs/1.jpg" />
			<div class="pe-description">
				<h3>Time</h3>
				<p>Since time, and his predestinated end</p>
			</div></a>
	</li>
	<li><!-- ... --></li>
</ul>

Let’s style it!

The CSS

The unordered list will be centered on the page and we’ll add a background image to “shine through” the thumbnails that will have a low opacity:

.pe-thumbs{
	width: 900px;
	height: 400px;
	margin: 20px auto;
	position: relative;
	background: transparent url(../images/3.jpg) top center;
	border: 5px solid #fff;
	box-shadow: 0 1px 2px rgba(0,0,0,0.2);
}

We’ll also add a bit of tint to the background image by adding a pseudo element with a rgba background color:

.pe-thumbs:before {
	content: "";
	display: block;
	position: absolute;
	top: 0px;
	left: 0px;
	width: 100%;
	height: 100%;
	background: rgba(255,102,0,0.2);
}

The list items will float left and we’ll set the position of the anchors and images to relative:

.pe-thumbs li{
	float: left;
	position: relative;
}
.pe-thumbs li a,
.pe-thumbs li a img{
	display: block;
	position: relative;
}

Each thumbnail will have an initial width of 100 pixels and an opacity of 0.2:

.pe-thumbs li a img{
	width: 100px;
	opacity: 0.2;
}

The discription will be hidden by default and then we will make it appear using JavaScript:

.pe-thumbs li a div.pe-description{
	width: 200px;
	height: 100px;
	background: rgba(0,0,0,0.8);
	position: absolute;
	top: 0px;
	left: -200px;
	color: white;
	display: none;
	z-index: 1001;
	text-align: left;
}

And finally, let’s style the content of the description division:

.pe-description h3{
	padding: 10px 10px 0px 10px;
	line-height: 20px;
	font-family: 'BebasNeueRegular', 'Arial Narrow', Arial, sans-serif;
	font-size: 22px;
	margin: 0px;
}
.pe-description p{
	padding: 10px 0px;
	margin: 10px;
	font-size: 11px;
	font-style: italic;
	border-top: 1px solid rgba(255,255,255,0.3);
}

And that’s all the style! Let’s move on to the JavaScript!

The JavaScript

The main idea is to first of all calculate the description container size and position when we hover over a thumbnail. This depends of course on the maximum scale of the thumbnail and where the thumbnail is located in the main wrapper. For example, when the thumbnail is close to the right edge, we’ll want to make the description box appear on the left side of the thumbnail.

Then we’ll need to bind the proximity event to the images. The idea is to scale the images up or down according to the position of the mouse. Once the image scales to the maximum value, we set its z-index very high, so that it stays on top, and we show the respective description:

	// list of thumbs
var $list		= $('#pe-thumbs'),
	// list's width and offset left.
	// this will be used to know the position of the description container
	listW		= $list.width(),
	listL		= $list.offset().left,
	// the images
	$elems		= $list.find('img'),
	// the description containers
	$descrp		= $list.find('div.pe-description'),
	// maxScale : maximum scale value the image will have
	// minOpacity / maxOpacity : minimum (set in the CSS) and maximum values for the image's opacity
	settings	= {
		maxScale	: 1.3,
		maxOpacity	: 0.9,
		minOpacity	: Number( $elems.css('opacity') )
	},
	init		= function() {
		
		// minScale will be set in the CSS
		settings.minScale = _getScaleVal() || 1;
		// preload the images (thumbs)
		_loadImages( function() {
			
			_calcDescrp();
			_initEvents();
		
		});
	
	},
	// Get Value of CSS Scale through JavaScript:
	// http://css-tricks.com/get-value-of-css-rotation-through-javascript/
	_getScaleVal= function() {
	
		var st = window.getComputedStyle($elems.get(0), null),
			tr = st.getPropertyValue("-webkit-transform") ||
				 st.getPropertyValue("-moz-transform") ||
				 st.getPropertyValue("-ms-transform") ||
				 st.getPropertyValue("-o-transform") ||
				 st.getPropertyValue("transform") ||
				 "fail...";

		if( tr !== 'none' ) {	 

			var values = tr.split('(')[1].split(')')[0].split(','),
				a = values[0],
				b = values[1],
				c = values[2],
				d = values[3];

			return Math.sqrt( a * a + b * b );
		
		}
		
	},
	// calculates the style values for the description containers,
	// based on the settings variable
	_calcDescrp	= function() {
		
		$descrp.each( function(i) {
		
			var $el		= $(this),
				$img	= $el.prev(),
				img_w	= $img.width(),
				img_h	= $img.height(),
				img_n_w	= settings.maxScale * img_w,
				img_n_h	= settings.maxScale * img_h,
				space_t = ( img_n_h - img_h ) / 2,
				space_l = ( img_n_w - img_w ) / 2;
			
			$el.data( 'space_l', space_l ).css({
				height	: settings.maxScale * $el.height(),
				top		: -space_t,
				left	: img_n_w - space_l
			});
		
		});
	
	},
	_initEvents	= function() {
	
		$elems.on('proximity.Photo', { max: 80, throttle: 10, fireOutOfBounds : true }, function(event, proximity, distance) {
			
			var $el			= $(this),
				$li			= $el.closest('li'),
				$desc		= $el.next(),
				scaleVal	= proximity * ( settings.maxScale - settings.minScale ) + settings.minScale,
				scaleExp	= 'scale(' + scaleVal + ')';

			// change the z-index of the element once 
			// it reaches the maximum scale value
			// also, show the description container
			if( scaleVal === settings.maxScale ) {
				
				$li.css( 'z-index', 1000 );
				
				if( $desc.offset().left + $desc.width() > listL + listW ) {
					
					$desc.css( 'left', -$desc.width() - $desc.data( 'space_l' ) );
				
				}
				
				$desc.fadeIn( 800 );
				
			}	
			else {
				
				$li.css( 'z-index', 1 );
			
				$desc.stop(true,true).hide();
			
			}	
			
			$el.css({
				'-webkit-transform'	: scaleExp,
				'-moz-transform'	: scaleExp,
				'-o-transform'		: scaleExp,
				'-ms-transform'		: scaleExp,
				'transform'			: scaleExp,
				'opacity'			: ( proximity * ( settings.maxOpacity - settings.minOpacity ) + settings.minOpacity )
			});

		});
	
	},
	_loadImages	= function( callback ) {
		
		var loaded 	= 0,
			total	= $elems.length;
		
		$elems.each( function(i) {
			
			var $el = $(this);
			
			$('').load( function() {
				
				++loaded;
				if( loaded === total )
					callback.call();
				
			}).attr( 'src', $el.attr('src') );
		
		});
	
	};

return {
	init	: init
};

Demos

Check out all three examples:

  1. Demo 1: Scale
  2. Demo 2: Scale with description
  3. Demo 3: Less scaling with description

Tagged with:

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 up to date with the latest web design and development news and relevant updates from Codrops.