javascript的缓动效果

JavaScript技术    2009-09-22 14:38  

  缓动,学名为Tween,缓冲移动的简称。要想页面内容切换起来舒服,就使用淡入淡出特效,要想让页面元素动起来自然,就要使用缓动效果。这两个混合起来,可以衍生多种特效的。感谢Flash开发人员为我们做了那么多先行研究,我们直接把它们拆出来装在各种菜单与相册中。我们先从最简单的东西做起,加速与减速。

  既然是缓动,它就一定涉及以下概念:距离,时间与速度。我们可以想象存在一条直线L,点A与点B就是L的起点与终点,有一个点C在直线L上移动,从点A到点B。所需的时间通常都是未知,但速度我们一定要制定。看下面的图,我们想让绿色的方块在淡紧色的滑动带上移动。滑动带左上角就相当于点A,右上角就相当于B点,方块的左上角就相当于点C,移动距离为两者的宽度之差。由于我们移动的物体是存在宽度,也就是说点C永远不可能与点B重合。但一个准确的目的地(为了方便,我们把它称之为点D)是必须的,我们一定要计算它出来。因为在加速运动中,点C随时可能超过点D,当点超过它时,我们就要终止此移动,并把点C拉回到点D上。

  运行代码

  

  以下为引用的内容:

  <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
<HTML>
 <HEAD>
  <TITLE> New Document </TITLE>
  <META NAME="Generator" CONTENT="EditPlus">
  <META NAME="Author" CONTENT="">
  <META NAME="Keywords" CONTENT="">
  <META NAME="Description" CONTENT="">
 </HEAD>
<BODY>
<STYLE type=text/css>#taxiway {
 BACKGROUND: #e8e8ff; WIDTH: 800px; HEIGHT: 100px
}
#move {
 BACKGROUND: #a9ea00; WIDTH: 100px; HEIGHT: 100px
}
</STYLE>
<DIV id=taxiway>
<DIV id=move onclick=move(this)></DIV></DIV>
<P class=notice display="text-align:center">点击可移动绿色方块</P>
<SCRIPT type=text/javascript>
  var getCoords = function(el){
    var box = el.getBoundingClientRect(),
    doc = el.ownerDocument,
    body = doc.body,
    html = doc.documentElement,
    clientTop = html.clientTop || body.clientTop || 0,
    clientLeft = html.clientLeft || body.clientLeft || 0,
    top  = box.top  + (self.pageYOffset || html.scrollTop  ||  body.scrollTop ) - clientTop,
    left = box.left + (self.pageXOffset || html.scrollLeft ||  body.scrollLeft) - clientLeft
    return { 'top': top, 'left': left };
  };

    var getStyle = function(el, style){
    if(!+"\v1"){
      style = style.replace(/\-(\w)/g, function(all, letter){
        return letter.toUpperCase();
      });
      var value = el.currentStyle[style];
      (value == "auto")&&(value = "0px" );
      return value;
    }else{
      return document.defaultView.getComputedStyle(el, null).getPropertyValue(style)
    }
  }
  //辅助函数3,相当于$(),不用$符号命名是因为博客园在用JQuery,会引起命名冲突
  //我新一代查代元素的方法,拥有缓存能力
  var cache = []
  var _ = function(id){
    return cache[id] || (cache[id] = document.getElementById(id));
  }

    var move = function(el){
    el.style.position = "absolute";
    var begin =  getCoords(el).left,
    distance = parseFloat(getStyle(_("taxiway"),"width")) - parseFloat(getStyle(el,"width")),
    end = begin  + distance,
    speed = 10,//第一次移动的速度,单位px/ms,隐式地乘以1ms
    delta = 1.5,
    change = true;
    el.onclick = function(){
      if(change){
        el.innerHTML = "加速";
        (function(){
          setTimeout(function(){
            el.style.left = getCoords(el).left + speed + "px";//移动
            speed *= delta;//下一次移动的距离
            if(getCoords(el).left >= end){
              el.style.left = end + "px";
              change = false;
              delta = 0.85,
              speed = 100;
            }else{
              setTimeout(arguments.callee,25);//每移动一次停留25毫秒
            }
          },25)
        })()
      }else{
        el.innerHTML = "减速";
        (function(){
          setTimeout(function(){
            el.style.left = getCoords(el).left - speed + "px";//移动
            speed = Math.ceil(speed * delta );//下一次移动的距离
            if(getCoords(el).left <= begin ){
              el.style.left = begin + "px";
              change = true;
              delta = 1.5,
              speed = 10;
            }else{
              setTimeout(arguments.callee,25);
            }
          },25)
        })()
      }
    }
  }
  window.onload = function(){
    move(_("move"))
  }
</SCRIPT>
</BODY></HTML> 

  为了获取它们在页面上的坐标与尺寸,getCoords()与getStyle()又到出场时间了。对不起,我实在没有意思来炫耀我的函数。更何况getStyle()被砍去了不少东西,威力没有以前那么强大。

  

  以下为引用的内容:

  //辅助函数1
