image zoom

Image Zoom Tour with jQuery

Today we want to share a little zoom tour script with you. Showing a main image initially, we want to be able to zoom into certain parts of the image by clicking on tags, using another image for the closer view. This next step can contain other tags that again allow to show more images. We achieve the “zoom” effect by enlarging the current image and fading in the new one.

Today we want to share a little zoom tour script with you. Showing a main image initially, we want to be able to zoom into certain parts of the image by clicking on tags, using another image for the closer view. This next step can contain other tags that again allow to show more images. We achieve the “zoom” effect by enlarging the current image and fading in the new one.

The images used in the demos can be found here:
image1: City Crowd
image1_5: Office
image2: Ikea Room
image3_n: Portimão Marina
Background Pattern by http://www.blunia.com/ on http://subtlepatterns.com/

The HTML Structure

For the HTML structure we will have a main container and each image inside of a div with the class “zt-item”:

<div id="zt-container" class="zt-container">
    <div class="zt-item" data-id="zt-item-1">
        <img class="zt-current" src="images/image1.jpg" />
        <div class="zt-tag" data-dir="1" data-link="zt-item-2" data-zoom="5" data-speed="700" data-delay="50" style="top:138px;left:151px;"></div>
        <div class="zt-tag" data-dir="1" data-link="zt-item-3" data-zoom="6" data-speed="700" data-delay="50" style="top:253px;left:520px;"></div>
    </div>
    <div class="zt-item" data-id="zt-item-2">
        <img class="zt-current" src="images/image1_1.jpg" />
        <div class="zt-tag zt-tag-back" data-dir="-1" data-link="zt-item-1" data-zoom="5" data-speed="500" data-delay="0"></div>
    </div>       
    <div class="zt-item" data-id="zt-item-3">
        <img class="zt-current" src="images/image2_2.jpg" />
        <div class="zt-tag zt-tag-back" data-dir="-1" data-link="zt-item-1" data-zoom="6" data-speed="500" data-delay="0"></div>
    </div>           
</div>
<img class="zt-current" src="/tech/images/imageX.jpg" /> 

is the image in each step. The item then have tags with certain attributes.
The data attributes are the following:

data-dir is either 1 or -1 depending on whether we want to “zoom in” (1) or “zoom back out” (-1)
data-link will indicate to which item we connect the tag to (based on the data-id we give to each item)
data-zoom is the factor of zooming. Set very low, the image we zoom into or zoom back to will only enlarge slightly.
data-speed the animation speed in milliseconds
data-delay the delay time for the new image to appear
The example structure above has the first initial image (zt-item-1) with two tags that lead to zt-item-2 and zt-item-3. zt-item-2 and zt-item-3 only have the back tag. As you can see, the back tag has another class “zt-tag-back” and a data-dir value of -1.

The position of the tags is defined in the style attribute and you can also add another size for it, too.

Options
The following are the default options:

$('#zt-container').zoomtour({
    // if true the tags are rotated depending on their position
    rotation        : true,
    // zoom out animation easing. Example: easeOutBounce , easeOutBack 
    zoominEasing    : '', 
    // zoom out animation easing
    zoomoutEasing   : ''   
});

Jquery Example

