Creating An Image Zoom And Clip Effect With jQuery

Posted January 21, 2010 at 1:52 PM by Ben Nadel

Tags: Javascript / DHTML

A long time ago, I saw a really cool image effect in which I could select a region of an image and the image would automatically scale up to show the selected region. In that effect, the grainy, scaled-up image was then replaced with a hi-resolution version of the selected region. Years later, as I am getting more comfortable with jQuery, I wanted to see if I could recreate the effect. As an initial blog post, I'm just going to deal with the client-side image zoom; I'll save the hi-resolution image creation and client-side swap for a follow-up post.

 
 
 
 
 
 
 
 
 
 

To accomplish this image zooming effect with jQuery, I figured I would need some sort of "view" container for the image. If the image were positioned relative to this parent container, the image could then be moved around and resized within it. And, if the view remained a static size and clipped any overflow content, I could show only the selected portion of the image.

 
 
 
 
 
 
Creating A Zoom And Clip Effect With jQuery Requires A View Container And A Relative-Positioned Image. 
 
 
 

As I got started, the solution unfolded a lot quicker than I thought it would. The hardest part of it was figuring out how to adjust the image position for the zoom. In the following code, you can really see that I thought in a top-down manor, simply writing code as I went. In fact, if you start at the top and just read down, you probably won't have to jump around at all.

  • <!DOCTYPE HTML>
  • <html>
  • <head>
  • <title>Image Zoom With jQuery</title>
  • <style type="text/css">
  •  
  • #view {
  • border: 1px solid #333333 ;
  • overflow: hidden ;
  • position: relative ;
  • width: 400px ;
  • }
  •  
  • #image {
  • display: block ;
  • left: 0px ;
  • top: 0px ;
  • }
  •  
  • #zoom {
  • background-image: url( "./white_fade.png" ) ;
  • border: 1px solid #000000 ;
  • cursor: pointer ;
  • display: none ;
  • height: 200px ;
  • position: absolute ;
  • width: 200px ;
  • z-index: 100 ;
  • }
  •  
  • </style>
  • <script type="text/javascript" src="../jquery-1.4.js"></script>
  • <script type="text/javascript">
  •  
  • // When the WINDOW is ready, initialize. We are going with
  • // the window load rather than the document so that we
  • // know our image will be ready as well (complete with
  • // gettable dimentions).
  • $( window ).load(function(){
  •  
  • // First, let's get refernces to the elements we will
  • // be using.
  • var view = $( "#view" );
  • var image = $( "#image" );
  •  
  • // Create the ZOOM element - this will be added with
  • // Javascript since it's more of an "effect".
  • var zoom = $( "<a id='zoom'><span><br /></span></a>" );
  •  
  • // Before we start messing with the scripts, let's
  • // update the display to allow for the absolute
  • // positioning of the image and zoomer.
  •  
  • // Set an explicit height / width on the view based
  • // on the initial size of the image.
  • view.width( image.width() );
  • view.height( image.height() );
  •  
  • // Now that the view has an explicit width and height,
  • // we can change the displays for positioning.
  • image.css( "position", "absolute" );
  •  
  • // Set an exlicit height on the image (to make sure
  • // that some of the later calcualtions don't get
  • // messed up - I saw some irradic caculated-height
  • // behavior).
  • image.height( image.height() );
  •  
  • // Before we add the zoom square, we need it to match
  • // the aspect ratio of the image.
  • zoom.width( Math.floor( image.width() / 2 ) );
  • zoom.height( Math.floor( image.height() / 2 ) );
  •  
  • // Now, add the zoom square to the view.
  • view.append( zoom );
  •  
  •  
  • // ---------------------------------------------- //
  • // ---------------------------------------------- //
  •  
  •  
  • // Now that we have our UI set up physically, we need
  • // to bind the event handlers.
  •  
  • // We want to show and hide the zoom only when the
  • // user hovers over the view.
  • view.hover(
  • function( event ){
  • // Show the soom.
  • zoom.show();
  • },
  • function( event ){
  • // Hide the zoom.
  • zoom.hide();
  • }
  • );
  •  
  •  
  • // As the user mouses over the view, we can get the
  • // mouse coordinates in terms of the page; we need
  • // to be able to translate those into VIEW-based
  • // X and Y cooridates. As such, let's get the offset
  • // of the view as our base 0x0 coordinate.
  • //
  • // NOTE: We are doing this here so that we do it once,
  • // rather than every time the mouse moves.
  • viewOffset = view.offset();
  •  
  • // Get the jQuery-ed version of the window as we will
  • // need to access it's scroll offsets every time the
  • // mouse moves over the div.
  • //
  • // NOTE: This will change the change the refernce to
  • // "window" for all of the code in this closure.
  • var window = $( window );
  •  
  •  
  • // As the user moves across the view, we want to move
  • // the zoom square with them.
  • view.mousemove(
  • function( event ){
  • // Get the window scroll top; the mouse
  • // position is relative to the window, NOT
  • // the document.
  • var windowScrollTop = window.scrollTop();
  • var windowScrollLeft = window.scrollLeft();
  •  
  • // Translate the mouse X / Y into view-local
  • // coordinates that can be used to position
  • // the zoom box.
  • setZoomPosition(
  • Math.floor(
  • event.clientX - viewOffset.left + windowScrollLeft
  • ),
  • Math.floor(
  • event.clientY - viewOffset.top + windowScrollTop
  • )
  • );
  • }
  • );
  •  
  •  
  • // I position the zoom box within the view based on
  • // the given view-local mouse coordinates.
  • var setZoomPosition = function( mouseLeft, mouseTop ){
  • // Ideally, we want to keep the zoom box centered
  • // on the mouse. As such, we want the given mouse
  • // left and mouse top coordiantes to be in the
  • // middle of the zoom box.
  • var zoomLeft = (mouseLeft - (zoom.width() / 2));
  • var zoomTop = (mouseTop - (zoom.height() / 2))
  •  
  • // As we move the zoom box around, however, we
  • // never want it to go out of bounds of the view.
  •  
  • // Protect the top-left bounds.
  • zoomLeft = Math.max( zoomLeft, 0 );
  • zoomTop = Math.max( zoomTop, 0 );
  •  
  • // Protect the bottom-right bounds. Because the
  • // bottom and right need to take the dimensions
  • // of the zoom box into account, be sure to use
  • // the outer width to include the border.
  • zoomLeft = Math.min(
  • zoomLeft,
  • (view.width() - zoom.outerWidth())
  • );
  • zoomTop = Math.min(
  • zoomTop,
  • (view.height() - zoom.outerHeight())
  • );
  •  
  • // Position the zoom box in the bounds of the
  • // image view box.
  • zoom.css({
  • left: (zoomLeft + "px"),
  • top: (zoomTop + "px")
  • });
  • };
  •  
  •  
  • // Now that we have the mouse movements being tracked
  • // properly, we need to track the click on the zoom to
  • // zoom in the image on demand. To do that, however,
  • // we need to start storing some information with the
  • // image so we can manipulate it as needed.
  • image.data({
  • zoomFactor: (view.width() / zoom.width()),
  • zoom: 1,
  • top: 0,
  • left: 0,
  • width: image.width(),
  • height: image.height(),
  • originalWidth: image.width(),
  • originalHeight: image.height()
  • });
  •  
  •  
  • // Now, let's attach the click event handler to the
  • // zoom box.
  • zoom.click(
  • function( event ){
  • // First, prevent the default since this is
  • // not a navigational link.
  • event.preventDefault();
  •  
  • // Let's pass the position of the zoom box
  • // off to the function that is responsible
  • // for zooming the image.
  • zoomImage(
  • zoom.position().left,
  • zoom.position().top
  • );
  • }
  • );
  •  
  •  
  • // I take the zoom box coordinates and translate them
  • // into an actual image zoom based on the existing
  • // zoom and offset of the image.
  • //
  • // NOTE: We don't care about the dimensions of the
  • // zoom box itself as those should have already been
  • // properly translated into the zoom *factor*.
  • var zoomImage = function( zoomLeft, zoomTop ){
  • // Get a reference to the image data object so we
  • // don't need to keep retreiving it.
  • var imageData = image.data();
  •  
  • // Check to see if we have reached the max zoom
  • // or if the image is currently animating.
  • // If so, just return out.
  • if (
  • (imageData.zoom == 5) ||
  • (image.is( ":animated" ))
  • ){
  •  
  • // Zooming in beyond this is pointless (and
  • // can cause the browser to mis-render the
  • // image).
  • return;
  •  
  • }
  •  
  • // Scale the image up based on the zoom factor.
  • imageData.width =
  • (image.width() * imageData.zoomFactor);
  •  
  • imageData.height =
  • (image.height() * imageData.zoomFactor);
  •  
  • // Change the offset set data to re-position the
  • // 0,0 coordinate back up in the top left.
  • imageData.left =
  • ((imageData.left - zoomLeft) * imageData.zoomFactor);
  •  
  • imageData.top =
  • ((imageData.top - zoomTop) * imageData.zoomFactor);
  •  
  • // Increase the zoom.
  • imageData.zoom++;
  •  
  • // Animate the zoom.
  • image.animate(
  • {
  • width: imageData.width,
  • height: imageData.height,
  • left: imageData.left,
  • top: imageData.top
  • },
  • 500
  • );
  • };
  •  
  •  
  • // I reset the image zoom.
  • var resetZoom = function(){
  • // Get a reference to the image data object so we
  • // don't need to keep retreiving it.
  • var imageData = image.data();
  •  
  • // Reset the image data.
  • imageData.zoom = 1;
  • imageData.top = 0;
  • imageData.left = 0;
  • imageData.width = imageData.originalWidth;
  • imageData.height = imageData.originalHeight;
  •  
  • // Animate the zoom.
  • image.animate(
  • {
  • width: imageData.width,
  • height: imageData.height,
  • left: imageData.left,
  • top: imageData.top
  • },
  • 300
  • );
  • };
  •  
  •  
  • // As a final step, to make sure that the image can
  • // be zoomed out, bind the mousedown to the document.
  • $( document ).mousedown(
  • function( event ){
  • // Check to see if the view is in the event
  • // bubble chain for the mouse down. If it is,
  • // then this click was in the view or its
  • // child elements.
  • var closestView = $( event.target ).closest( "#view" );
  •  
  • // Check to see if this mouse down was in the
  • // image view.
  • if (!closestView.size()){
  •  
  • // The view was not found in the chain.
  • // This was clicked outside of the view.
  • // Reset the image zoom.
  • resetZoom();
  •  
  • }
  • }
  • );
  •  
  • });
  •  
  • </script>
  • </head>
  • <body>
  •  
  • <h1>
  • Image Zoom And Clip With jQuery
  • </h1>
  •  
  • <div id="view">
  • <img id="image" src="./girl.jpg" width="400" />
  • </div>
  •  
  • </body>
  • </html>