var getCoords = function(el){ 
  var box = el.getBoundingClientRect(), 
  doc = el.ownerDocument, 
  body = doc.body, 
  html = doc.documentElement, 
  clientTop = html.clientTop || body.clientTop || 0, 
  clientLeft = html.clientLeft || body.clientLeft || 0, 
  top  = box.top  + (self.pageYOffset || html.scrollTop  ||  body.scrollTop ) - clientTop, 
  left = box.left + (self.pageXOffset || html.scrollLeft ||  body.scrollLeft) - clientLeft 
  return { 'top': top, 'left': left };
};
//辅助函数2
var getStyle = function(el, style){ 
  if(!+"\v1"){ 
    style = style.replace(/\-(\w)/g, function(all, letter){ 
      return letter.toUpperCase(); 
    }); 
    var value = el.currentStyle[style]; 
    (value == "auto")&&(value = "0px" ); 
    return value; 
  }else{ 
    return document.defaultView.getComputedStyle(el, null).getPropertyValue(style) 
  }
}

  那么我们怎么移动呢?在javascript只有让它变为绝对定位对象,给它的top与left赋值。它就会立即移动到相应的坐标上。由于javascript处理位置变化太有效率,根本不可能让你有“移动”的感觉,感觉是直接从点C直接跳到点D。我们必须让物体每移动一点点,就停一下,让眼睛有个残影。根据人眼睛的视觉停留效应,若前一幅画像留在大脑中的印象还没消失,后一幅画像就接踵而至,而且两副画面间的差别很小,就会有“动”的感觉。那么停留多么毫秒最合适呢?我们不但要照顾人的眼睛,还要顾及一下显示器的显示速度与浏览器的渲染速度。根据外国的统计,25毫秒为最佳数值。其实,这个数值我们应该当作常识来记住。联想一下,日本动画好像有个规定是1秒30张画,中国的,比较垃圾,是1秒24张。用1秒去除以张数,就得到每张停留的时间。日本的那个27.77毫秒已经很接近我们的25毫秒了,因为浏览器的渲染速度明显不如电视机的渲染速度,尤其是IE6这个拉后腿的。要实现加速度,就是让它每次移动快一点点,让上一次移动的距离乘以一个大于1的数便可。

  

  以下为引用的内容:

  //辅助函数3,相当于$(),不用$符号命名是因为博客园在用JQuery,会引起命名冲突
//我新一代查代元素的方法,拥有缓存能力
var cache = []
var _ = function(id){ 
  return cache[id] || (cache[id] = document.getElementById(id));
}
//主函数:加速移动
var accelerate= function(el){ 
  el.style.position = "absolute"; 
  var begin =  getCoords(el).left, 
  distance = parseFloat(getStyle(_("taxiway"),"width")) - parseFloat(getStyle(el,"width")), 
  end = begin + distance, 
  speed = 10;//第一次移动的速度,单位px/ms,隐式地乘以1ms 
  (function(){ 
    setTimeout(function(){ 
      el.style.left = getCoords(el).left + speed + "px";//移动 
      speed *= 1.5;//下一次移动的距离 
      if(getCoords(el).left >= end){ 
        el.style.left = end + "px"; 
      }else{        
        setTimeout(arguments.callee,25);//每移动一次停留25毫秒 
      } 
    },25) 
  })()
}

  明白了加速,减速就好办了。我们给第一次移动的距离一个很大的数,往后每次减少一点点,换言之乘以一个小于1的数。但这里有个注意点,如果有一次,它移动的距离少于1px怎么办?!它再往后也是少于1px。浏览器就会忽略这个值,当作0来处理。这样一来,它就会停在中途不动了。为了防止这样可怕的事发生,我们利用Math.ceil来确保其最小移动距离为1px,哪怕最后的匀速移动也要抵达终点。

  

  以下为引用的内容:

  //主函数:减速移动 
var decelerate = function(el){ 
   el.style.position = "absolute"; 
   var begin =  getCoords(el).left, 
   distance = parseFloat(getStyle(_("taxiway"),"width")) - parseFloat(getStyle(el,"width")), 
   end = begin + distance, 
   speed = 100;//第一次移动的速度,单位px/ms,隐式地乘以1ms 
   (function(){ 
     setTimeout(function(){ 
       el.style.left = getCoords(el).left + speed + "px";//移动 
       speed = Math.ceil(speed * 0.9);//下一次移动的距离 
       if(getCoords(el).left <= end){ 
         el.style.left = end + "px"; 
       }else{        
         setTimeout(arguments.callee,25); 
       } 
     },25) 
   })() 
}

  现在函数的功能还很弱,主要是由于在抽象与制定上有所欠缺,如果克服这些缺点并配合Robert Penner大神的缓动公式,我们就可以搞出花样繁多的缓动效果来。而这正是下面要讲解的。

  下面这部分对原先的缓动函数进行抽象化,并结合缓动公式进行强化。成品的效果非常惊人逆天。走过路过不要错过。

  好了,打诨到此为止。普通的加速减速是难以让人满意的,为了实现弹簧等让人眼花缭乱的效果必须动用缓动公式。我见过两套缓动公式,一套是早期Robert Penner大神的缓动公式,内置到tween类中,不过现在人们越来越推荐tweenlite这个新秀了。另一套是script.aculo.us与mootools里面的,由于mootools可称之为prototype的升级版,script.aculo.us则是基于prototype,我们就把它们并称为prototype流派。与flash流派最大的不同是,它们封装得更好,并只需传入一个参数(0~1的小数),并且拥有严密的队列机制来调用各种回调函数。如在回调函数设置元素的长宽,就弄成Scale特效,利用它我们进一步制作SlideUp,SlideDown,Squish等复合特效。

  我们先来看flash流派的缓动公式,它们基本都有如下四个参数。

  t:timestamp,指缓动效果开始执行到当前帧开始执行时经过的时间段,单位ms

  b:beginning position,起始位置

  c:change,要移动的距离,就是终点位置减去起始位置。

  d: duration ,缓和效果持续的时间。

  我们把这四个参数传入Robert Penner大神的缓动公式,它就会计算出当前帧物体移动的位置。我们对比原来的函数来改写。

  

  以下为引用的内容:

  var transition = function(el){ 
  transition.linear = function(t,b,c,d){ return c*t/d + b; };//免费提供一个缓动公式(匀速运动公式) 
  el.style.position = "absolute"; 
  var options = arguments[1] || {}, 
  begin =  getCoords(el).left,//开始位置 
  change = parseFloat(getStyle(_("taxiway"),"width")) - parseFloat(getStyle(el,"width")),//要移动的距离 
  duration = options.duration || 500,//缓动效果持续时间 
  ease = options.ease || transition.linear,//要使用的缓动公式 
  end = begin + change,//结束位置 
  startTime = new Date().getTime();//开始执行的时间 
  (function(){ 
    setTimeout(function(){ 
      var newTime = new Date().getTime(),//当前帧开始的时间 
      timestamp = newTime - startTime;//逝去时间 
      el.style.left = ease(timestamp,begin,change,duration) + "px";//移动 
      if(duration <= timestamp){ 
        el.style.left = end + "px"; 
      }else{ 
        setTimeout(arguments.callee,25);//每移动一次停留25毫秒 
      } 
    },25) 
  })()
}

  接着是各种缓动公式大阅兵,共分为十一大类,除了linear。其他类又分为三种。

  easeIn方法控制补间如何从开始到最高速度。

  easeOut 方法控制补间减速并停在目标位置

  easeInOut方法同时控制上述两者。

  具体公式见下面(共31种):

  

  以下为引用的内容:

  //***********@author:Robert Penner and cloudgamer*************