(function($) {
	var	tag			= {
			saveInitialData		: function( $tag ) {
				$tag.data({
					width	: $tag.width(),
					height	: $tag.height(),
					left	: $tag.position().left,
					top		: $tag.position().top
				}).addClass( $tag.data('link') );
			},
			rotate				: function( $tag, cache ) {
				
				// element's center
				var center		= {
					x	: $tag.position().left + $tag.width() / 2,
					y	: $tag.position().top + $tag.height() / 2
				};
				
				var quadrant	= tag.getElementQuadrant( center, cache );
				// distance from element's center to the quadrants origin
				var dist_element;
				switch( quadrant ) {
					case 1 :
						dist_element = Math.sqrt( Math.pow( ( center.x - 0 ), 2 ) + Math.pow( ( center.y - 0 ), 2 ) );
						break;
					case 2 :
						dist_element = Math.sqrt( Math.pow( ( center.x - cache.ztdim.x ), 2 ) + Math.pow( ( center.y - 0 ), 2 ) );
						break;
					case 3 :
						dist_element = Math.sqrt( Math.pow( ( center.x - 0 ), 2 ) + Math.pow( ( center.y - cache.ztdim.y ), 2 ) );
						break;
					case 4 :
						dist_element = Math.sqrt( Math.pow( ( center.x - cache.ztdim.x ), 2 ) + Math.pow( ( center.y - cache.ztdim.y ), 2 ) );
						break;
				}
				var anglefactor = 25;
				var angle	= ( ( cache.dist_center - dist_element ) / cache.dist_center ) * anglefactor;
				
				switch( quadrant ) {
					case 1 :
						$tag.data( 'rotate', angle ).transform({'rotate'	: angle + 'deg'});
						break;
					case 2 :
						$tag.data( 'rotate', -angle ).transform({'rotate'	: -angle + 'deg'});
						break;
					case 3 :
						$tag.data( 'rotate', -angle ).transform({'rotate'	: -angle + 'deg'});
						break;
					case 4 :
						$tag.data( 'rotate', angle ).transform({'rotate'	: angle + 'deg'});
						break;
				}
		
			},
			getElementQuadrant	: function( c, cache ) {
				if( c.x <= cache.ztdim.x / 2 && c.y <= cache.ztdim.y / 2 ) 
					return 1;
				else if( c.x > cache.ztdim.x / 2 && c.y <= cache.ztdim.y / 2 ) 
					return 2;	
				else if( c.x <= cache.ztdim.x / 2 && c.y >= cache.ztdim.y / 2 ) 
					return 3;
				else if( c.x > cache.ztdim.x / 2 && c.y > cache.ztdim.y / 2 ) 
					return 4;
			}
		},
		viewport	= {
			zoom				: function( $ztcontainer, $tag, cache, settings ) {
				var $ztitem			= $tag.closest('div.zt-item'),
					ztitemid		= $ztitem.data('id'),
					$ztimage		= $ztitem.children('img.zt-current'),
					
					zoomfactor		= $tag.data('zoom'),
					speedfactor		= $tag.data('speed'),
					imgdelayfactor 	= $tag.data('delay'),
					link			= $tag.data('link'),
					dir				= $tag.data('dir'),
					
					$new_ztitem		= $ztcontainer.find('div.' + link),
					$new_ztitemimage= $new_ztitem.find('img.zt-current'),
					
					zoomAnimation	= true;
				
				if( !link ) return false;
				
				if( zoomfactor === undefined || speedfactor === undefined || imgdelayfactor === undefined || dir === undefined )
					zoomAnimation = false;
					
				( dir === 1 ) ? 
					viewport.zoomin( $tag, $ztitem, ztitemid, $ztimage, zoomfactor, speedfactor, imgdelayfactor, zoomAnimation, $new_ztitem, $new_ztitemimage, cache, settings ) : 
					viewport.zoomout( $tag, $ztitem, ztitemid, $ztimage, zoomfactor, speedfactor, imgdelayfactor, zoomAnimation, $new_ztitem, $new_ztitemimage, cache, settings );		
			},
			zoomin				: function( $tag, $ztitem, ztitemid, $ztimage, zoomfactor, speedfactor, imgdelayfactor, zoomAnimation, $new_ztitem, $new_ztitemimage, cache, settings ) {
				
				var el_l 			= $tag.data('left') + $tag.data('width') / 2,
					el_t 			= $tag.data('top') + $tag.data('height') / 2;
				
				$.fn.applyStyle 	= ( zoomAnimation ) ? $.fn.animate : $.fn.css;
				
				$ztimage.applyStyle( {
					width	: cache.ztdim.x * zoomfactor + 'px',
					height	: cache.ztdim.y * zoomfactor + 'px',
					left	: - ( ( zoomfactor * ( 2 * el_l ) ) - ( 2 * el_l ) ) / 2 + 'px',
					top		: - ( ( zoomfactor * ( 2 * el_t ) ) - ( 2 * el_t ) ) / 2 + 'px'
				}, $.extend( true, [], { duration : speedfactor } ) );
				
				// hide all the other tags (for the current item)
				$ztitem.children('div.zt-tag').hide();
				
				var imgAnimationCss	= {
					width	: cache.ztdim.x + 'px',
					height	: cache.ztdim.y + 'px',
					left	: '0px',
					top		: '0px',
					opacity	: 1
				};
				if( settings.rotation && !cache.ieLte8 )
					imgAnimationCss.rotate	= '0deg';
				
				var newztimg		= $new_ztitemimage.attr('src');
				
				var tagCss	= {
					position	: 'absolute',
					width		: $tag.data('width'),
					height		: $tag.data('height'),
					left		: $tag.data('left'),
					top			: $tag.data('top')
				};
				if( settings.rotation && !cache.ieLte8 )
					tagCss.rotate	= $tag.data('rotate') + 'deg';
				
				$ztitem.append(
					$('<img src="' + newztimg + '" class="zt-temp"></img>')
					.css( tagCss )
					.delay(imgdelayfactor)
					.applyStyle( imgAnimationCss, $.extend( true, [], { duration : speedfactor, easing : settings.zoominEasing, complete : function() {
						viewport.zoominCallback( $(this), $new_ztitem, $ztitem, $ztimage, cache );
					} } ) )	
				);
				
				if( !zoomAnimation )
					viewport.zoominCallback( $ztitem.find('img.zt-temp'), $new_ztitem, $ztitem, $ztimage, cache );
			},
			zoominCallback		: function( $zttemp, $new_ztitem, $ztitem, $ztimage, cache ) {
				$(this).remove();
						
				$new_ztitem
				.show()
				.children('div.zt-tag')
				.show();
				
				$ztitem.hide();
				
				$ztimage.css({
					width	: cache.ztdim.x + 'px',
					height	: cache.ztdim.y + 'px',
					left	: '0px',
					top		: '0px'
				});
				
				cache.animTour	= false;
			},
			zoomout				: function( $tag, $ztitem, ztitemid, $ztimage, zoomfactor, speedfactor, imgdelayfactor, zoomAnimation, $new_ztitem, $new_ztitemimage, cache, settings ) {
				
				var $originalTag	= $new_ztitem.children( 'div.' + ztitemid ),
					o_tag_w			= $originalTag.data('width'),
					o_tag_h			= $originalTag.data('height'),
					o_tag_l			= $originalTag.data('left'),
					o_tag_t			= $originalTag.data('top'),
					o_tag_r			= $originalTag.data('rotate'),
				
					el_l 			= o_tag_l + o_tag_w / 2,
					el_t 			= o_tag_t + o_tag_h / 2;
				
				$.fn.applyStyle 	= ( zoomAnimation ) ? $.fn.animate : $.fn.css;
				
				$new_ztitemimage.css({
					width	: cache.ztdim.x * zoomfactor + 'px',
					height	: cache.ztdim.y * zoomfactor + 'px',
					left	: - ( ( zoomfactor * ( 2 * el_l ) ) - ( 2 * el_l ) ) / 2 + 'px',
					top		: - ( ( zoomfactor * ( 2 * el_t ) ) - ( 2 * el_t ) ) / 2 + 'px'
				});
				
				$ztitem.hide();
				
				var $new_ztitem_tags = $new_ztitem.children('div.zt-tag');
				$new_ztitem_tags.hide();
				
				$new_ztitem.show();
				
				var expandCss	= {
					width	: cache.ztdim.x + 'px',
					height	: cache.ztdim.y + 'px',
					left	: '0px',
					top		: '0px',
					opacity	: 1
				};
				if( settings.rotation && !cache.ieLte8 )
					expandCss.rotate	= '0deg';
				
				var imgAnimationCss	= {
					width	: o_tag_w + 'px',
					height	: o_tag_h + 'px',
					left	: o_tag_l + 'px',
					top		: o_tag_t + 'px',
					opacity	: 0
				};
				if( settings.rotation && !cache.ieLte8 )
					imgAnimationCss.rotate	= o_tag_r + 'deg';
				
				$new_ztitem.append( 
					$('<img src="' + $ztimage.attr('src') + '" class="zt-temp"></img>')
					.css( expandCss )
				)
				
				var $zttemp = $new_ztitem.find('img.zt-temp');
				
				$zttemp.applyStyle( imgAnimationCss, $.extend( true, [], { duration : speedfactor, complete : function() {
					$(this).remove();
				} } ) );
				
				if( !zoomAnimation ) 
					$zttemp.remove();
				
				$new_ztitemimage
				.delay( imgdelayfactor )
				.applyStyle( {
					width	: cache.ztdim.x + 'px',
					height	: cache.ztdim.y + 'px',
					left	: '0px',
					top		: '0px'
				}, $.extend( true, [], { duration : speedfactor, easing : settings.zoomoutEasing, complete : function() {
					viewport.zoomoutCallback( $new_ztitem_tags, cache );
				} } ) );
				
				if( !zoomAnimation )
					viewport.zoomoutCallback( $new_ztitem_tags, cache );
				
			},
			zoomoutCallback		: function( $new_ztitem_tags, cache ) {
				$new_ztitem_tags.show();
				cache.animTour	= false;
			}
		},
		methods 	= {
			init 				: function( options ) {
				
				if( this.length ) {
					
					var settings = {
						rotation		: true,	// if true the tags are rotated
						zoominEasing	: '',  	// zoom out animation easing. ex: easeOutBounce , easeOutBack
						zoomoutEasing	: ''	// zoom out animation easing.
					};
					
					return this.each(function() {
						
						// if options exist, lets merge them with our default settings
						if ( options ) {
							$.extend( settings, options );
						}
						
						var $ztcontainer 		= $(this),
							// the container's items / images
							$ztitems			= $ztcontainer.children('div.zt-item'),
							// large images
							$ztimages			= $ztitems.children('img.zt-current'),
							// the tags
							$zttags				= $ztitems.children('div.zt-tag'),
							// some values we will need later..
							cache				= {
								// container's width & height
								ztdim		: {
									x	: $ztcontainer.width(),
									y	: $ztcontainer.height()
								},
								// check if the browser is <= IE8
								ieLte8		: ($.browser.msie && parseInt($.browser.version) <= 8),
								// true if currently animating
								animTour	: false
							};
						
						// add a loading image until all the images are loaded
						var $loading			= $('<div class="zt-loading"></div>').prependTo( $ztcontainer );
						
						// add the class with value "id" to each one of the items. We will need this later to access the items.
						$ztitems.each( function() {
							var $ztitem	= $(this);
							$ztitem.addClass( $ztitem.data('id') );
						});
						
						// distance from the container's center to one of its corners
						// this will be needed to calculate how much we rotate each tag
						if( settings.rotation && !cache.ieLte8 )
							cache.dist_center		= Math.sqrt( Math.pow( ( cache.ztdim.x / 2 ), 2 ) + Math.pow( ( cache.ztdim.y / 2 ), 2 ) );
						
						$zttags.each(function() {
							var $tag	= $(this);
							// save each tag's widh height left and top
							tag.saveInitialData( $tag );
							// rotate the tags
							if( settings.rotation && !cache.ieLte8 && !$tag.hasClass('zt-tag-back') )
								tag.rotate( $tag, cache );
						}).hide(); // hide the tags
						
						// show the first item
						$ztitems.not(':first').hide();
						
						// preload all large images
						var loaded		= 0,
							totalImages	= $ztimages.length;
							
						$ztimages.each( function() {
							$('<img>').load( function() {
								++loaded
								if( loaded === totalImages ) {
									// show all large images (just the first will be visible)
									$ztimages.show();
									
									// hide the loading image
									$loading.hide();
									
									// show the tags
									$zttags.show();
									
									// clicking one tag, zooms in / out
									$zttags.bind('click.zoomtour', function( e ) {
										if( cache.animTour ) return false;
										cache.animTour	= true;
										
										var $tag			= $(this);
										viewport.zoom( $ztcontainer, $tag, cache, settings );
									});
									
									// hide / show tags on mouse hover
									$ztcontainer.bind('mouseenter.zoomtour', function( e ) {
										if( !cache.animTour )
											$zttags.show();
									}).bind('mouseleave.zoomtour', function( e ) {
										if( !cache.animTour )
											$zttags.hide();
									});
									
								}
							}).attr( 'src', $(this).attr('src') );
						});
					});
				}
			}
		};
	
	$.fn.zoomtour = function(method) {
		if ( methods[method] ) {
			return methods[method].apply( this, Array.prototype.slice.call( arguments, 1 ));
		} else if ( typeof method === 'object' || ! method ) {
			return methods.init.apply( this, arguments );
		} else {
			$.error( 'Method ' +  method + ' does not exist on jQuery.zoomtour' );
		}
	};
	
})(jQuery);

We hope you like this little script and find it useful!

Additional Details

DemoDownloads