Ben Nadel
On User Experience (UX) Design, JavaScript, ColdFusion, Node.js, Life, and Love.
I am the chief technical officer at InVision App, Inc - a prototyping and collaboration platform for designers, built by designers. I also rock out in JavaScript and ColdFusion 24x7.
Meanwhile on Twitter
Loading latest tweet...
Ben Nadel at cf.Objective() 2010 (Minneapolis, MN) with: Kris Jones

Creating An Image Zoom And Clip Effect With jQuery

By Ben Nadel on

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

@Sammy,

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

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 ;-)

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

@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 :)

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?

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.

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.

Hey Ben,

Thanks for sharing your code. It works fine, but I am trying to apply zoom effect on several images in one page.
What I did is just wrapped your code into $('div[id*=image]').each(function(index){...});

Could you advise me what changes I have to do, in order to make it working?

I was searching for a way to find the center of an image. I came across this post. As a female programmer I find this post absolutely rude. You are truly unprofessional to post such crap.

lol this subject has only come up half a million times or so. This post was a relatively long time ago. @Ben has really cleaned up a lot of his code and examples because of comments like the previous comment. I, personally, think it is totally unnecessary and love this example...I think it is AWESOME!!! But it's not my blog, it's @Ben's, so if he wants to be PC to attempt not to offend readers like you, then GREAT. That's his prerogative. But as a female programmer, it doesn't offend me at all. I find it hot and I have a sense of humor and find the text of the content extremely useful and informative. Let em repeat this...this is @Ben's blog. It's up to him as to what goes on here. Feel free to throw out the baby with the bathwater, but you'll be the one who misses out on some of the useful information @Ben shares on his blog, and you will most likely not be as good of a programmer as a result.

The important part of the blog is the awesome information that comes with it. I'm thrilled there's something like this out there. It has helped me so much and so many times in my journey as a programmer. It's hot, but that's besides the point.

I am actually offended there are women out there who ruin it for the rest of us. It gives us a bad name. Not all of us are offended that easily or uptight. Some are very easy-going, and can spot a good blog subject and accompanying information when we see it, regardless of what the example is about or any pictures that comes with it. I'm so glad that I have been able to get past it and see the helpful content and allow it to make me a better programmer. Thanks, @Ben for all that you do.

@Anna,

yes, it is his blog and he can write whatever he would like and he also allows comments for feedback. so it is not like i am way off.

no i don't ruin it for everyone and i don't give female programmers a bad name. i am a girl who is willing to stand up for myself even when other people are going to say nasty things to me because the don't agree with my view. that's life...

I guess there are some girls who are more uptight about stuff like that. I'm glad I am in the more easy-going column. If you were a dude, the other dudes on here would have jumped all over you, up and down. But because you are a lady, they are showing you respect and NOT saying anything. But I'm not a dude, so I don't have to be restricted by such rules.

@Anna,

I am glad you are easy going and I wish you all the best in the programming world. I am just a single person who chose to post my 'gut reaction' to a blogpost. I wish no one ill will. I am not demanding it be deleted or anything. In all my research and reading for problem/resolutions I've personally never come across a programmer who posted something slightly offensive to me so I decided to post a comment...

If a few guys in life don't like me because of my opinion then oh well. That's how you learn to get a thick skin.

I really like writing code and glad it is my chosen career. I just find the post a little un-professional. Ben is not claiming to be Mr. Professional so maybe my standards are a little high. It is not like we are in the workplace or something.

Hey this is just a difference of opinion. Isn't it what 99% of the web is about?

@Laurie,

You were right that you had a right to comment about it and say whatever you felt. You were also right to stick up for yourself. That's something that is a responsibility for everyone. I was not sticking up for myself per se. Honestly, if someone craps all over @Ben, I wouldn't really be all that effected. It's not like he and I are bosom buddies or best of friends or anything. If he decided to stop posting his superior code because of it, then that would affect me slightly. Not enough to get into a physical brawl over, but I would have some trouble at times finding solutions to problems I am having. As a programmer, I can't tell you how many times I have had a problem and went to google and typed in key words describing my problem, and Voila!!! @Ben's blog jumps up, to the top of the list, too. And the content was RELEVANT to what I wanted to do, and it helped me...so much. So I DO appreciate what @Ben has done for our "little" programming community, but at the same time, it wouldn't have too much of a person effect on me if someone jumped all over him.

So, I wasn't doing it for any personal reason or for myself....I was simply sticking up for @Ben, because I felt it was the right thing to do. I feel it is a higher order to unselfishly stick up for someone other than yourself. If complaints had been lodged at me, personally, you better believe I would stick up for myself! But I was sticking up for @Ben, because it was the right thing to do, and I would've done it for anyone if I felt they weren't in the wrong.

I do have trouble, though I try, getting into the shoes of the way certain women think. I am a programmer...a field that is still mainly male-dominated, and most of my jobs have been with co-workers that were all or mostly...at least 90%-95% male. You get used to certain things. It doesn't "help" in terms of me being able to "think like a woman" that I am basically a man who has had boobs, a feminine voice, and long straight brown hair slapped on me. Yes, I look very feminine....I will agree with that. But that look very sharply contrasts with my personality and mindset. I often think about something the way a man would. I think that is at least part of what drew me into programming...but I love the actual programming, too.

I also did not mean to imply that the men wouldn't like you because of the way you think or your comments. That's precisely one of the reasons WHY they would not respond to you in the manner they do to dudes who post similar comments on other blog posts...because most of us have our picture up there, and a lot of the guys in programming...hopefully, the single ones...have this mindset...they think...Oh....if I respond to her the way I would respond to some dude that said the same thing, and then I meet her at a conference, she might remember my face and I would ruin my chances with her! I better not say to her what I would a dude. And, of course, out of respect to women is secondary motivation for the response being slightly different. But the first/primary motivation is that they don't want to ruin their chances...."what if"...with you. And this does not apply to all men, but most. That's just the way they think, their primary goal in life.

Like I said, in thought, I am like a man. So I am like that, but different. I am like that in that, if I get that as an end goal, then GREAT!!!!!!!! However, I also am not going to stoop to meaningless relationships and behaving in that manner with people I don't know or barely know. That's the WOMAN part coming out in me...little bit that she is. Lol

I'd like to take this code as a starting point and roll my own based on this. Is it ok to use this on a large website? What license are you releasing this under? I'm replacing a vender supplied script that is some 20 files and massively complex with this as a require module and some other modules to wrap it with. Also not only zooming in on the same image but zooming in on a side by side overlay.