Right now, I am manually controlling the position of the "zoomer" (the white image selection box). I am sure that I could have included the jQuery UI library and made use of the "draggable" functionality; but I wanted to keep this as straightforward as possible. The code would have been shorter with the draggable library; but, to be honest, writing out the code for it really got me to think about the dynamics of the UI interaction and ultimately, helped me think through the solution more effectively.

Every time that I try do something complicated with jQuery, I am always blow away by how much less complex this Javascript library makes things. In this exploration, the data storage, positional, and dimensional methods really just came through in a huge way! jQuery is so badass that it made the Math the most complex aspect of the problem. Isn't that wild? In a follow-up post, I hope to have the client interacting with the server to pull down a pre-clipped, hi-resolution image of the selected region.




Reader Comments

Jan 22, 2010 at 3:39 AM // reply »
14 Comments

Nice job dude :)

You could also implement a very nice crop effect on top of this (I've personally used jCrop which I found was awesome when implemented alongside CF's image tags) to create an "uber" image manipulation package.

http://deepliquid.com/content/Jcrop.html

In fact I might just do that myself :P


Jan 22, 2010 at 11:21 AM // reply »
10,638 Comments

Here's a quick follow up on that post - this time, adding a server-side component to create and serve up hi-resolution images on demand:

http://www.bennadel.com/blog/1824-Creating-An-Image-Zoom-And-Clip-Effect-With-jQuery-And-ColdFusion.htm


