当前位置: 首页 > 图文教程 > 网络编程 > Javascript > JS教程:Javascript实现缓动效果

Javascript
ExtJs 3.1 XmlTreeLoader Example Error
JQuery 获得绝对,相对位置的坐标方法
JQUERY操作JSON实例代码
基于Jquery的简单&简陋Tabs插件代码
jQuery插件 tabBox实现代码
JavaScript Event学习第十章 一些可替换的事件对
JavaScript Event学习第十一章 按键的检测
一段实现页面上的图片延时加载的js代码
我遇到的参数传递中 双引号单引号嵌套问题
Extjs学习过程中新手容易碰到的低级错误积累
JavaScript 输入框内容格式验证代码
JavaScript Event学习补遗 addEventSimple
jquery实现的提示浮层跟随鼠标移动
jQuery 添加/移除CSS类实现代码
jQuery 改变CSS样式基础代码
改善你的jQuery的25个步骤 千倍级效率提升
jquery 问答知识整理
Jquery iframe内部出滚动条
不同浏览器对回车提交表单的处理办法
Javascript 浏览器事件小结

JS教程:Javascript实现缓动效果


出处:互联网   整理: 软晨网(RuanChen.com)   发布: 2009-09-28   浏览: 390 ::
收藏到网摘: n/a

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

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

运行代码框

[Ctrl+A 全部选择 提示:你可先修改部分代码,再按运行]

为了获取它们在页面上的坐标与尺寸,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。其他类又分为三种。

  1. easeIn方法控制补间如何从开始到最高速度。
  2. easeOut 方法控制补间减速并停在目标位置
  3. easeInOut方法同时控制上述两者。

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

代码拷贝框

[Ctrl+A 全部选择 然后拷贝]

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

代码拷贝框

[Ctrl+A 全部选择 然后拷贝]

运行代码框

[Ctrl+A 全部选择 提示:你可先修改部分代码,再按运行]

除了这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>