jQuery image panning
Image panning with animation easing that works on mouse movement with css and jQuery.
view demo Example without animation
Multiple instances demo
Multiple instances synchronization example
Multiple instances demo
Multiple instances synchronization example
download Get all files related to post
Image panning with animation easing
The markup
Simple markup: an image inside a div
<div class="content"> <img src="img.jpg" /> </div>
The CSS
.content{ width: 800px; height: 600px; overflow: hidden; } .content img{ opacity: 0; transition: opacity .6s linear .8s; } .content img.loaded{ opacity: 1; } .img-pan-container, .img-pan-container img{ -webkit-box-sizing: border-box; -moz-box-sizing: border-box; box-sizing: border-box; } .img-pan-container{ position: relative; overflow: hidden; cursor: crosshair; height: 100%; width: 100%; } .img-pan-container img{ -webkit-transform: translateZ(0); -ms-transform: translateZ(0); transform: translateZ(0); position: absolute; top: 0; left: 0; }
The javascript
Can be placed inside the head tag or at the bottom of the document right before the closing body tag
More code, better animation/performance (60 fps)
<!-- jQuery --> <script src="http://ajax.googleapis.com/ajax/libs/jquery/1.11.2/jquery.min.js"></script> <!-- JS --> <script> (function($){ $(document).ready(function(){ //call imagePanning fn when DOM is ready $(".content img").imagePanning(); }); //imagePanning fn $.fn.imagePanning=function(){ var init="center", speed=800, //animation/tween speed //custom js tween _tweenTo=function(el,prop,to,duration,easing,overwrite){ if(!el._mTween){el._mTween={top:{},left:{}};} var startTime=_getTime(),_delay,progress=0,from=el.offsetTop,elStyle=el.style,_request,tobj=el._mTween[prop]; if(prop==="left"){from=el.offsetLeft;} var diff=to-from; if(overwrite!=="none"){_cancelTween();} _startTween(); function _step(){ progress=_getTime()-startTime; _tween(); if(progress>=tobj.time){ tobj.time=(progress>tobj.time) ? progress+_delay-(progress-tobj.time) : progress+_delay-1; if(tobj.time<progress+1){tobj.time=progress+1;} } if(tobj.time<duration){tobj.id=_request(_step);} } function _tween(){ if(duration>0){ tobj.currVal=_ease(tobj.time,from,diff,duration,easing); elStyle[prop]=Math.round(tobj.currVal)+"px"; }else{ elStyle[prop]=to+"px"; } } function _startTween(){ _delay=1000/60; tobj.time=progress+_delay; _request=(!window.requestAnimationFrame) ? function(f){_tween(); return setTimeout(f,0.01);} : window.requestAnimationFrame; tobj.id=_request(_step); } function _cancelTween(){ if(tobj.id==null){return;} if(!window.requestAnimationFrame){clearTimeout(tobj.id); }else{window.cancelAnimationFrame(tobj.id);} tobj.id=null; } function _ease(t,b,c,d,type){ var ts=(t/=d)*t,tc=ts*t; return b+c*(0.499999999999997*tc*ts + -2.5*ts*ts + 5.5*tc + -6.5*ts + 4*t); } function _getTime(){ if(window.performance && window.performance.now){ return window.performance.now(); }else{ if(window.performance && window.performance.webkitNow){ return window.performance.webkitNow(); }else{ if(Date.now){return Date.now();}else{return new Date().getTime();} } } } }; return this.each(function(){ var $this=$(this),timer,dest; if($this.data("imagePanning")) return; $this.data("imagePanning",1) //create markup .wrap("<div class='img-pan-container' />") .after("<div class='resize' style='position:absolute; width:auto; height:auto; top:0; right:0; bottom:0; left:0; margin:0; padding:0; overflow:hidden; visibility:hidden; z-index:-1'><iframe style='width:100%; height:0; border:0; visibility:visible; margin:0' /><iframe style='width:0; height:100%; border:0; visibility:visible; margin:0' /></div>") //image loaded fn .one("load",function(){ setTimeout(function(){ $this.addClass("loaded").trigger("mousemove",1); },1); }).each(function(){ //run load fn even if cached if(this.complete) $(this).load(); }) //panning fn .parent().on("mousemove touchmove MSPointerMove pointermove",function(e,p){ var cont=$(this); e.preventDefault(); var contH=cont.height(),contW=cont.width(), isTouch=e.type.indexOf("touch")!==-1,isPointer=e.type.indexOf("pointer")!==-1, evt=isPointer ? e.originalEvent : isTouch ? e.originalEvent.touches[0] || e.originalEvent.changedTouches[0] : e, coords=[ !p ? evt.pageY-cont.offset().top : init==="center" ? contH/2 : 0, !p ? evt.pageX-cont.offset().left : init==="center" ? contW/2 : 0 ]; dest=[Math.round(($this.outerHeight(true)-contH)*(coords[0]/contH)),Math.round(($this.outerWidth(true)-contW)*(coords[1]/contW))]; }) //resize fn .find(".resize iframe").each(function(){ $(this.contentWindow || this).on("resize",function(){ $this.trigger("mousemove",1); }); }); //panning animation 60FPS if(timer) clearInterval(timer); timer=setInterval(function(){ _tweenTo($this[0],"top",-dest[0],speed); _tweenTo($this[0],"left",-dest[1],speed); },16.6); }); } })(jQuery); </script>
The js code above is more than few lines, simply because it includes a vanilla custom javascript tween for much better performance and smoother animations.
Less code, average animation/performance (30 fps)
$.fn.imagePanning=function(){ var init="center", speed=800; //animation/tween speed //add custom easing for jquery animation $.extend($.easing,{ pan:function(x,t,b,c,d){ return -c * ((t=t/d-1)*t*t*t - 1) + b; } }); return this.each(function(){ var $this=$(this),timer,dest; if($this.data("imagePanning")) return; $this.data("imagePanning",1) //create markup .wrap("<div class='img-pan-container' />") .after("<div class='resize' style='position:absolute; width:auto; height:auto; top:0; right:0; bottom:0; left:0; margin:0; padding:0; overflow:hidden; visibility:hidden; z-index:-1'><iframe style='width:100%; height:0; border:0; visibility:visible; margin:0' /><iframe style='width:0; height:100%; border:0; visibility:visible; margin:0' /></div>") //image loaded fn .one("load",function(){ setTimeout(function(){ $this.addClass("loaded").trigger("mousemove",1); },1); }).each(function(){ //run load fn even if cached if(this.complete) $(this).load(); }) //panning fn .parent().on("mousemove touchmove MSPointerMove pointermove",function(e,p){ var cont=$(this); e.preventDefault(); var contH=cont.height(),contW=cont.width(), isTouch=e.type.indexOf("touch")!==-1,isPointer=e.type.indexOf("pointer")!==-1, evt=isPointer ? e.originalEvent : isTouch ? e.originalEvent.touches[0] || e.originalEvent.changedTouches[0] : e, coords=[ !p ? evt.pageY-cont.offset().top : init==="center" ? contH/2 : 0, !p ? evt.pageX-cont.offset().left : init==="center" ? contW/2 : 0 ]; dest=[Math.round(($this.outerHeight(true)-contH)*(coords[0]/contH)),Math.round(($this.outerWidth(true)-contW)*(coords[1]/contW))]; }) //resize fn .find(".resize iframe").each(function(){ $(this.contentWindow || this).on("resize",function(){ $this.trigger("mousemove",1); }); }); //panning animation 30 FPS if(!timer){ timer=setInterval(function(){ $this.stop().animate({"top":-dest[0],"left":-dest[1]},speed,"pan"); },33.3); } }); }
Image panning without animation
imagePanning function
$.fn.imagePanning=function(){ var init="center"; return this.each(function(){ var $this=$(this); if($this.data("imagePanning")) return; $this.data("imagePanning",1) //create markup .wrap("<div class='img-pan-container' />") .after("<div class='resize' style='position:absolute; width:auto; height:auto; top:0; right:0; bottom:0; left:0; margin:0; padding:0; overflow:hidden; visibility:hidden; z-index:-1'><iframe style='width:100%; height:0; border:0; visibility:visible; margin:0' /><iframe style='width:0; height:100%; border:0; visibility:visible; margin:0' /></div>") //image loaded fn .one("load",function(){ setTimeout(function(){ $this.addClass("loaded").trigger("mousemove",1); },1); }).each(function(){ //run load fn even if cached if(this.complete) $(this).load(); }) //panning fn .parent().on("mousemove touchmove MSPointerMove pointermove",function(e,p){ var cont=$(this); e.preventDefault(); var contH=cont.height(),contW=cont.width(), isTouch=e.type.indexOf("touch")!==-1,isPointer=e.type.indexOf("pointer")!==-1, evt=isPointer ? e.originalEvent : isTouch ? e.originalEvent.touches[0] || e.originalEvent.changedTouches[0] : e, coords=[ !p ? evt.pageY-cont.offset().top : init==="center" ? contH/2 : 0, !p ? evt.pageX-cont.offset().left : init==="center" ? contW/2 : 0 ], dest=[Math.round(($this.outerHeight(true)-contH)*(coords[0]/contH)),Math.round(($this.outerWidth(true)-contW)*(coords[1]/contW))]; $this.css({"top":-dest[0],"left":-dest[1]}); }) //resize fn .find(".resize iframe").each(function(){ $(this.contentWindow || this).on("resize",function(){ $this.trigger("mousemove",1); }); }); }); }
As with everything, you can change it, optimize it and pretty much use it anyway and anywhere you like 🙂
Hi there,
this looks great but do you have any idea how i can get it work for indexhibit by any chance?
cheers
Dary
For those looking for an image panning script that supports multiple images, here’s some code. It works a bit different than Malihu’s, in that it only requires you to have images with a ‘pan’ class. The javascript will wrap divs around them and event listeners for mouse movement.
Style the divs like above, only note that I’ve used different div class names and these are class names instead of id’s, because you’re using multiple images. Thus: #outer_container = .wrap_pan-image, #imagePan = .pan-image and .container = .img-container
So, html:
And javascript:
$(window).load(function () { $('img.pan').wrap('').wrap('').wrap(''); $.each($('.wrap_pan-image'), function () { var img = $(this).find('img'), imgW = $(img).width(), imgH = $(img).height(); $(img).css('margin-top', ($(this).find('.pan-image').height() - imgH) / 2 + 'px'); $(this).find('.pan-image .img-container').css('width', imgW).css('height', imgH); $(img).css('margin-left', ($(this).find('.pan-image').width() - imgW) / 2).css('margin-top', ($(this).find('.pan-image').height() - imgH) / 2); $(this).find('.pan-image').bind('mousemove', function(event){ MouseMove(event, $(this).parent(), imgW, imgH); }); }); }); function MouseMove(e, el, imgW, imgH){ var containerWidth = $(el).find('.pan-image').width(); var containerHeight = $(el).find('.pan-image').height(); var mouseCoordsX = (e.pageX - $(el).find('.pan-image').offset().left); var mouseCoordsY = (e.pageY - $(el).find('.pan-image').offset().top); var mousePercentX = mouseCoordsX / containerWidth; var mousePercentY = mouseCoordsY / containerHeight; var destX = -(((imgW - (containerWidth)) - containerWidth) * (mousePercentX)); var destY = -(((imgH - (containerHeight)) - containerHeight) * (mousePercentY)); var posA = mouseCoordsX - destX; var posB = destX - mouseCoordsX; var posC = mouseCoordsY - destY; var posD = destY - mouseCoordsY; var marginL = $(el).find('img').css('marginLeft').replace('px', ''); var marginT = $(el).find('img').css('marginTop').replace('px', ''); var animSpeed = 500; var easeType = 'easeOutCirc'; if (mouseCoordsX > destX || mouseCoordsY > destY) { $(el).find('.pan-image .img-container').stop().animate({left: -posA - marginL, top: -posC - marginT}, animSpeed, easeType); } else if (mouseCoordsX < destX || mouseCoordsY < destY){ $(el).find('.pan-image .img-container').stop().animate({left: posB - marginL, top: posD - marginT}, animSpeed, easeType); } else { $(el).find('.pan-image .img-container').stop(); } }
Seems the html is cut out of the code blocks. It should read (replace percentage symbols with opening and closing brackets):
So HTML:
So HTML: %img src="bla.jpg" class="pan" /%
and
$('img.pan').wrap('').wrap('').wrap('');
should be:
$('img.pan').wrap('%div class="wrap_pan-image" /%').wrap('%div class="pan-image" /%').wrap('%div class="img-container" /%');
Hi Kasimir, thanks for your version of this script, it helped me a lot 🙂
Still I have a strange problem, which seems to come from the “pan-image” div position.
Cool effect, but not really on purpose : clik here to see
Any idea how I can fix this ?
Ok, found my mistake : I didn’t get that the javascript function wrap () replaced the previous html structure. I left the whole set of imbricated divs in my html document, which made me have a double imbricated set…
Thanks again for the script 🙂
Hi!
I’ve tried using this for multiple images but I failed.
I get error message.
Should I just copy the code?
Thank you
Thank you so much, i’ve been googling for ages something like this. Many similar things, but this is exactly what i was looking for 🙂
Is it possible to mouseover Image A and have Image B do the panning (like a mouse over for detail view functionality)?
Hi ArleyM, this panning code by Manos is excellent for what it currently does. Perhaps you are looking for something like this instead
Sorry, please ignore my comment. I have placed the overlaying text div within the “imagepan” div giving the panning function for the entire image area. ‘Coffee’ is on me 🙂
Wonderful script – thank you and looking forward to donating soon. I’m testing it out to see if it works well for my project and have one question: would it be possible to control the panning from a smaller div than the image full container? For example, suppose the image is 1000×1000, the container is 600×400 but a smaller div for example 60×40 inside controls the panning. The reason is I’d like to overlay text above the image but be able to control the full image from a smaller area since the overlaying text stops the panning function. Sorry for the long comment – hope this question makes sense 🙂 Thank you in advance
thanx for that gorgeous script. make use of it @ http://www.leitbilder.net. was not to easy to figure out how to manage different images ($(window).load();). anyway still mysterious behavior with safari 5.0.x and 5.1. x (ff, chrome, opera etc work fine) . 3 of 4 times the new image will not placed properly. offline everything is fine just online it’s tricky. any idea? thanx again and thank you for any tips regarding safari ( sure its my fault, not safaris). btw any idea to have the video fluid?
hardy
did it. safari is to fast (or to slow). have to wait for some millisec to preform that window.load thing. anyway thank you again
Thanks alot, very good tutorial
Hi, your script is pretty cool, but I’m wondering if its possible somehow edit it to work with multiple images on one page? I dont want you to do it, I just need an advice, because Im JQuery newbie 🙂
THX!
Καλησπέρα,
πολύ καλή δουλεία…
μήπως υπάρχει καποια ιδέα τί να κάνω στην περίπτωση που έχω περισσότερες απο μια εικόνες..? βάζοντας ένα next κ ένα back …?
Γεια σου Δημήτρη,
Είναι διαφορετική διαδικασία και αρκετά πιο περίπλοκη η δημιουργία ενός mini-gallery. Μπορείς αν θες να δεις ένα gallery script όπως αυτό: http://manos.malihu.gr/simple-jquery-fullscreen-image-gallery και να ενώσεις τα 2 script, αλλά οπωσδήποτε χρειάζεται γνώσεις σε css και jquery.
Hi! Do you have a modified version of the script for IE 8 and 7? Cause the script doesn’t work on these versions of IE. Great great script by the way! 😀 Love it!
Really great script mate!! Really really useful!
Hi Malihu,
You’ve got a really awesome site here, spent a while just browsing it checking out scripts.
I have a bit of a problem, I’ve a plugin(http://jquery.malsup.com/cycle/) to do a simple cycle through some images, but each of them needs to pan which is where your script comes in.
It works fine on the first image, then when I go to the click next it works for a second then the whole images starts moving, interestingly if I hit the previous button and go back to the first image the first image still works.
Any ideas?
Also I’m using jquery 1.6.1 which seems to make the panning a bit jerky in ie8
I’m still working on this site so it’s not online yet, but I can put it up somewhere if it helps.
Any assistance would be highly appreciated 🙂
Hi Malihu,
Fantastic code, thanks Malihu.
Refer to following link to my test server
I am trying to place in another image over the top, absolute positioning to the panned image, as I want to make it a roll over rather than embedding the image into the panning image and creating maps. I have been successful but when you re-size any browser window the top image shows its positioning relative to the browser window and I am trying to get around this. Have tried applying class panning to it and placing the div elsewhere ie directly under imagePan div, obviously unsuccessful.
Mark up:-
CSS:-
html,body{height:100%;}
body {margin:0; padding:0; background:#eee;}
#outer_container{position:relative; height:100%; width:100%;}
#imagePan{position:relative; overflow:hidden; cursor:crosshair; height:100%; width:100%;}
#imagePan .container{position:relative; left:0; z-index:50;}
.sun {width:93px; height:94px; border:none; position:absolute; z-index:100; cursor:pointer; margin-left: 250px; margin-top:200px;}
Any help would be much appreciated
cheers,
Dave
Sorry, made a mess of last post, with link and not putting in mark up.
Anyway, mark up
div id=”outer_container”
div id=”imagePan”
div class=”container”
div class=”sun”>
div
Hi Dave,
May you help me with your code? it’s working very well but I would like the image to be resized as long as the browser window is resized… is it possible? mantaining the width and height 100% of browser window.
I dont know if I was clear about this….
Thanks!!!!!!
Hi,
Love this — it’s working great for me! I just need to tweak the speed of the panning. How would I go about making the image pan slower in relation to the mouse? Thanks!
Never mind, I think I figured it out! I was changing the animSpeed but was making it smaller when I should I have been making it bigger. Great plugin malihu!
cheers 🙂
This is great love it… but was wondering is it possible to have another image at the background that can pan with different speed like in http://kalendiar.lenm.cz/ ?
@2046 @michelle
Not as it is. I need to update the script a bit to add these features. If you have time though, you can use the plugin at http://manos.malihu.gr/jquery-thumbnail-scroller with some additional css rules to pan an image. I’ll try to find some time to do it and I’ll post it here 😉
Is there way how to have multiple images on one page, where only one image that has mouse over moves?
thx
malihu, can you please show me how to implementing image maps w/ links?
can you give me a hint?
thank you
dan
hello,
it is possible to implement image map/hot spots with hyperlinks?
Yes, there shouldn’t be any problems implementing image maps w/ links 🙂
no, i want the pixel pattern to stay on top…don’t pan.. but overlps the panning image.. ive an exmaple here click on image and see panning..
http://tympanus.net/Tutorials/FullPageImageGallery/
Pleas ehelp.. thanks.
You would need to add the overlay div below .container div (inside #imagePan) and give it an absolute position. You’d probably need to change .container position to absolute as well or/and give your overlay div a greater z-index.
Hi, really nice stuff… but i want to ask something… i want to add an overly like we see on full screen galleries. i mean the pixel overlay…i tried to add div after outer container and add pixel pattern as image bg with repeat.. but somehow the images dosent pan.. i think it’s messing with JS..
Can you help please..
thanks…
You need an extra image on top of the one that pans? Will that extra image also pan?
Hi is there a way to set the image to start of on the top right, instead of bottom right. im using a large image as the image and it starts off on bottom right, how can i set it to strat top right
The image’s initial position is and should be centered (horizontal and vertical).
To change it, find “$imagePan_panning.css(…” inside the script and remove/comment it. On window resize, image position is also reset ($imagePan_container.css(“top”,0).css(“left”,0);), so you may wanna change this as well if you need it bottom/right.
Hi,
I’ve the same query as above.
I’m not a script writer.
Could you please clearly explain it?
Thank you very much.
This is just what I needed, thanks! I’ll show you where I’m using it once I get the greenlight 🙂
Hello,
would be possible to apply the panning effect to the div’s background image, so that i can have some text on top of it?
thank you
great job, with all of your scripts!
Hi,
You could add another absolutely positioned div inside #outer_container with greater z-index and 100% width/height and add your text inside it. This way the text containing div would be on top of the panning image.
great! thanks for the quick reply! keep up the great work here 🙂
malihu,
i do love your work. I understand that most people dont care about IE anymore but with clients, it is always a concern. the panning effect does some wierd things in ie6. Do you have a possible fix for this?
Ibuku
Ah, sorry but I never check any of the things I do in IE6, so I have absolutely no idea what could be the problem. I understand that a client might request it but it’s a conscious decision of mine not to develop or support old browsers like IE6.
This was really helpful! Everything works great.
Hello malihu – superb clean code, with a responsive feel!
Have you considered turning this elegant snippet into a simple image gallery? I’ve looked without success for a jQuery gallery with panning functionality – it would be a wonderful tool for novice coders like myself –
thanks again for your reliably crisp code!
Hello and thanks for your comments 🙂
I might try making one when I get some time. Do you have any specific idea of its functionality?
Is it possible to have panning the fading effect together? By the way, love it!
Thanks! How do you mean?
Hi there,
I want to have more than one image in the container and have them fade in and out while at the same time when a particular image is being displayed, the panning effect will be applied to it. I hope you can understand what I mean. Thanks.
Very nice one. Thank you for sharing all those beautiful code 🙂
help me please how to embed it at web page?
Download the script. Inside the .zip file there’s the html file that you can open with a text editor and see the code.