Jan 25, 2010 at 3:30 AM // reply »
1 Comments

Is this possible with regular CSS or just with Java?

Thanks in advance.


Jan 25, 2010 at 9:21 PM // reply »
10,638 Comments

@Sammy,

Not that I know of - there's so much interactivity. Is there a particular reason you would want to have CSS do this?


Apr 30, 2010 at 1:07 PM // reply »
1 Comments

Hello Ben,

Nice job, i've put this script on my website but there's a problem, when the div#view is put at the bottom of any pages. After scrolling on my browser to see the div#view that contain the image. The div#zoom is not working correctly anymore and is blocked at the top of the div#view.

Hope it helps to improve your script. Sorry for my bad english ;-)


May 18, 2010 at 12:48 AM // reply »
1 Comments

Hi ben thanks for all this nice articals and it is realy helpful. i have one problem with using (Creating An Image Zoom And Clip Effect With jQuery) topic when i run the project zoom selector is stuck on the tope of the image it is moving horizentaly on the heading of the image not on other part of image and while using is along with the image it is working fine and Link has also post his comment on this topic he has the same problem so is there any solution for this porblem.

Thanks


May 18, 2010 at 8:46 PM // reply »
10,638 Comments

@Link, @Amin,

Hmmm, not exactly sure. Looking at the code, I appear to be making use of clientX, clientY. After posting this, I found out that jQuery actually standardized on pageX and pageY:

