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

View demo Download source

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 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">
		<a href="#">
			<img src="images/thumbs/1.jpg" />
			<div class="pe-description">
				<p>Since time, and his predestinated end</p>
	<li><!-- ... --></li>

Let’s style it!


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:

	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{
	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(''),
	// 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() {
	// Get Value of CSS Scale 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") ||

		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;
			$ '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		= $,
				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() - $ 'space_l' ) );
				$desc.fadeIn( 800 );
			else {
				$li.css( 'z-index', 1 );
				'-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() {
				if( loaded === total );
			}).attr( 'src', $el.attr('src') );

return {
	init	: init


Check out all three examples:

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

View demo Download source


Tagged with:

ML is a freelance web designer and developer with a passion for interaction design. She studied Cognitive Science and Computational Logic and has a weakness for the smell of freshly ground peppercorns.

View all contributions by


Related Articles

CSS Reference

Learn about all important CSS properties from the basics with our extensive and easy-to-read CSS Reference.

It doesn't matter if you are a beginner or intermediate, start learning CSS now.

Feedback 39

  1. 3

    I noticed there is a nice fallback for IE7/8. However, in Mobile Safari the full version shows – but of course can’t be used since no rollovers on an iPad.

    It would be better to display the IE7 version on mobile devices for this reason. How would I accomplish that?

  2. 4

    Steve –
    what ‘nice fallback’ are you referring to for IE7/8? It’s merely a broken-looking grid that has no roll over effect, with functionality stripped. Or are you referring to something else you implemented?

  3. 10

    Great! A 4th example with an overlay on click of a picture would be nice or something like that don’t you think so?

  4. 11

    Really nice effect, improved a bit by adding ‘overflow:hidden;’ to ‘.pe-thumbs li a, .pe-thumbs li a img’ as follows:

    .pe-thumbs li a, .pe-thumbs li a img {

  5. 12

    By adding JQuery sortable to this I was able to do what would, otherwise be a really complicated UI, in a really simple and wicked way… So, again Mary (and I’m fully aware that I must be repeating myself). THANK YOU.

  6. 13

    Really nice! Is it possible to use any box script in conjunction? I tried a few different ones but to no avail…

  7. 14

    i its a really nice effect.. but it does not work on IE7/8.. the thumbs just froze all together. please i need a solution..

  8. 15

    Yes, it’s broken in IE7/8 – nothing is clickable, is there a patch coming out soon for this? We love the script but it needs a bit of love!

  9. 16

    This is great! I got it to work with highslide js – you can click on an image and go to a YouTube video for ex. (test site). Bummed that the effect doesn’t work on an iPad, would be perfect if it could trace your finger. Love all the awesome tutorials here though!

  10. 17

    Hi Mary Lou,

    I’ve just tried to implement this tutorial you’ve created and the functionality doesn’t appear to work in IE7/8.

    Are there any workarounds to get this fantastic script to work in IE7/8? I’ve read through the comments and there doesn’t seem to be any response or solution to this issue.

    Your help or response would be greatly appreciated, especially in this community too.


    • 18

      Does anybody have a solution to get this fantastic script run in IE 7/8/9?
      Thanks for an answer!

Follow this discussion

Leave a Comment