This website uses cookies to personalise ads and to analyse traffic ok
web design

jQuery image panning

Image panning with animation easing that works on mouse movement with css and jQuery.

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

 

FAQ


80 Comments

Post a comment

Comments pages: 1 2

  1. ash
    Posted on July 28, 2015 at 12:38 Permalink

    This is a great piece of code! I was wondering would it work with panning only a certain amount of a image? AKA constraining the image pan?

    Reply
    • malihu
      Posted on August 1, 2015 at 19:25 Permalink

      You’d need to pan a div with the image as a background, like this example (check the first one):
      http://manos.malihu.gr/repository/jquery-image-panning/demo/image-map-div-panning.html

      In this example you could use a combination of div dimensions with background position to do what you need. For example:
      .img-pan-container .img-wrapper .img{ /* div panning */ position: relative; /* make panned element smaller than its background image */ width: 1320px; height: 621px; } /* ... */ /* set its background position to get where you want */ <div class="img" style="background:url(img.jpg) no-repeat 70% bottom;">

      Reply
  2. christopher
    Posted on June 1, 2015 at 05:27 Permalink

    hi malihu,

    this is great – thanks!

    i’m wondering, could it be adapted to pan a div instead of an image? i’d like to pan a map (image or div with a background image), which has markers (divs) absolutely positioned on top of it. i was thinking if i could move the entire div, then those markers would stay in place.

    is this something possible? thanks!

    Reply
  3. Khai
    Posted on May 26, 2015 at 21:12 Permalink

    Hey

    First, love your work!
    I’m looking for a image panner for a project for the Norwegian Colorlab: http://colorlab.no/. The goal of the project is to have a research tool for image quality assessment and need a panner for large images.

    Would it be possible in the demo where there are multiple instances that they move paralell to each other? Meaning if you pan in either boxes/instances the others would move the same?

    Thanks in advance.

    Best,
    Khai

    Reply
    • malihu
      Posted on May 27, 2015 at 16:54 Permalink

      Hello,

      It can be done with few minor script mods. I’ll upload an example later today πŸ˜‰

      Reply
    • malihu
      Posted on May 27, 2015 at 17:23 Permalink

      Made some minor changes to the script and I’ve uploaded a new example here:
      http://manos.malihu.gr/repository/jquery-image-panning/demo/multiple-instances-synchronization.html

      Easy setup:

      1. Add a class to the instances you want to synchronize, e.g. “sync”
      <div class="content sync"> <img src="img.jpg" /> </div> <div class="content sync"> <img src="img.jpg" /> </div> <div class="content sync"> <img src="img.jpg" /> </div>

      2. Add the class selector to sync variable (in multiple-instances-synchronization.html on line: 84):
      sync=".sync"

      Reply
    • malihu
      Posted on May 27, 2015 at 17:26 Permalink

      I’ve added the multiple-instances-synchronization.html example in the demo links at the top of the page, as well as the download archive.

      Reply
      • Khai
        Posted on May 27, 2015 at 17:30 Permalink

        Thank you so much, this is really great and such a fast reply!

        Reply
  4. Laura
    Posted on March 25, 2015 at 21:24 Permalink

    Thanks for the code!

    Is there a way to position the image within the container so that the top of the image is aligned with the top of the container? I added autopan on window load. I’d like the image to load from top to bottom on window load.

    Reply
    • malihu
      Posted on April 4, 2015 at 15:20 Permalink

      Hello,

      I’ve just updated the script. In the updated code, you can change the centering by changing:
      var init="center"
      to:
      var init="0"

      Reply
      • Laura
        Posted on April 6, 2015 at 23:49 Permalink

        Thanks Malihu. I’m too far down the rabbit hole on the old code at this point. I got the image to pan from top to bottom on window load, but now when I include a setTimeout function for the MouseMove function, I can no longer pan the image. Ideas?

        Reply
        • malihu
          Posted on April 7, 2015 at 16:00 Permalink

          What do you need the timeout for? If you can send me your url/code I’ll be able to help.

          Reply
  5. cms
    Posted on February 25, 2014 at 12:19 Permalink

    I needed this code tanx a lot

    Reply
  6. mediahub
    Posted on August 26, 2013 at 08:59 Permalink

    Nice code, is it works or compatible with touch device like smartphone or tablets?

    Reply
  7. newwebsitethemes
    Posted on August 26, 2013 at 08:56 Permalink

    Love this effects, now trying to make some different position, thanks for the great tips.

    Reply
  8. rjgamer
    Posted on July 24, 2013 at 08:32 Permalink

    Hi,

    great code! Thanks dude!

    I’ve question: I want the mouse outside of the div for moving the image. I’ve changed this code:
    $imagePan.bind("mousemove", function(event){ MouseMove(event); });
    to this:
    $('body').bind("mousemove", function(event){ MouseMove(event); });

    But my change worked not correct. What have I to change?

    Thanks for helping.

    Reply
  9. Alicia
    Posted on April 30, 2013 at 10:14 Permalink

    Hey, nice script

    I was wondering if it’s possible to change the code for viewing on smartphones & tablets so that the user can drag instead of hover?

    Reply
  10. Alicia
    Posted on April 30, 2013 at 10:14 Permalink

    Hi there, great script!

    I was wondering if it’s possible to change the code for viewing on smartphones & tablets so that the user can drag instead of hover?

    Reply
  11. Dean
    Posted on February 20, 2013 at 00:49 Permalink

    Thanks for a great script it is the perfect answer to what I was looking for.
    I have impleted your script on a page of a website of mine to allow users to move a map around. I hope to eventually use it on photographs of mine.

    I am trying to understand how I can get the script to work on a page that resizes depending on what size screen the viewer is using and even how to implement it on a page with a header and a footer and the usaual menu buttons.

    I take my hat off to people who understand HTML, Java and CSS as I have to rely on WordPress themes and Dreamweaver. Having said that I still can’t get Dreamweaver to do what I want hence the switch to WordPress, apart from the page with the map.

    Okay I’m off to the library to get a book on the subject!

    Reply
  12. anija
    Posted on January 23, 2013 at 16:36 Permalink

    Hi! Thank you for this code :)
    i’m using it with outside container fixed at 100% width and 100% height (a fullscreen image zoom) but i’ve a problem on the left side: when mouse reach position 0, the image is shifted on the right of about 200px, instead vertical movements are fine, and i just can’t realize why…

    Reply
  13. DongDongFace
    Posted on May 18, 2012 at 05:42 Permalink

    Fix ie6 display Bug:
    find line :
    var easeType="easeOutCirc";

    replace to:

    var easeType="easeOutCirc"; if ($.browser.msie && $.browser.version.substr(0,1)<7) {$imagePan_panning.css("margin-left","0px").css("margin-top","0px");}// fix ie6

    Reply
  14. Alan
    Posted on May 2, 2012 at 22:35 Permalink

    Hi. Thanks for this great script. I have used it on the Berndt Museum site for a virtual exhibition, using Lasso to build the clickable links by pulling information from the filemaker database. Check it out at http://berndt.uwa.edu.au/panorama.lasso?panID=1. Thanks a million!

    Reply
    • malihu
      Posted on May 2, 2012 at 23:53 Permalink

      Awesome! Thank you for using the script and for your comments Alan :)

      Reply
  15. phonecluster
    Posted on April 27, 2012 at 13:05 Permalink

    Yes, I’m looking for this wonderful effect, thank you so much, great job.

    Reply

Comments pages: 1 2

Post a comment

Your e-mail is never published nor shared. Required fields are marked *

You may use these HTML tags and attributes:
<a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>
You can write or copy/paste code directly in your comment using the <code> tag:
<code>code here...</code>
You may also use the data-lang attribute to determine the code language like so:
<code data-lang-html>, <code data-lang-css>, <code data-lang-js> and <code data-lang-php>

css.php