//http://www.cnblogs.com/cloudgamer/archive/2009/01/06/Tween.html
  var Tween = {
    Linear: function(t,b,c,d){ return c*t/d + b; },
    Quad: {
      easeIn: function(t,b,c,d){
        return c*(t/=d)*t + b;
      },
      easeOut: function(t,b,c,d){
        return -c *(t/=d)*(t-2) + b;
      },
      easeInOut: function(t,b,c,d){
        if ((t/=d/2) < 1) return c/2*t*t + b;
        return -c/2 * ((--t)*(t-2) - 1) + b;
      }
    },
    Cubic: {
      easeIn: function(t,b,c,d){
        return c*(t/=d)*t*t + b;
      },
      easeOut: function(t,b,c,d){
        return c*((t=t/d-1)*t*t + 1) + b;
      },
      easeInOut: function(t,b,c,d){
        if ((t/=d/2) < 1) return c/2*t*t*t + b;
        return c/2*((t-=2)*t*t + 2) + b;
      }
    },
    Quart: {
      easeIn: function(t,b,c,d){
        return c*(t/=d)*t*t*t + b;
      },
      easeOut: function(t,b,c,d){
        return -c * ((t=t/d-1)*t*t*t - 1) + b;
      },
      easeInOut: function(t,b,c,d){
        if ((t/=d/2) < 1) return c/2*t*t*t*t + b;
        return -c/2 * ((t-=2)*t*t*t - 2) + b;
      }
    },
    Quint: {
      easeIn: function(t,b,c,d){
        return c*(t/=d)*t*t*t*t + b;
      },
      easeOut: function(t,b,c,d){
        return c*((t=t/d-1)*t*t*t*t + 1) + b;
      },
      easeInOut: function(t,b,c,d){
        if ((t/=d/2) < 1) return c/2*t*t*t*t*t + b;
        return c/2*((t-=2)*t*t*t*t + 2) + b;
      }
    },
    Sine: {
      easeIn: function(t,b,c,d){
        return -c * Math.cos(t/d * (Math.PI/2)) + c + b;
      },
      easeOut: function(t,b,c,d){
        return c * Math.sin(t/d * (Math.PI/2)) + b;
      },
      easeInOut: function(t,b,c,d){
        return -c/2 * (Math.cos(Math.PI*t/d) - 1) + b;
      }
    },
    Expo: {
      easeIn: function(t,b,c,d){
        return (t==0) ? b : c * Math.pow(2, 10 * (t/d - 1)) + b;
      },
      easeOut: function(t,b,c,d){
        return (t==d) ? b+c : c * (-Math.pow(2, -10 * t/d) + 1) + b;
      },
      easeInOut: function(t,b,c,d){
        if (t==0) return b;
        if (t==d) return b+c;
        if ((t/=d/2) < 1) return c/2 * Math.pow(2, 10 * (t - 1)) + b;
        return c/2 * (-Math.pow(2, -10 * --t) + 2) + b;
      }
    },
    Circ: {
      easeIn: function(t,b,c,d){
        return -c * (Math.sqrt(1 - (t/=d)*t) - 1) + b;
      },
      easeOut: function(t,b,c,d){
        return c * Math.sqrt(1 - (t=t/d-1)*t) + b;
      },
      easeInOut: function(t,b,c,d){
        if ((t/=d/2) < 1) return -c/2 * (Math.sqrt(1 - t*t) - 1) + b;
        return c/2 * (Math.sqrt(1 - (t-=2)*t) + 1) + b;
      }
    },
    Elastic: {
      easeIn: function(t,b,c,d,a,p){
        if (t==0) return b;  if ((t/=d)==1) return b+c;  if (!p) p=d*.3;
        if (!a || a < Math.abs(c)) { a=c; var s=p/4; }
        else var s = p/(2*Math.PI) * Math.asin (c/a);
        return -(a*Math.pow(2,10*(t-=1)) * Math.sin( (t*d-s)*(2*Math.PI)/p )) + b;
      },
      easeOut: function(t,b,c,d,a,p){
        if (t==0) return b;  if ((t/=d)==1) return b+c;  if (!p) p=d*.3;
        if (!a || a < Math.abs(c)) { a=c; var s=p/4; }
        else var s = p/(2*Math.PI) * Math.asin (c/a);
        return (a*Math.pow(2,-10*t) * Math.sin( (t*d-s)*(2*Math.PI)/p ) + c + b);
      },
      easeInOut: function(t,b,c,d,a,p){
        if (t==0) return b;  if ((t/=d/2)==2) return b+c;  if (!p) p=d*(.3*1.5);
        if (!a || a < Math.abs(c)) { a=c; var s=p/4; }
        else var s = p/(2*Math.PI) * Math.asin (c/a);
        if (t < 1) return -.5*(a*Math.pow(2,10*(t-=1)) * Math.sin( (t*d-s)*(2*Math.PI)/p )) + b;
        return a*Math.pow(2,-10*(t-=1)) * Math.sin( (t*d-s)*(2*Math.PI)/p )*.5 + c + b;
      }
    },
    Back: {
      easeIn: function(t,b,c,d,s){
        if (s == undefined) s = 1.70158;
        return c*(t/=d)*t*((s+1)*t - s) + b;
      },
      easeOut: function(t,b,c,d,s){
        if (s == undefined) s = 1.70158;
        return c*((t=t/d-1)*t*((s+1)*t + s) + 1) + b;
      },
      easeInOut: function(t,b,c,d,s){
        if (s == undefined) s = 1.70158;
        if ((t/=d/2) < 1) return c/2*(t*t*(((s*=(1.525))+1)*t - s)) + b;
        return c/2*((t-=2)*t*(((s*=(1.525))+1)*t + s) + 2) + b;
      }
    },
    Bounce: {
      easeIn: function(t,b,c,d){
        return c - Tween.Bounce.easeOut(d-t, 0, c, d) + b;
      },
      easeOut: function(t,b,c,d){
        if ((t/=d) < (1/2.75)) {
          return c*(7.5625*t*t) + b;
        } else if (t < (2/2.75)) {
          return c*(7.5625*(t-=(1.5/2.75))*t + .75) + b;
        } else if (t < (2.5/2.75)) {
          return c*(7.5625*(t-=(2.25/2.75))*t + .9375) + b;
        } else {
          return c*(7.5625*(t-=(2.625/2.75))*t + .984375) + b;
        }
      },
      easeInOut: function(t,b,c,d){
        if (t < d/2) return Tween.Bounce.easeIn(t*2, 0, c, d) * .5 + b;
        else return Tween.Bounce.easeOut(t*2-d, 0, c, d) * .5 + c*.5 + b;
      }
    }
  }

  

  以下为引用的内容:

  <div id="taxiway">  