http://www.bennadel.com/blog/1869-jQuery-Mouse-Events-PageX-Y-vs-ClientX-Y.htm

It's possible that this difference between these two sets of variables is what is causing the problem on pages that have a scroll. In fact, that kind of makes sense - clientX/Y is relative to the view frame and pageX/Y is relative to the top/left of the full document.

It's all a learning experience :)


FS
Aug 26, 2010 at 8:39 PM // reply »
1 Comments

This king of effect has been implemented by ajax-zoom with PHP and jQuery. It uses image tiles and can be viewed here: http://www.ajax-zoom.com


Sep 4, 2010 at 11:16 AM // reply »
10,638 Comments

@FS,

Dude, that one looks really awesome!


Moi
Oct 7, 2010 at 1:54 PM // reply »
2 Comments

Hi dude, tnks for this awesome effect. I'm trying it on a Blogger template and it works, but I have a problem, the zoom scuare goes above the cursor all the time.
Any idea?


Moi
Oct 7, 2010 at 1:58 PM // reply »
2 Comments

Forget it, it's working fine now =)
Tnks so much!!!


Oct 10, 2010 at 4:11 PM // reply »
10,638 Comments

@Moi,

Glad you got it working :)


Apr 13, 2011 at 9:59 AM // reply »
1 Comments

dude! that's my MOM!


Apr 22, 2011 at 8:24 PM // reply »
2 Comments

