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


113 Comments

Post a comment

Comments pages: 1 2 3

  1. پیامک صوتی
    Posted on December 2, 2016 at 12:22 Permalink

    amazing post, thank for share it

    Reply
  2. ساخت سایت
    Posted on December 2, 2016 at 12:22 Permalink

    Great article, thanks for share it

    Reply
  3. سایت ساز
    Posted on November 7, 2016 at 17:39 Permalink

    very nice article, thanks

    Reply
  4. پیامک صوتی
    Posted on November 5, 2016 at 19:46 Permalink

    very nice article, thanks

    Reply

Comments pages: 1 2 3

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