<div id="move" onclick="transition(this,{ease:Tween.Bounce.easeOut})"></div>
</div>

  但我不喜欢flash流派的缓动公式,为了使用prototype流派的缓动公式,我进一步改进与抽象化我的缓动函数

  

  以下为引用的内容:

  //******************@author : 司徒正美************ 
  var transition = function(el){ 
    el.style.position = "absolute"; 
    var options = arguments[1] || {}, 
    begin =  options.begin,//开始位置 
    change = options.change,//变化量 
    duration = options.duration || 500,//缓动效果持续时间 
    field = options.field,//必须指定,基本上对top,left,width,height这个属性进行设置 
    ftp = options.ftp || 50, 
    onStart = options.onStart || function(){}, 
    onEnd = options.onEnd || function(){}, 
    ease = options.ease,//要使用的缓动公式 
    end = begin + change,//结束位置 
    startTime = new Date().getTime();//开始执行的时间 
    onStart(); 
    (function(){ 
      setTimeout(function(){ 
        var newTime = new Date().getTime(),//当前帧开始的时间 
        timestamp = newTime - startTime,//逝去时间 
        delta = ease(timestamp / duration); 
        el.style[field] = Math.ceil(begin + delta * change) + "px"
        if(duration <= timestamp){ 
          el.style[field] = end + "px"; 
          onEnd(); 
        }else{ 
          setTimeout(arguments.callee,1000/ftp); 
        } 
      },1000/ftp) 
    })() 
  }

  