Hi ben thanks for all this nice articals and it is realy helpful. i have one problem with using (Creating An Image Zoom And Clip Effect With jQuery) topic when i run the project zoom selector is stuck on the tope of the image it is moving horizentaly on the heading of the image not on other part of image and while using is along with the image it is working fine and Link has also post his comment on this topic he has the same problem so is there any solution for this porblem.


Aug 22, 2011 at 2:03 PM // reply »
1 Comments

Hey Ben, Was playing around with this code snippet (your Jquery Zoom Code). I can't seem to get it to function in IE8. Have you had problems in past or is it just me. I have also change clientx,y as per previous commented post. Works fine in FF and Chrome.


Nov 18, 2011 at 12:50 PM // reply »
1 Comments

good job. where you can download the image they did in the video tutorial ?.

grettings.


Dec 23, 2011 at 9:16 AM // reply »
1 Comments

Thanks Ben, this is just what I needed for an upcoming project!



Post A Comment

Comment Etiquette: Please do not post spam. Please keep the comments on-topic. Please do not post unrelated questions or large chunks of code. And, above all, please be nice to each other - we're trying to have a good conversation here.

Please review the following issues:

Author Name:


Author Email:

Author Website:

Comment:

Supported HTML tags for formatting: <strong>bold</strong>   <em>italic</em>   <code>code</code>







  • Help Wanted - Find Your Next ColdFusion Job
InVision App - Prototyping Made Beautiful With Prototyping Tools Ben Nadel's Company - Epicenter Consulting Recent Blog Comments
Feb 3, 2012 at 10:49 PM
How I Got Node.js Running On A Linux Micro Instance Using Amazon EC2
Wow this was really helpful! Only thing I would add is you need to update your .bash_profile after you edit the secure_path. This is what I did: $ . ~/.bash_profile Otherwise, NPM won't be found. ... read »
Feb 3, 2012 at 10:14 PM
Pushing Base64-Encoded Images Over HTML5 WebSockets With Pusher And ColdFusion
@Ben, Just wanted to let you know that pusher are soon to start limiting sizes on messages. This was the detail that came through in the Feb dispatch: "However, we will soon be limiting the s ... read »
Feb 3, 2012 at 5:05 PM
Regular Expressions Make CSV Parsing In ColdFusion So Much Easier (And Faster)
I tried using your RegEx in my C# program, but it was matching an extra empty-string at the end and so I would end up with an extra field that doesn't exist, so I changed it to this: (^|,)("(?: ... read »
Feb 3, 2012 at 3:47 PM
ColdFusion Supports HTTP Verbs PUT And DELETE (As Well As GET And POST)
Josh Cyr posted this on Twitter just a little bit ago. Thought it was appropriate. http://stackoverflow.com/questions/1619152/how-to-create-rest-urls-without-verbs/1619677#1619677 ... read »
Feb 3, 2012 at 2:28 PM
Changing The Execution Context Of Your Self-Executing Function Blocks In JavaScript
@Michael, You definitely make a good point (and extra points for quoting movies - I love movies). When you use a return() statement to define the object's public API, it does provide a consistent a ... read »
Feb 3, 2012 at 2:04 PM
Changing The Execution Context Of Your Self-Executing Function Blocks In JavaScript
To quote Jurassic Park: "Just because you can doesn't mean you should". I completely, utterly disagree with the thought that this is more readable. Consider the current module pattern: if ... read »
Feb 3, 2012 at 1:10 PM
REST API Design Rulebook By Mark Masse
@Jordan, Yeah, WRML was created by Mark Masse (author of the book). I also found it to be a bit convoluted. I suppose it is intended to allow the Client to be able to programmaticaly respond to cha ... read »
Feb 3, 2012 at 1:08 PM
ColdFusion Supports HTTP Verbs PUT And DELETE (As Well As GET And POST)
@Jason, To be honest, I don't have good answers for that kinds of stuff. And, to the point, that is specifically why I *really* liked the REST API Design Rulebook by Mark Masse - he just cuts throu ... read »