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


147 Comments

Post a comment

Comments pages: 1 2 3 4

  1. Ranna
    Posted on November 21, 2020 at 08:50 Permalink

    Hi there! Any way to disable/enable the script upon clicking a div outside the panned content?

    This is a very nice script and has saved me tons of headache — thank you…

    Reply
  2. MeTi
    Posted on September 29, 2020 at 07:43 Permalink

    Tnx for sharing, very good job

    Reply
  3. چاپ کارت Pvc تهران
    Posted on May 3, 2020 at 18:54 Permalink

    Hello
    That is very useful and I am happy to find your article about this method.
    Thanks.

    Reply
  4. seva
    Posted on April 29, 2020 at 06:41 Permalink

    thank you.

    Reply
  5. nebengers
    Posted on March 3, 2020 at 09:21 Permalink

    Thanks

    Reply
  6. mohsai
    Posted on March 3, 2020 at 09:17 Permalink

    very good, thanks.

    Reply
  7. Mch Dz
    Posted on January 31, 2020 at 19:29 Permalink

    great job thanks

    Reply
  8. استیریتی
    Posted on December 18, 2019 at 16:07 Permalink

    I enjoyed it so much
    Do not be tired
    I sent it to my friend
    Thankful

    Reply
  9. تور کربلا
    Posted on November 3, 2019 at 13:00 Permalink

    loading site is go000ood
    Good for me
    Anyone can understand this
    The installation process is explained very well and in a very definite way.

    Reply
  10. songs
    Posted on January 28, 2019 at 14:05 Permalink

    good jobs , thanks

    Reply
  11. چاپ پلات
    Posted on December 11, 2018 at 17:11 Permalink

    very good

    Reply
  12. گروه isi center
    Posted on December 6, 2018 at 10:38 Permalink

    Hi there! Any way to disable/enable the script upon clicking a div outside the panned content?

    Reply
  13. ایده های پول سازی
    Posted on November 5, 2018 at 18:38 Permalink

    this is the best and amazing post, I liked it a lot! Thank so much!

    Reply
  14. آموزش طراحی سایت
    Posted on August 4, 2018 at 08:16 Permalink

    Thanks for sharing

    Reply
  15. چاپ دیجیتال
    Posted on July 25, 2018 at 14:33 Permalink

    So flexible …
    thanks

    Reply
  16. چاپ کارت pvc
    Posted on June 19, 2018 at 12:29 Permalink

    thanks a lot for share this post
    very good
    it is amazing script

    Reply
  17. Damian Trejo
    Posted on May 29, 2018 at 20:14 Permalink

    Hi! this code is great! is possible set two divs (o more) to moving a different speeds, simulating a perspective scenario?

    Thanks!

    Reply
  18. nopaweb
    Posted on May 29, 2018 at 15:29 Permalink

    thanks to you for your kind effort
    great!!!

    These codes are very functional

    Reply
  19. Gianluca Monti
    Posted on March 15, 2018 at 14:20 Permalink

    Greetings,
    very great stuff! there’s a way to invert panning on mobile? thanks

    Reply
  20. بالینو
    Posted on January 27, 2018 at 12:33 Permalink

    very good
    thanks a lot for share this best post

    Reply
  21. چاپ کارت تبریک
    Posted on January 27, 2018 at 12:31 Permalink

    thanks a lot for share this post
    very good

    Reply
    • ali
      Posted on April 12, 2020 at 12:18 Permalink

      very good, thanks.

      Reply
    • https://almacard.ir/
      Posted on May 17, 2021 at 09:04 Permalink

      I have a problem on Mac. I’m using some mix for image zoom and then when class for zoom is added, I’m calling this plugin for panning. On Windows, it works perfect if I add $this.trigger(“mousemove”,1) just before resize function. On Mac, piece of zoomed image that is shown on click is not good before I start moving the mouse. Then some repositioning happen and place where you clicked at first place is shown. It seems like triggering mousemove event is not working on Mac maybe.. Does anybody have some hint?

      Reply
  22. خرید اینترنتی
    Posted on November 27, 2017 at 21:37 Permalink

    tanks for sharing

    Reply
  23. deconik
    Posted on October 19, 2017 at 20:43 Permalink

    These codes are very functional thanks to you for your kind effort

    Reply
  24. فروشگاه اینترنتی
    Posted on October 8, 2017 at 22:49 Permalink

    tanks for sharing

    Reply
  25. talawp
    Posted on August 31, 2017 at 10:31 Permalink

    thank you, very very nice, kiss kiss

    Reply
  26. ratin
    Posted on June 20, 2017 at 09:40 Permalink

    very good

    Reply
  27. ratin
    Posted on June 20, 2017 at 09:38 Permalink

    thanks

    Reply
  28. Alex
    Posted on March 14, 2017 at 08:12 Permalink

    Hi there! Any way to disable/enable the script upon clicking a div outside the panned content?

    This is a very nice script and has saved me tons of headache — thank you!

    Reply
  29. mima
    Posted on March 3, 2017 at 16:17 Permalink

    I have a problem on Mac. I’m using some mix for image zoom and then when class for zoom is added, I’m calling this plugin for panning. On Windows, it works perfect if I add $this.trigger(“mousemove”,1) just before resize function. On Mac, piece of zoomed image that is shown on click is not good before I start moving the mouse. Then some repositioning happen and place where you clicked at first place is shown. It seems like triggering mousemove event is not working on Mac maybe.. Does anybody have some hint?

    Thanks!

    Reply
  30. Roee Yossef
    Posted on January 27, 2017 at 12:19 Permalink

    Thanks for the amazing script !

    It works well, but i wonder how can i get to the point where i have content on the image and the image itself keeps panning while i’m moving the mouse on the content as well. obviously, currently the image doesnt pan while the mouse is on the content..

    I’ll Appreciate a tip !

    Reply

Comments pages: 1 2 3 4

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