参数 类型 说明
el element 必需,为页面元素
begin number 必需,开始的位置
change number 必需,要移动的距离
duration number 可选,缓动效果持续时间,默认是500ms。建议取300~1000ms。
field string 必需,要发生变化的样式属性。请在top,left,bottom,right,width与height中选择。
ftp number 可选,每秒进行多少帧动画,默认50帧,保证流畅播放。一些参考资料,日本动画1秒36帧,中国卡通24帧,赛车游戏60帧。
ease function 必需,缓动公式,参数为0~1之间的数。可参考我下面给出的45条公式。
onStart function 可选,在开始时执行。
onEnd function 可选,在结束时执行。

  prototype流派的缓动公式,只需一个参数(增至45种):

  

  以下为引用的内容:

  var tween = {
    easeInQuad: function(pos){
      return Math.pow(pos, 2);
    },

      easeOutQuad: function(pos){
      return -(Math.pow((pos-1), 2) -1);
    },

      easeInOutQuad: function(pos){
      if ((pos/=0.5) < 1) return 0.5*Math.pow(pos,2);
      return -0.5 * ((pos-=2)*pos - 2);
    },

      easeInCubic: function(pos){
      return Math.pow(pos, 3);
    },

      easeOutCubic: function(pos){
      return (Math.pow((pos-1), 3) +1);
    },

      easeInOutCubic: function(pos){
      if ((pos/=0.5) < 1) return 0.5*Math.pow(pos,3);
      return 0.5 * (Math.pow((pos-2),3) + 2);
    },

      easeInQuart: function(pos){
      return Math.pow(pos, 4);
    },

      easeOutQuart: function(pos){
      return -(Math.pow((pos-1), 4) -1)
    },

      easeInOutQuart: function(pos){
      if ((pos/=0.5) < 1) return 0.5*Math.pow(pos,4);
      return -0.5 * ((pos-=2)*Math.pow(pos,3) - 2);
    },

      easeInQuint: function(pos){
      return Math.pow(pos, 5);
    },

      easeOutQuint: function(pos){
      return (Math.pow((pos-1), 5) +1);
    },

      easeInOutQuint: function(pos){
      if ((pos/=0.5) < 1) return 0.5*Math.pow(pos,5);
      return 0.5 * (Math.pow((pos-2),5) + 2);
    },

      easeInSine: function(pos){
      return -Math.cos(pos * (Math.PI/2)) + 1;
    },

      easeOutSine: function(pos){
      return Math.sin(pos * (Math.PI/2));
    },

      easeInOutSine: function(pos){
      return (-.5 * (Math.cos(Math.PI*pos) -1));
    },

      easeInExpo: function(pos){
      return (pos==0) ? 0 : Math.pow(2, 10 * (pos - 1));
    },

      easeOutExpo: function(pos){
      return (pos==1) ? 1 : -Math.pow(2, -10 * pos) + 1;
    },

      easeInOutExpo: function(pos){
      if(pos==0) return 0;
      if(pos==1) return 1;
      if((pos/=0.5) < 1) return 0.5 * Math.pow(2,10 * (pos-1));
      return 0.5 * (-Math.pow(2, -10 * --pos) + 2);
    },

      easeInCirc: function(pos){
      return -(Math.sqrt(1 - (pos*pos)) - 1);
    },

      easeOutCirc: function(pos){
      return Math.sqrt(1 - Math.pow((pos-1), 2))
    },

      easeInOutCirc: function(pos){
      if((pos/=0.5) < 1) return -0.5 * (Math.sqrt(1 - pos*pos) - 1);
      return 0.5 * (Math.sqrt(1 - (pos-=2)*pos) + 1);
    },

      easeOutBounce: function(pos){
      if ((pos) < (1/2.75)) {
        return (7.5625*pos*pos);
      } else if (pos < (2/2.75)) {
        return (7.5625*(pos-=(1.5/2.75))*pos + .75);
      } else if (pos < (2.5/2.75)) {
        return (7.5625*(pos-=(2.25/2.75))*pos + .9375);
      } else {
        return (7.5625*(pos-=(2.625/2.75))*pos + .984375);
      }
    },

      easeInBack: function(pos){
      var s = 1.70158;
      return (pos)*pos*((s+1)*pos - s);
    },

      easeOutBack: function(pos){
      var s = 1.70158;
      return (pos=pos-1)*pos*((s+1)*pos + s) + 1;
    },

      easeInOutBack: function(pos){
      var s = 1.70158;
      if((pos/=0.5) < 1) return 0.5*(pos*pos*(((s*=(1.525))+1)*pos -s));
      return 0.5*((pos-=2)*pos*(((s*=(1.525))+1)*pos +s) +2);
    },

      elastic: function(pos) {
      return -1 * Math.pow(4,-8*pos) * Math.sin((pos*6-1)*(2*Math.PI)/2) + 1;
    },

      swingFromTo: function(pos) {
      var s = 1.70158;
      return ((pos/=0.5) < 1) ? 0.5*(pos*pos*(((s*=(1.525))+1)*pos - s)) :
        0.5*((pos-=2)*pos*(((s*=(1.525))+1)*pos + s) + 2);
    },

      swingFrom: function(pos) {
      var s = 1.70158;
      return pos*pos*((s+1)*pos - s);
    },

      swingTo: function(pos) {
      var s = 1.70158;
      return (pos-=1)*pos*((s+1)*pos + s) + 1;
    },

      bounce: function(pos) {
      if (pos < (1/2.75)) {
        return (7.5625*pos*pos);
      } else if (pos < (2/2.75)) {
        return (7.5625*(pos-=(1.5/2.75))*pos + .75);
      } else if (pos < (2.5/2.75)) {
        return (7.5625*(pos-=(2.25/2.75))*pos + .9375);
      } else {
        return (7.5625*(pos-=(2.625/2.75))*pos + .984375);
      }
    },

      bouncePast: function(pos) {
      if (pos < (1/2.75)) {
        return (7.5625*pos*pos);
      } else if (pos < (2/2.75)) {
        return 2 - (7.5625*(pos-=(1.5/2.75))*pos + .75);
      } else if (pos < (2.5/2.75)) {
        return 2 - (7.5625*(pos-=(2.25/2.75))*pos + .9375);
      } else {
        return 2 - (7.5625*(pos-=(2.625/2.75))*pos + .984375);
      }
    },

      easeFromTo: function(pos) {
      if ((pos/=0.5) < 1) return 0.5*Math.pow(pos,4);
      return -0.5 * ((pos-=2)*Math.pow(pos,3) - 2);
    },

      easeFrom: function(pos) {
      return Math.pow(pos,4);
    },

      easeTo: function(pos) {
      return Math.pow(pos,0.25);
    },

      linear:  function(pos) {
      return pos
    },

      sinusoidal: function(pos) {
      return (-Math.cos(pos*Math.PI)/2) + 0.5;
    },

      reverse: function(pos) {
      return 1 - pos;
    },

      mirror: function(pos, transition) {
      transition = transition || tween.sinusoidal;
      if(pos<0.5)
        return transition(pos*2);
      else
        return transition(1-(pos-0.5)*2);
    },

      flicker: function(pos) {
      var pos = pos + (Math.random()-0.5)/5;
      return tween.sinusoidal(pos < 0 ? 0 : pos > 1 ? 1 : pos);
    },

      wobble: function(pos) {
      return (-Math.cos(pos*Math.PI*(9*pos))/2) + 0.5;
    },

      pulse: function(pos, pulses) {
      return (-Math.cos((pos*((pulses||5)-.5)*2)*Math.PI)/2) + .5;
    },

      blink: function(pos, blinks) {
      return Math.round(pos*(blinks||5)) % 2;
    },

      spring: function(pos) {
      return 1 - (Math.cos(pos * 4.5 * Math.PI) * Math.exp(-pos * 6));
    },

      none: function(pos){
      return 0
    },

      full: function(pos){
      return 1
    }
  }

  

  以下为引用的内容:

  <!doctype html>
<html dir="ltr" lang="zh-CN">
    <head>
        <meta charset="utf-8"/>
        <meta http-equiv="X-UA-Compatible" content="IE=8">
        <style type="text/css">
            .taxiway{
                width:800px;
                height:100px;
                background:#E8E8FF;
            }
            .move{
                width:100px;
                height:100px;
                background:#a9ea00;
            }
            #panel {
                float:left;
                width:810px
            }
            #panel div{
                float:left;
                width:88px;
                border:1px solid #333;
                height:20px;
                font-size:11px;
            }
            div.transition {
                margin-top: 30px;
                width: 200px;
                height: 200px;
                position: relative;
                margin-bottom:10px;
            }
            div.transition div {
                position: absolute;
                height: 1px;
                width: 1px;
                background: #000;
            }
            div.transition span {
                display: block;
                position: absolute;
                border-bottom: 1px solid #dadada;
                font-size: 10px;
                color: #888;
                width: 200px;
                left: 0px;
            }
            div.transition div#indicator {
                position:absolute;
                background-color:#a9ea00;
                height: 200px;
                top: 0px;
                left: 0px;
            }
            div.transition div#marker {
                background-color: #f00;
                height: 6px;
                width: 6px;
                border-radius: 3px;
                -webkit-border-radius: 3px;
                -moz-border-radius: 3px;
                left: 0px;
                margin-bottom: -3px;
                margin-left: -3px;
            }
            div.transition div#label {
                background: transparent;
                color: #ABD474;
                font-size: 20px;
                height: 20px;
                width: 200px;
                text-align: center;
                top: 80px;
                left: 0px;
                z-index: -1;
            }
        </style>
        <script type="text/javascript">
            var getCoords = function(el){
                var box = el.getBoundingClientRect(),
                doc = el.ownerDocument,
                body = doc.body,
                html = doc.documentElement,
                clientTop = html.clientTop || body.clientTop || 0,
                clientLeft = html.clientLeft || body.clientLeft || 0,
                top  = box.top  + (self.pageYOffset || html.scrollTop  ||  body.scrollTop ) - clientTop,
                left = box.left + (self.pageXOffset || html.scrollLeft ||  body.scrollLeft) - clientLeft
                return { 'top': top, 'left': left };
            };
            var getStyle = function(el, style){
                if(!+"\v1"){
                    style = style.replace(/\-(\w)/g, function(all, letter){
                        return letter.toUpperCase();
                    });
                    var value = el.currentStyle[style];
                    (value == "auto")&&(value = "0px" );
                    return value;
                }else{
                    return document.defaultView.getComputedStyle(el, null).getPropertyValue(style)
                }
            }
            var tween = {
                easeInQuad: function(pos){
                    return Math.pow(pos, 2);
                },
                easeOutQuad: function(pos){
                    return -(Math.pow((pos-1), 2) -1);
                },
                easeInOutQuad: function(pos){
                    if ((pos/=0.5) < 1) return 0.5*Math.pow(pos,2);
                    return -0.5 * ((pos-=2)*pos - 2);
                },
                easeInCubic: function(pos){
                    return Math.pow(pos, 3);
                },
                easeOutCubic: function(pos){
                    return (Math.pow((pos-1), 3) +1);
                },
                easeInOutCubic: function(pos){
                    if ((pos/=0.5) < 1) return 0.5*Math.pow(pos,3);
                    return 0.5 * (Math.pow((pos-2),3) + 2);
                },
                easeInQuart: function(pos){
                    return Math.pow(pos, 4);
                },
                easeOutQuart: function(pos){
                    return -(Math.pow((pos-1), 4) -1)
                },
                easeInOutQuart: function(pos){
                    if ((pos/=0.5) < 1) return 0.5*Math.pow(pos,4);
                    return -0.5 * ((pos-=2)*Math.pow(pos,3) - 2);
                },
                easeInQuint: function(pos){
                    return Math.pow(pos, 5);
                },
                easeOutQuint: function(pos){
                    return (Math.pow((pos-1), 5) +1);
                },
                easeInOutQuint: function(pos){
                    if ((pos/=0.5) < 1) return 0.5*Math.pow(pos,5);
                    return 0.5 * (Math.pow((pos-2),5) + 2);
                },
                easeInSine: function(pos){
                    return -Math.cos(pos * (Math.PI/2)) + 1;
                },
                easeOutSine: function(pos){
                    return Math.sin(pos * (Math.PI/2));
                },
                easeInOutSine: function(pos){
                    return (-.5 * (Math.cos(Math.PI*pos) -1));
                },
                easeInExpo: function(pos){
                    return (pos==0) ? 0 : Math.pow(2, 10 * (pos - 1));
                },
                easeOutExpo: function(pos){
                    return (pos==1) ? 1 : -Math.pow(2, -10 * pos) + 1;
                },
                easeInOutExpo: function(pos){
                    if(pos==0) return 0;
                    if(pos==1) return 1;
                    if((pos/=0.5) < 1) return 0.5 * Math.pow(2,10 * (pos-1));
                    return 0.5 * (-Math.pow(2, -10 * --pos) + 2);
                },
                easeInCirc: function(pos){
                    return -(Math.sqrt(1 - (pos*pos)) - 1);
                },
                easeOutCirc: function(pos){
                    return Math.sqrt(1 - Math.pow((pos-1), 2))
                },
                easeInOutCirc: function(pos){
                    if((pos/=0.5) < 1) return -0.5 * (Math.sqrt(1 - pos*pos) - 1);
                    return 0.5 * (Math.sqrt(1 - (pos-=2)*pos) + 1);
                },
                easeOutBounce: function(pos){
                    if ((pos) < (1/2.75)) {
                        return (7.5625*pos*pos);
                    } else if (pos < (2/2.75)) {
                        return (7.5625*(pos-=(1.5/2.75))*pos + .75);
                    } else if (pos < (2.5/2.75)) {
                        return (7.5625*(pos-=(2.25/2.75))*pos + .9375);
                    } else {
                        return (7.5625*(pos-=(2.625/2.75))*pos + .984375);
                    }
                },
                easeInBack: function(pos){
                    var s = 1.70158;
                    return (pos)*pos*((s+1)*pos - s);
                },
                easeOutBack: function(pos){
                    var s = 1.70158;
                    return (pos=pos-1)*pos*((s+1)*pos + s) + 1;
                },
                easeInOutBack: function(pos){
                    var s = 1.70158;
                    if((pos/=0.5) < 1) return 0.5*(pos*pos*(((s*=(1.525))+1)*pos -s));
                    return 0.5*((pos-=2)*pos*(((s*=(1.525))+1)*pos +s) +2);
                },
                elastic: function(pos) {
                    return -1 * Math.pow(4,-8*pos) * Math.sin((pos*6-1)*(2*Math.PI)/2) + 1;
                },
                swingFromTo: function(pos) {
                    var s = 1.70158;
                    return ((pos/=0.5) < 1) ? 0.5*(pos*pos*(((s*=(1.525))+1)*pos - s)) :
                        0.5*((pos-=2)*pos*(((s*=(1.525))+1)*pos + s) + 2);
                },
                swingFrom: function(pos) {
                    var s = 1.70158;
                    return pos*pos*((s+1)*pos - s);
                },
                swingTo: function(pos) {
                    var s = 1.70158;
                    return (pos-=1)*pos*((s+1)*pos + s) + 1;
                },
                bounce: function(pos) {
                    if (pos < (1/2.75)) {
                        return (7.5625*pos*pos);
                    } else if (pos < (2/2.75)) {
                        return (7.5625*(pos-=(1.5/2.75))*pos + .75);
                    } else if (pos < (2.5/2.75)) {
                        return (7.5625*(pos-=(2.25/2.75))*pos + .9375);
                    } else {
                        return (7.5625*(pos-=(2.625/2.75))*pos + .984375);
                    }
                },
                bouncePast: function(pos) {
                    if (pos < (1/2.75)) {
                        return (7.5625*pos*pos);
                    } else if (pos < (2/2.75)) {
                        return 2 - (7.5625*(pos-=(1.5/2.75))*pos + .75);
                    } else if (pos < (2.5/2.75)) {
                        return 2 - (7.5625*(pos-=(2.25/2.75))*pos + .9375);
                    } else {
                        return 2 - (7.5625*(pos-=(2.625/2.75))*pos + .984375);
                    }
                },
                easeFromTo: function(pos) {
                    if ((pos/=0.5) < 1) return 0.5*Math.pow(pos,4);
                    return -0.5 * ((pos-=2)*Math.pow(pos,3) - 2);
                },
                easeFrom: function(pos) {
                    return Math.pow(pos,4);
                },
                easeTo: function(pos) {
                    return Math.pow(pos,0.25);
                },
                linear:  function(pos) {
                    return pos
                },
                sinusoidal: function(pos) {
                    return (-Math.cos(pos*Math.PI)/2) + 0.5;
                },
                reverse: function(pos) {
                    return 1 - pos;
                },
                mirror: function(pos, transition) {
                    transition = transition || tween.sinusoidal;
                    if(pos<0.5)
                        return transition(pos*2);
                    else
                        return transition(1-(pos-0.5)*2);
                },
                flicker: function(pos) {
                    var pos = pos + (Math.random()-0.5)/5;
                    return tween.sinusoidal(pos < 0 ? 0 : pos > 1 ? 1 : pos);
                },
                wobble: function(pos) {
                    return (-Math.cos(pos*Math.PI*(9*pos))/2) + 0.5;
                },
                pulse: function(pos, pulses) {
                    return (-Math.cos((pos*((pulses||5)-.5)*2)*Math.PI)/2) + .5;
                },
                blink: function(pos, blinks) {
                    return Math.round(pos*(blinks||5)) % 2;
                },
                spring: function(pos) {
                    return 1 - (Math.cos(pos * 4.5 * Math.PI) * Math.exp(-pos * 6));
                },
                none: function(pos){
                    return 0
                },
                full: function(pos){
                    return 1
                }
            }
            var _ = function(id){
                return document.getElementById(id);
            }
            var transition = function(el){
                el.style.position = "absolute";
                var options = arguments[1] || {},
                begin =  options.begin,//开始位置
                change = options.change,//变化量
                duration = options.duration || 500,//缓动效果持续时间
                field = options.field,//必须指定,基本上对top,left,width,height这个属性进行设置
                ftp = options.ftp || 50,
                onEnd = options.onEnd || function(){},
                ease = options.ease,//要使用的缓动公式
                end = begin + change,//结束位置
                startTime = new Date().getTime();//开始执行的时间
                (function(){
                    setTimeout(function(){
                        var newTime = new Date().getTime(),//当前帧开始的时间
                        timestamp = newTime - startTime,//逝去时间
                        delta = ease(timestamp / duration);
                        el.style[field] = Math.ceil(begin + delta * change) + "px"
                        if(duration <= timestamp){
                            el.style[field] = end + "px";
                            onEnd();
                        }else{
                            setTimeout(arguments.callee,1000/ftp);
                        }
                    },1000/ftp)
                })()
            }
            if (typeof Array.prototype['max'] == 'undefined') {
                Array.prototype.map = function(fn, thisObj) {
                    var scope = thisObj || window;
                    var a = [];
                    for ( var i=0, j=this.length; i < j; ++i ) {
                        a.push(fn.call(scope, this[i], i, this));
                    }
                    return a;
                };
                Array.prototype.max = function(){
                    return Math.max.apply({},this)
                }
                Array.prototype.min = function(){
                    return Math.min.apply({},this)
                }
            }
            var range = function(start,end){
                var _range = []
                for(var i = start,l=end-start;i<l;i++){
                    _range.push(i)
                }
                return _range
            }
            var draw = function(ease){
                var demo = _("transition");
                demo.innerHTML = "";//还原!
                //***********绘制控制台********************
                var values = range(0,200).map(function(v){
                    return  tween[ease](v/200) * 200;
                }),
                max = Math.max(200, values.max()),
                min = Math.min(0, values.min());
                if (min==max) {
                    min = 0;
                    max = 200;
                }
                var factor = 200/(max-min),
                grid = '<span style="bottom:'+Math.round((0-min)*factor)+'px">0</span>'+
                    '<span style="bottom:'+Math.round((200-min)*factor)+'px">1</span>',
                graph = range(0,200).map(function(v){
                    return '<div style="left:'+v+'px;bottom:'+Math.round((values[v]-min)*factor)+'px;height:1px"></div>';
                }).join('') + '<div id="indicator" style="display:none">'
                    +'</div><div id="marker" style="display:none"></div><div id="label"></div>';
                demo.innerHTML = grid + graph;
                var indicator = _("indicator"),
                marker = _("marker"),
                label = _("label"),
                demoTransition = function(pos){
                    var value = tween[ease](pos);
                    indicator.style.display = "block";
                    marker.style.display = "block";
                    marker.style.left = Math.round((pos*200))+'px';
                    marker.style.bottom = Math.round(((value*200)-min)*factor)+'px';
                    label.innerHTML = Math.round((pos*200))+'px';
                    return value;
                }
                transition(indicator,{field:"left",begin:parseFloat(getCoords(demo).left),change:200,
                    ease:demoTransition})
            }
            window.onload = function(){
                var panelHTML = function(){
                    var builder = [];
                    var _temp = 'Back Circ Cubic Expo Quad Quart Quint Sine'.split(' ');
                    var ease = _temp.map(function(v){
                        return 'easeIn'+v;
                    });
                    ease = ease.concat(_temp.map(function(v){
                        return 'easeOut'+v;
                    }));
                    ease = ease.concat(_temp.map(function(v){
                        return 'easeInOut'+v;
                    }));
                    ease = ease.concat('blink bounce bouncePast easeFrom easeFromTo easeOutBounce easeTo elastic'.split(' '));
                    ease = ease.concat('flicker full linear mirror none pulse reverse sinusoidal spring swingTo swingFrom swingFromTo wobble'.split(' '))
                    for(var i =0,l=ease.length;i<l;i++){
                        builder.push("<div onclick='draw(this.innerHTML)'>");
                        builder.push(ease[i]);
                        builder.push("</div>");
                    }
                    return builder.join('');
                }
                var panel = document.createElement("div");
                panel.id = "panel"
                panel.innerHTML = panelHTML();
                _("transition").parentNode.insertBefore(panel,_("transition").nextSibling);
            }
        </script>
        <title>缓动BY司徒正美</title>
    </head>
    <body>
        <div class="taxiway">
            <div class="move" onclick="transition(this,{field:'left',begin:parseFloat(getCoords(this).left),change:700,ease:tween.bouncePast})"></div>
        </div>
        <div class="taxiway">
            <div class="move" onclick="transition(this,{field:'width',begin:parseFloat(getStyle(this,'width')),change:300,ease:tween.spring})"></div>
        </div>
<span class="clear"></span>
<h2>请点击下表的格子</h2>
        <div id="transition" class="transition">
        </div>
    </body>
</html>

  除了这45条公式外,我们还可以制定自己的缓动公式。正如我在上面表格中提到,它在运行过程是不执行回调函数时,但你们可以在运行框中看到,我可以实现一边移动一边记录点的坐标。这是怎样实现的呢?我们只要把上面的缓动公式的任何一条塞进一个只有一个参数的函数就行了。当然此函数要有返回,供继续向下调用。以下就是一个模板:

  

  以下为引用的内容:

  var myTween = function(pos){ //缓动公式 
    var value = tween[ease](pos); 
    //***********这上面是固定的************** 
    indicator.style.display = "block"; 
    marker.style.display = "block"; 
    marker.style.left = Math.round((pos*200))+'px'; 
    marker.style.bottom = Math.round(((value*200)-min)*factor)+'px'; 
    label.innerHTML = Math.round((pos*200))+'px'; 
    //************这下面是固定的************* 
    return value; 
  }

  更多示例,不懂再留言给我。

  

 

  

  以下为引用的内容:

  <div class="taxiway"> 
  <div class="move" onclick="transition(this,{field:'left',begin:parseFloat(getCoords(this).left),change:700,ease:tween.bouncePast})"></div>
</div>
<div class="taxiway"> 
  <div class="move" onclick="transition(this,{field:'width',begin:parseFloat(getStyle(this,'width')),change:300,ease:tween.spring})"></div>
</div>

  本文链接:http://www.blueidea.com/tech/web/2009/7019.asp

在线留言

我要留言