你的位置:首页 > Java教程

[Java教程]JavaScript学习总结【11】、JS运动


  动画效果在网站中是一种非常常见的交互式体验效果,比如侧边栏分享、图片淡入淡出,我们把这种动画效果就叫做运动,也就是让物体动起来。如果想让一个物体动起来,无非就是改变它的速度,也就是改变属性值,比如 left、right、width、height、opacity ,那么既然是运动,就可以分为很多种,如匀速运动、缓冲运动、多物体运动、任意值运动、链式运动和同时运动。我们从最简单的动画开始,如何让单个物体运动,逐步深入多物体运动、多动画同时运动到实现完美运动框架的封装,在这个过程中,每一个运动都封装为一个函数,可以更好的培养和锻炼我们的编程思想,增强逻辑思维。

  1、简单运动

  简单运动的实现都是匀速运动,顾名思义就是运动速度不变,通过宽、高、透明度等的变化,实现简单的动画效果,下面我们就让一个 div 运动起来。

 1 <!DOCTYPE html> 2 <html> 3 <head> 4   <meta charset="UTF-8"> 5   <title>简单运动</title> 6 <style> 7 *{margin:0;padding:0;} 8 #div1{ 9   width:200px;10   height:200px;11   background:green;12   position:absolute;13 }14 </style>15 <script>16 function startMove(){17   var oDiv = document.getElementById('div1');18   setInterval(function (){19     oDiv.style.left = oDiv.offsetLeft + 10 + 'px';20   },30);21 }22 </script>23 </head>24 <body>25 <input type="button" value="动起来" onclick="startMove()">26 <div id="div1"></div>27 </body>28 </html>

  让一个 div 动起来,只需要开一个定时器,用于定义速度,告诉物体运动的快慢,上面的代码,当点击按钮后,div 每隔30毫秒从左向右运动10像素。

  这里需要注意,让 div 向右运动,在定义样式时,一定要给运动的物体加绝对定位,也就是相对于哪个位置进行运动,offsetLeft 代表物体的当前位置,所以每次运动,都是给当前的 offsetLeft 加10像素。

  虽然是让 div 动起来了,但是问题多多,动起来后根本停不下来,这样就太任性了,而且当重复点击的话,运动速度还会加快,这都不是我们想要的,下面就我们就让他停止在指定位置处。

 1 <!DOCTYPE html> 2 <html> 3 <head> 4   <meta charset="UTF-8"> 5   <title>简单运动</title> 6 <style> 7 *{margin:0;padding:0;} 8 #div1{ 9   width:200px;10   height:200px;11   background:green;12   position:absolute;13 }14 </style>15 <script>16 var timer = null;17 function startMove(){18   var oDiv = document.getElementById('div1');19   clearInterval(timer);20   timer = setInterval(function (){21     if(oDiv.offsetLeft == 300){22       clearInterval(timer);23     }24     else{25       oDiv.style.left = oDiv.offsetLeft + 10 + 'px';26     }27   },30);28 }29 </script>30 </head>31 <body>32 <input type="button" value="动起来" onclick="startMove()">33 <div id="div1"></div>34 </body>35 </html>

  上面的代码,点击按钮后,div 从左向右运动到300像素时停止运动。

  若要停止一个物体的运动,只要关闭定时器即可,也就是判断 div 的 offsetLeft 值是否等于300,若等于300则清空定时器。这里需要注意,在做判断时,一定要给运动位置加 else,判断语句为二选一,成立或者不成立时执行,这样再点击按钮就不会运动了,否则当 div 运动到300像素时,再点击按钮,div 还会向右移动10像素,虽然在到达300像素时已经关闭了定时器,但是按钮的点击事件,还会执行一次函数,所以加了 else 之后,当再点击按钮时,这时候条件已经成立了,也就不会再执行 else 中的语句了。

  重复点击按钮,运动速度会不断加快,是因为每点击一次按钮,startMove 函数被执行一次,多次点击,也就相当于开了多个定时器,所以需要在执行 startMove 函数时,首先清空定时器,这样重复点击按钮时,会先把之前运行的定时器关闭,再开一个新的定时器运行,这就保证了始终是一个定时器在工作。

  下面我们看两个简单动画的实例:

  实例:侧边栏分享

  实现思路:网站侧边栏菜单是最常见的动画效果,该效果在初始时,只显示一个按钮或者菜单项,当鼠标移上去时,滑出隐藏部分,展示内容。该效果在做布局时,主要用绝对定位实现隐藏,left 的值为内容容器 width 的值,该值为负值,动画效果实现就是改变它的 offsetLeft 的值,当鼠标移入时,增加 offsetLeft 的值,值为0时停止运动,当鼠标移开时,offsetLeft 的值从0减小到 left 的值。

 1 <!DOCTYPE html> 2 <html> 3 <head> 4   <meta charset="UTF-8"> 5   <title>侧边栏分享</title> 6 <style> 7 *{margin:0;padding:0;} 8 #div1{ 9   width:150px;10   height:210px;11   background:lightgreen;12   position:absolute;13   left:-150px;14 }15 #div1 span{16   width:20px;17   height:60px;18   line-height:20px;19   color:white;20   background:green;21   position:absolute;22   right:-20px;23   top:70px;24 }25 </style>26 <script>27 window.onload = function (){28   var oDiv = document.getElementById('div1');29   oDiv.onmouseover = function (){30     startMove();31   };32   oDiv.onmouseout = function (){33     stopMove();34   };35 };36 var timer = null;37 function startMove(){38   var oDiv = document.getElementById('div1');39   clearInterval(timer);40   timer = setInterval(function (){41     if(oDiv.offsetLeft == 0){42       clearInterval(timer);43     }44     else{45       oDiv.style.left = oDiv.offsetLeft + 10 + 'px';46     }47   },30);48 }49 function stopMove(){50   var oDiv = document.getElementById('div1');51   clearInterval(timer);52   timer = setInterval(function (){53     if(oDiv.offsetLeft == -150){54       clearInterval(timer);55     }56     else{57       oDiv.style.left = oDiv.offsetLeft - 10+ 'px';58     }59   },30);60 }61 </script>62 </head>63 <body>64 <div id="div1">65   <span>分享到</span>66 </div>67 </body>68 </html>

  上面的代码,当鼠标移入"分享到",隐藏的 div 即内容容器每隔30毫秒从左向右运动10像素,offsetLeft 值为0时停止运动,当鼠标移开时,显示的 div 每隔30毫秒从右向左移动5像素,offsetLeft 值为-150时停止运动。这里要注意的是,offsetLeft 值的变化,从左向右运动时,值为正值,也就是+10,从右向左运动时,值为负值,也就是-10。

  我们可以看到,startMove 和 stopMove 都有相同的代码结构,如果代码中存在大致相同的代码,就可以对代码进行优化,上面的代码,就只有 offsetLeft 的移动位置 和 停止位置不同,因此可以用函数传参的方式将上面的代码简化为:

 1 <script> 2 window.onload = function (){ 3   var oDiv = document.getElementById('div1'); 4   oDiv.onmouseover = function (){ 5     startMove(10, 0); 6   }; 7   oDiv.onmouseout = function (){ 8     startMove(-10, -150); 9   };10 };11 var timer = null;12 function startMove(speed, iTarget){13   var oDiv = document.getElementById('div1');14   clearInterval(timer);15   timer = setInterval(function (){16     if(oDiv.offsetLeft === iTarget){17       clearInterval(timer);18     }19     else{20       oDiv.style.left = oDiv.offsetLeft + speed + 'px';21     }22   },30);23 }24 </script>

  移动位置也就是速度,用 speed 参数传入,停止位置也就是目标位置,用 iTarget 参数传入。在功能相同的情况下,一个函数传入的参数越少越好,那么还可以简化为:

 1 <script> 2 window.onload = function (){ 3   var oDiv = document.getElementById('div1'); 4   oDiv.onmouseover = function (){ 5     startMove(0); 6   }; 7   oDiv.onmouseout = function (){ 8     startMove(-150); 9   };10 };11 var timer = null;12 function startMove(iTarget){13   var oDiv = document.getElementById('div1');14   clearInterval(timer);15   timer = setInterval(function (){16     var speed = 0;17     if(oDiv.offsetLeft > iTarget){18       speed = -10;19     }20     else{21       speed = 10;22     }23     if(oDiv.offsetLeft == iTarget){24       clearInterval(timer);25     }26     else{27       oDiv.style.left = oDiv.offsetLeft + speed + 'px';28     }29   },30);30 }31 </script>

  速度值和目标值,目标值肯定是不能省略的,就好比坐火车,买票肯定得有一个终点,所以速度值可以被省略掉,不管是动车还是普通车,都会到达终点,不同的就是速度的快慢。首先让速度值等于0,再做一个判断,判断目标值与物体当前位置的关系,如果 offsetLeft 值大于目标值,那么就要从右向左运动,所以为负值,否则,也就是目标值大于 offsetLeft 值,这说明此时物体是隐藏的,那么就从左向右运动,速度值为正值。

  该效果如果用缓冲运动做的话,效果更好。

  实例:淡入淡出

  淡入淡出效果是鼠标移入移出改变透明度,透明度动画也属于运动效果,可以使用上例中 startMove 框架完成这种效果。

 1 <!DOCTYPE html> 2 <html> 3 <head> 4   <meta charset="UTF-8"> 5   <title>淡入淡出</title> 6 <style> 7 #div1{ 8   width:200px; 9   height:200px;10   background:red;11   filter:alpha(opacity:30);12   opacity:0.3;13 }14 </style>15 <script>16 window.onload = function (){17   var oDiv = document.getElementById('div1');18   oDiv.onmouseover = function (){19     startMove(100);20   };21   oDiv.onmouseout = function (){22     startMove(30);23   };24 };25 var alpha = 30;  //将透明度值存储在变量中26 var timer = null;27 function startMove(iTarget){28   var oDiv = document.getElementById('div1');29   clearInterval(timer);30   timer = setInterval(function (){31     var speed = 0;32     if(alpha > iTarget){33       speed = -10;34     }35     else{36       speed = 10;37     }38     if(alpha == iTarget){39       clearInterval(timer);40     }41     else{42       alpha += speed;  //透明度值增加效果43       oDiv.style.opacity = alpha/100;  //高版本滤镜为:0.1-1,所以除以10044       oDiv.style.filter = 'alpha(opacity:'+ alpha +')';  //IE低版本浏览器使用45     }46   },30);47 }48 </script>49 </head>50 <body>51 <div id="div1"></div>52 </body>53 </html>

   改变透明度的值,可没有 offsetalpha 这样的写法,透明度值没有直接的属性可以改变,但是可以将透明度值存储在变量中,通过判断变量的值和目标值之间的关系,就可以确定速度值,有了速度值之后,再将这个速度值通过变量赋值给他,就可以达到改变透明度值的效果。

  

  2、缓冲运动

  匀速运动的速度始终都是不变的,而缓冲运动的速度则是逐渐变慢,最后停止的,就像火车一样,出站后速度都是很快的,距离目标位置越近,速度会逐渐变慢,到站后停止,也就是缓冲运动的速度是由距离决定的,速度和距离成正比的,距离越远,速度越大。

  那么,缓冲运动的速度就可以用这个公式表示:速度 = (目标值 - 当前值)/缩放系数

  何为缩放系数,为什么要除以缩放系数,我们看下面的实例。

 1 <!DOCTYPE html> 2 <html> 3 <head> 4   <meta charset="UTF-8"> 5   <title>缓冲运动</title> 6 <style> 7 #div1{ 8   width:100px; 9   height:100px;10   background:green;11   position:absolute;12   left:0;13   top:50px;14 }15 #div2{16   width:1px;17   height:300px;18   background:black;19   position:absolute;20   left:300px;21   top:0;22 }23 </style>24 <script>25 function startMove(){26   var oDiv = document.getElementById('div1');27   setInterval(function (){28     var speed = 300 - oDiv.offsetLeft;29     oDiv.style.left = oDiv.offsetLeft + speed + "px";30   },30);31 }32 </script>33 </head>34 <body>35 <input type="button" value="动起来" onclick="startMove()">36 <div id="div1"></div>37 <div id="div2">300px处</div>38 </body>39 </html>

   上面的代码,在点击按钮后,让 div 运动到300像素处停止,可以看到 div 直接就跳到300像素处了,他的运动速度等于目标位置减去当前位置,初始位置为0,所以点击按钮后,一瞬间就跳到300像素处了。

  速度太大了,就没有缓冲的效果了,所以要除以缩放系数,就是让他有一个缓冲的过程,我们给他除以10,初始位置0,点击按钮后,速度为30,当在最后靠近300像素处,速度就为0,距离越小,则速度就越小。

var speed = (300 - oDiv.offsetLeft)/10;

 

  这样虽然是达到了缓冲的效果,但是可以很明显的看到,div 并不是停在300像素处,还差那么一点,打开调试工具可以看到,此时的 left 值为296.4px,像素的最小单位是 px,我们平时写代码时,不可能这么写100.5px 或者 200.9px,出现这样的情况就是因为这句代码:oDiv.style.left = oDiv.offsetLeft + speed + "px",也就是 div 的 left 值是当前值加速度值,所以296就是当前的 left 值,296到300差4,再除以缩放系数10,就是0.4,因此 div 的 left 值就为296.4px,这并不是我们想要的,速度不能为小数,解决方法也很简单,那就是向上取整。

speed = Math.seil(speed);

 

  那么,问题又来了,如果 div 的初始值是在600像素处,这时候就是从左向右运动,速度小于0,也就是速度为负值,向上取整之后,离要运动到的300px处还差一点,这时候需要向下取整,但是向下取整之后,初始值为为0时,运动到300px处还是差一点,这时候就需要做判断,当速度大于0时,速度为正,向上取整,如果小于0时,速度为负,则向下取整。

 1 <!DOCTYPE html> 2 <html> 3 <head> 4   <meta charset="UTF-8"> 5   <title>缓冲运动</title> 6 <style> 7 #div1{ 8   width:100px; 9   height:100px;10   background:green;11   position:absolute;12   left:0;13   top:50px;14 }15 #div2{16   width:1px;17   height:300px;18   background:black;19   position:absolute;20   left:300px;21   top:0;22 }23 </style>24 <script>25 function startMove(){26   var oDiv = document.getElementById('div1');27   setInterval(function (){28     var speed = (300 - oDiv.offsetLeft)/10;29     speed = speed>0 ? Math.ceil(speed) : Math.floor(speed);30     oDiv.style.left = oDiv.offsetLeft + speed + "px";31   },30);32 }33 </script>34 </head>35 <body>36 <input type="button" value="动起来" onclick="startMove()">37 <div id="div1"></div>38 <div id="div2">300px处</div>39 </body>40 </html>

  

  这里一定要注意,缓冲运动一定要取整,否则就到不了目标位置。下面是一个用缓冲运动做的侧边栏分享效果。

 

 1 <!DOCTYPE html> 2 <html> 3 <head> 4   <meta charset="UTF-8"> 5   <title>缓冲分享</title> 6 <style> 7 *{margin:0;padding:0;} 8 #div1{ 9   width:150px;10   height:210px;11   background:lightgreen;12   position:absolute;13   left:-150px;14 }15 #div1 span{16   width:20px;17   height:60px;18   line-height:20px;19   color:white;20   background:green;21   position:absolute;22   right:-20px;23   top:70px;24 }25 </style>26 <script>27 window.onload = function (){28   var oDiv = document.getElementById('div1');29   oDiv.onmouseover = function (){30     startMove(0);31   };32   oDiv.onmouseout = function (){33     startMove(-150);34   };35 };36 var timer = null;37 function startMove(iTarget){38   var oDiv = document.getElementById('div1');39   clearInterval(timer);40   timer = setInterval(function (){41     var speed = (iTarget - oDiv.offsetLeft)/10;42     speed = speed>0 ? Math.ceil(speed) : Math.floor(speed);43     if(oDiv.offsetLeft == iTarget){44       clearInterval(timer);45     }46     else{47       oDiv.style.left = oDiv.offsetLeft + speed + 'px';48     }49   },30);50 }51 </script>52 </head>53 <body>54 <div id="div1">55   <span>分享到</span>56 </div>57 </body>58 </html>

 

  缓冲运动的停止条件是当两点重合时,也就是物体当前的 left 值等于目标值,而匀速运动的停止条件是距离足够近时,也就是物体当前的 left 值大于或小于目标值,再强行让他的 left 值等于目标值,下面我们通过一个实例来更好的理解。

 1 <!DOCTYPE html> 2 <html> 3 <head> 4   <meta charset="UTF-8"> 5   <title>运动的停止</title> 6 <style> 7 #div1{ 8   width:100px; 9   height:100px;10   background:green;11   position:absolute;12   left:600px;13   top:50px;14 }15 #div2{16   width:1px;17   height:300px;18   background:black;19   position:absolute;20   left:200px;21   top:0;22 }23 #div3{24   width:1px;25   height:300px;26   background:black;27   position:absolute;28   left:400px;29   top:0;30 }31 </style>32 <script>33 var timer = null;34 function startMove(iTarget){35   var oDiv = document.getElementById('div1');36   clearInterval(timer);37   timer = setInterval(function (){38     var speed = 0;39     if(oDiv.offsetLeft > iTarget){40       speed = -7;41     }42     else{43       speed = 7;44     }45     if(Math.abs(iTarget - oDiv.offsetLeft) <= 7){46       clearInterval(timer);47       oDiv.style.left = iTarget + 'px';48     }49     else{50       oDiv.style.left = oDiv.offsetLeft + speed + "px";51     }52   },30);53 }54 </script>55 </head>56 <body>57 <input type="button" value="到200" onclick="startMove(200)">58 <input type="button" value="到400" onclick="startMove(400)">59 <div id="div1"></div>60 <div id="div2">200px处</div>61 <div id="div3">400px处</div>62 </body>63 </html>

  上面的代码,点击 到200 按钮,div 从右向左每隔30毫秒运动7像素,速度为负,运动到200像素处停止,再点击 到400 按钮,div 从左向右每隔30毫秒运动7像素,速度为正,运动到400像素处停止。匀速运动的速度始终是保持不变的,这里的运动速度为7,初始位置运动到目标位置的移动速度除不尽,这样就会出现左右徘徊的情况,也就是 div 在目标点左右闪动,原因是前进一个7,比目标位置大了,后退一个7,比目标位置小了,所有会出现这种现象。解决办法就是使用绝对值进行判断,因为他的距离可能是正7,也可能是负7。那么使用绝对值做判断,目标位置减去物体当前位置若小于等于7,就算到了,这时候问题就来了,虽然到了不闪动了,但是可以很明显看到还有一点距离,所以最关键的一步,就是在清空定时器后,直接让 div 的 left 值等于目标点的值,这样就完成了匀速运动的停止。

  

  3、多物体运动和任意值运动

  (1)、多物体运动

  我们之前做的都是单个物体的运动,而在网站中,并不是单个物体在运动,而是多个物体的运动,下面我们就套用之前的 startMove 框架让多个 div 运动起来。

 

 1 <!DOCTYPE html> 2 <html> 3 <head> 4   <meta charset="UTF-8"> 5   <title>多个div变宽</title> 6 <style> 7   div{ 8     width:100px; 9     height:50px;10     background:green;11     margin:10px;12   }13 </style>14 <script>15 window.onload = function (){16   var aDiv = document.getElementsByTagName('div');17   for(var i=0; i<aDiv.length; i++){18     aDiv[i].onmouseover = function (){19       startMove(this,400);20     };21     aDiv[i].onmouseout = function (){22       startMove(this,100);23     };24   }25 };26 var timer = null;27 function startMove(obj,iTarget){28   clearInterval(timer);29   timer = setInterval(function (){30     var speed=(iTarget - obj.offsetWidth)/6;31     speed = speed>0 ? Math.ceil(speed) : Math.floor(speed);32 33     if(obj.offsetWidth == iTarget){34       clearInterval(timer);35     }36     else{37       obj.style.width = obj.offsetWidth + speed + 'px';38     }39   },30);40 }41 </script>42 </head>43 <body>44 <div></div>45 <div></div>46 <div></div>47 </body>48 </html>

 

  上面的代码,有3个 div,在鼠标移入时宽从100像素运动到400像素,而当鼠标移出后从400像素运动回100像素,因为是多个物体的运动,因此要使用 for 循环让每个物体都获得事件,那么要确定当前获得事件的物体,就需要再传入一个参数 this,我们不能确定当前是哪个物体获得事件,所以使用 this 指向,但是我们之前写的 startMove 框架只有一个参数,而多物体的运动传入了2个参数,因为要确定当前运动的物体,所以只要再给 startMove 传入一个参数 obj 就好了。那么多物体的运动框架,就是再传入一个参数,这样就能获取当前运动的物体了。

  这样就算做完了吗?肯定没有,当你鼠标同时滑过3个 div 时,就出事了,会发现变宽之后变不回来了,这是因为定时器,3个 div 只用一个定时器,虽然在刚开始运行时,我们就清空了定时器,但只是清空了整个定时器,不知道清空的是谁的,这样就造成了混乱,肯定就出错了,所以要给每个 div 都设置一个定时器,我们知道可以给对象添加一个自定义属性设置索引号 aDiv[i].index = 0,那么就可以把定时器也作为物体的属性 aDiv[i].timer=null 。

 1 <!DOCTYPE html> 2 <html> 3 <head> 4   <meta charset="UTF-8"> 5   <title>多个div变宽</title> 6 <style> 7   div{ 8     width:100px; 9     height:50px;10     background:green;11     margin:10px;12   }13 </style>14 <script>15 window.onload = function (){16   var aDiv = document.getElementsByTagName('div');17   for(var i=0; i<aDiv.length; i++){18     aDiv[i].timer = null;19     aDiv[i].onmouseover = function (){20       startMove(this,400);21     };22     aDiv[i].onmouseout = function (){23       startMove(this,100);24     };25   }26 };27 function startMove(obj,iTarget){28   clearInterval(obj.timer);29   obj.timer = setInterval(function (){30     var speed=(iTarget - obj.offsetWidth)/6;31     speed = speed>0 ? Math.ceil(speed) : Math.floor(speed);32 33     if(obj.offsetWidth == iTarget){34       clearInterval(obj.timer);35     }36     else{37       obj.style.width = obj.offsetWidth + speed + 'px';38     }39   },30);40 }41 </script>42 </head>43 <body>44 <div></div>45 <div></div>46 <div></div>47 </body>48 </html>

 

  下面再看一个多个 div 淡入淡出的实例。

 1 <!DOCTYPE html> 2 <html> 3 <head> 4   <meta charset="UTF-8"> 5   <title>多个div淡入淡出</title> 6 <style> 7 div{ 8   width:200px; 9   height:200px;10   background:red;11   float:left;12   margin:20px;13   filter:alpha(opacity:30);14   opacity:0.3;15 }16 </style>17 <script>18 window.onload = function (){19   var aDiv = document.getElementsByTagName('div');20   for(var i=0; i<aDiv.length; i++){21     aDiv[i].onmouseover = function (){22       startMove(this,100);23     };24     aDiv[i].onmouseout = function (){25       startMove(this,30);26     };27   }28 };29 var alpha = 30;30 function startMove(obj,iTarget){31   clearInterval(obj.timer);32   obj.timer = setInterval(function (){33     var speed=(iTarget - alpha)/6;34     speed = speed>0 ? Math.ceil(speed) : Math.floor(speed);35 36     if(alpha == iTarget){37       clearInterval(obj.timer);38     }39     else{40       alpha += speed;41       obj.style.opacity = alpha/100;42       obj.style.filter = 'alpha(opacity: '+ alpha +')';43     }44   },30);45 }46 </script>47 </head>48 <body>49 <div></div>50 <div></div>51 <div></div>52 <div></div>53 </body>54 </html>

  上面的代码,4个 div 初始的透明度都为0.3,在鼠标移入后透明度变为1,当鼠标移出后变回0.3,单个移入移出都没有问题,但是当快速移动鼠标时,又出事了,并没有达到我们预期的效果,我们给当前的物体都设置了定时器,但为什么还会出现这种情况呢?其实这并不是定时器的问题,而是用于存储透明度的变量 alpha 导致的,4个 div 公用了一个滤镜,就导致了混乱,从这我们可以看出,凡是多物体运动,所有东西都不能公用,也就是要改变的属性,必须给每个物体设置,要把属性和运动东西绑定。

 1 <!DOCTYPE html> 2 <html> 3 <head> 4   <meta charset="UTF-8"> 5   <title>多个div淡入淡出</title> 6 <style> 7 div{ 8   width:200px; 9   height:200px;10   background:red;11   float:left;12   margin:20px;13   filter:alpha(opacity:30);14   opacity:0.3;15 }16 </style>17 <script>18 window.onload = function (){19   var aDiv = document.getElementsByTagName('div');20   for(var i=0; i<aDiv.length; i++){21     aDiv[i].alpha = 30;22     aDiv[i].onmouseover = function (){23       startMove(this,100);24     };25     aDiv[i].onmouseout = function (){26       startMove(this,30);27     };28   }29 };30 function startMove(obj,iTarget){31   clearInterval(obj.timer);32   obj.timer = setInterval(function (){33     var speed=(iTarget - obj.alpha)/6;34     speed = speed>0 ? Math.ceil(speed) : Math.floor(speed);35 36     if(obj.alpha == iTarget){37       clearInterval(obj.timer);38     }39     else{40       obj.alpha += speed;41       obj.style.opacity = obj.alpha/100;42       obj.style.filter = 'alpha(opacity: '+ obj.alpha +')';43     }44   },30);45 }46 </script>47 </head>48 <body>49 <div></div>50 <div></div>51 <div></div>52 <div></div>53 </body>54 </html>

 

  (2)、任意值运动

  之前我们都做的是物体的单一运动,都是改变宽度或者改变透明度,在网站中,也肯定不只是单一的运动,有可能是变宽,也有可能是变高,下面我们就看一下 div 的变宽和变高。

 1 <!DOCTYPE html> 2 <html> 3 <head> 4   <meta charset="UTF-8"> 5   <title>div变宽和变高</title> 6 <style> 7 div{ 8   width:100px; 9   height:50px;10   background:green;11   margin-bottom:10px;12 }13 </style>14 <script>15 window.onload = function (){16   var oDiv1 = document.getElementById('div1');17   var oDiv2 = document.getElementById('div2');18 19   oDiv1.onmouseover = function (){20     startMove(this,400);21   };22   oDiv1.onmouseout = function (){23     startMove(this,100);24   };25 26   oDiv2.onmouseover = function (){27     startMove2(this,400);28   };29   oDiv2.onmouseout = function (){30     startMove2(this,50);31   };32 };33 function startMove(obj,iTarget){34   clearInterval(obj.timer);35   obj.timer = setInterval(function (){36     var speed = (iTarget - obj.offsetWidth)/6;37     speed = speed>0 ? Math.ceil(speed) : Math.floor(speed);38     if(obj.offsetWidth == iTarget){39       clearInterval(obj.timer);40     }41     else{42       obj.style.width = obj.offsetWidth + speed + 'px';43     }44   },30);45 }46 function startMove2(obj,iTarget){47   clearInterval(obj.timer);48   obj.timer = setInterval(function (){49     var speed = (iTarget - obj.offsetHeight)/6;50     speed = speed>0 ? Math.ceil(speed) : Math.floor(speed);51     if(obj.offsetHeight == iTarget){52       clearInterval(obj.timer);53     }54     else{55       obj.style.height = obj.offsetHeight + speed + 'px';56     }57   },30);58 }59 </script>60 </head>61 <body>62 <div id="div1">变宽</div>63 <div id="div2">变高</div>64 </body>65 </html>

   上面的代码,div1 在鼠标移入时宽度从100像素运动到400像素,移出后运动回100像素,div2 在鼠标移入时高度从50像素运动到400像素,移出后运动回50像素,我们只要调用2个 startMove 框架就可以很轻松的完成这样的效果,但是代码显得十分繁琐,而且这2个框架的功能是完全相同的,唯一不同的就是一个改变宽度,一个改变高度,之前我们说过,对于功能完全相同的代码,可以对代码进行简化,把不同的东西作为参数传入,这样就可以得到一个任意值的运动框架。

  在这之前,我们先来看一下 offset 这个属性,前边我们在做运动时,只给物体进行了简单的样式定义,宽、高和背景,但在实际的开发中,或许还有其他的样式属性,比如内外边距或者边框等,那如果在定义一个边框属性,会如何呢?看下面的实例:

 1 <!DOCTYPE html> 2 <html> 3 <head> 4   <meta charset="UTF-8"> 5   <title>offset属性</title> 6 <style> 7 #div1{ 8   width:200px; 9   height:200px;10   background:red;11   border:1px solid black;12 }13 </style>14 <script>15 setInterval(function (){16   var oDiv = document.getElementById('div1');17   oDiv.style.width = oDiv.offsetWidth - 1 + 'px';18 },30);19 </script>20 </head>21 <body>22 <div id="div1">变窄</div>23 </body>24 </html>

  上面的代码,我们给 div 加了1像素的边框,并且让他每隔30毫秒从右向左运动1像素,速度为负,也就是 offsetWidth - 1,本应该打开 demo 之后,会看到 div 会逐渐变窄,但是事与愿违,div 并没有逐渐变窄,反而逐渐变宽了,如果我们不加边框的话,div 肯定会逐渐变窄,这就是 offset 属性的一个小 bug,在设置了边框后,当前的宽度就要加上边框的宽度,实际上 offsetWidth 的值就为202,计算后为201并赋值给 width,当下一次运行时 width 的值就为201,再加上边框值 offsetWidth 的值则为203,计算后为202并赋值给 width,一次类推,所以就会出现逐渐变宽的现象。

  要解决这个 bug,就是不使用 offset 属性,最直接的办法就是将 width 属性放在行间,style.width 值只考虑本身的 width,而不考虑边框或者内边距及其他的影响因素,但是 style 仅仅只能取行间样式,如果是写在内部样式表或外部样式表,就没法用了,并且这也不符合 W3C 结构和表现相分离的原则,所以该方法是不可取的,但是我们可以看以下他是怎么实现的。

 1 <!DOCTYPE html> 2 <html> 3 <head> 4   <meta charset="UTF-8"> 5   <title>offset的bug</title> 6 <style> 7 #div1{ 8   height:200px; 9   background:red;10   border:1px solid black;11 }12 </style>13 <script>14 setInterval(function (){15   var oDiv = document.getElementById('div1');16   oDiv.style.width = parseInt(oDiv.style.width) - 1 + 'px';17 },30);18 </script>19 </head>20 <body>21 <div id="div1" style="width:200px">变窄</div>22 </body>23 </html>

  上面的代码,我们将 div 的 width 属性写在了行间,通过 oDiv.style.width 获取,再使用 parseInt 函数将字符串转换为一个整数,再减去速度,这样问题就解决了。

  最佳的解决方法,就是获取非行间样式,我们可以封装一个 getStyle 函数,再使用时传入相应的参数。

 1 <!DOCTYPE html> 2 <html> 3 <head> 4   <meta charset="UTF-8"> 5   <title>offset的bug</title> 6 <style> 7 #div1{ 8   width:200px; 9   height:200px;10   background:red;11   border:1px solid black;12 }13 </style>14 <script>15 function getStyle(obj,name){16   if(obj.currentStyle){17     return obj.currentStyle[name];18   }19   else{20     return getComputedStyle(obj,false)[name];21   }22 }23 setInterval(function (){24   var oDiv = document.getElementById('div1');25   oDiv.style.width = parseInt(getStyle(oDiv,'width')) - 1 + 'px';26 },30);27 </script>28 </head>29 <body>30 <div id="div1">变窄</div>31 </body>32 </html>

   getStyle 函数有2个参数,第一个参数 obj 为要获取的对象,第二个参数 name 为要获取的属性,并且做了兼容处理,currentStyle 针对 IE 浏览器,getComputedStyle 针对火狐浏览器。

  既然 offset 这个属性存在 bug,那么再做动画效果时就不能使用该属性了,用获取非行间样式的方法来做,下面我们就用这个方法来做 div 的变宽和变高。

 1 <!DOCTYPE html> 2 <html> 3 <head> 4   <meta charset="UTF-8"> 5   <title>div变宽和变高</title> 6 <style> 7 div{ 8   width:100px; 9   height:50px;10   background:green;11   margin-bottom:10px;12   border:5px solid black;13 }14 </style>15 <script>16 window.onload = function (){17   var oDiv = document.getElementById('div1');18   var oDiv2 = document.getElementById('div2');19   oDiv.onmouseover = function (){20     startMove(this,400);21   };22   oDiv.onmouseout = function (){23     startMove(this,100);24   };25 26   oDiv2.onmouseover = function (){27     startMove2(this,400);28   };29   oDiv2.onmouseout = function (){30     startMove2(this,50);31   };32 };33 function getStyle(obj,name){34   if(obj.currentStyle){35     return obj.currentStyle[name];36   }37   else{38     return getComputedStyle(obj,false)[name];39   }40 }41 function startMove(obj,iTarget){42   clearInterval(obj.timer);43   obj.timer = setInterval(function (){44     var cur = parseInt(getStyle(obj,'width'));45     var speed = (iTarget - cur)/6;46     speed = speed>0 ? Math.ceil(speed) : Math.floor(speed);47     if(cur == iTarget){48       clearInterval(obj.timer);49     }50     else{51       obj.style['width'] = cur + speed + 'px';52     }53   },30);54 }55 function startMove2(obj,iTarget){56   clearInterval(obj.timer);57   obj.timer = setInterval(function (){58     var cur = parseInt(getStyle(obj,'height'));59     var speed = (iTarget - cur)/6;60     speed = speed>0 ? Math.ceil(speed) : Math.floor(speed);61     if(cur == iTarget){62       clearInterval(obj.timer);63     }64     else{65       obj.style['height'] = cur + speed + 'px';66     }67   },30);68 }69 </script>70 </head>71 <body>72 <div id="div1">变宽</div>73 <div id="div2">变高</div>74 </body>75 </html>

   上面的代码,我们给 div 设置了5像素的边框,鼠标移入移出变宽和变高都是没问题的,为了代码的简洁,我们将获取物体当前属性值的代码保存在一个变量中,var cur = parseInt(getStyle(obj,'width')),也方便之后调用,我们知道 obj.style.width 也可以写为 obj.style['width'],只是后者写起来比较麻烦,平时大家不这样写,此处这样写,可以更好的说明问题,仔细观察代码,原本的框架只能让某个值运动起来,如果想要其他值运动起来,就必须修改程序,2个 startMove 框架结构是完全相同的,就只有传入的属性值不同,那么我们就可以进行代码优化了,将属性值作为参数传入  startMove 框架中,在使用时,传入什么属性,就可以改变什么属性,这就是任意值运动框架。

  下面我们就检测以下我们的任意值运动框架,做一个滤镜效果,div 的淡入淡出。

 1 <!DOCTYPE html> 2 <html> 3 <head> 4   <meta charset="UTF-8"> 5   <title>div淡入淡出</title> 6 <style> 7 div{ 8   width:200px; 9   height:200px;10   background:red;11   border:1px solid black;12   filter:alpha(opacity:30);13   opacity:0.3;14 }15 </style>16 <script>17 window.onload = function (){18   var oDiv = document.getElementById('div1');19   oDiv.onmouseover = function (){20     startMove(this,'opacity',100);21   };22   oDiv.onmouseout = function (){23     startMove(this,'opacity',30);24   };25 };26 function getStyle(obj,name){27   if(obj.currentStyle){28     return obj.currentStyle[name];29   }30   else{31     return getComputedStyle(obj,false)[name];32   }33 }34 function startMove(obj,attr,iTarget){35   clearInterval(obj.timer);36   obj.timer = setInterval(function (){37     var cur = parseFloat(getStyle(obj,attr))*100;38     var speed = (iTarget - cur)/10;39     speed = speed>0 ? Math.ceil(speed) : Math.floor(speed);40     if(cur == iTarget){41       clearInterval(obj.timer);42     }43     else{44       obj.style.opacity = (cur+speed)/100;45       obj.style.filter = "alpha(opacity: '+ (cur + speed) +')";46       document.getElementById('txt1').value = obj.style.opacity;47     }48   },30);49 }50 </script>51 </head>52 <body>53 <input type="text" id="txt1">54 <div id="div1"></div>55 </body>56 </html>

  上面的代码,div 初始透明度为0.3,鼠标移入时变为1,鼠标移开后恢复,还定义了一个文本框,用于显示 div 的透明度值,这里需要注意,透明度的值为小数,所以要使用 parseFloat 函数,将字符串转换为浮点数,而不是使用 parseInt 函数,他用于将字符串转换为整数,最后为了便于计算,在给这个数值乘以100。注意观察文本框显示的属性值,在鼠标移入时,显示为1.00999等一长串数字,而当鼠标移开后,透明度的值一直在0.300001到0.29000001之间徘徊,怎么会出现这情况呢?那是因为计算机存储的小数是一个近似值,所以会有误差,看下面的实例。

1 <script>2 alert(0.03*100);  //返回:33 alert(0.05*100);  //返回:54 alert(10/3);    //返回:3.33……355 alert(0.07*100);  //返回:7.00……016 </script>

  上面的代码,计算0.03*100和0.05*100,结果为3和5,计算结果正确,而计算 10/3 小数点后最后一位为5,更离谱的是计算0.07*100,结果居然不是7,这误差也太大了,因此不建议进行小数点计算,所以解决上面问题的方法,就是取浮点数之后再进行四舍五入。

  那么再观察上面改变透明度的代码,跟之前改变宽和高的代码,虽然使用的框架是相同的,但是结构是不相同的,也就是不能使用改变宽高的代码来进行改变透明度,那这岂不是任意值的运动框架了,要怎么办呢?很简单,对透明度特殊对待,也就是使用判断,如果要改变的属性为透明度,那么就使用改变透明度的方法,否则就是改变其他属性,那就使用改变宽高的方法。

  下面我们看一下任意值运动的综合实例。

 1 <!DOCTYPE html> 2 <html> 3 <head> 4   <meta charset="UTF-8"> 5   <title>任意值运动框架</title> 6 <style> 7 #div1{ 8   filter:alpha(opacity:30); 9   opacity:0.3;10 }11 div{12   width:100px;13   height:50px;14   background:red;15   margin-bottom:10px;16   border:1px solid black;17   font-size:14px;18 }19 </style>20 <script>21 window.onload = function (){22   var oDiv = document.getElementById('div1');23   var oDiv2 = document.getElementById('div2');24   var oDiv3 = document.getElementById('div3');25   var oDiv4 = document.getElementById('div4');26   oDiv.onmouseover = function (){27     startMove(this,'opacity',100);28   };29   oDiv.onmouseout = function (){30     startMove(this,'opacity',30);31   };32   oDiv2.onmouseover=function (){33     startMove(this,'fontSize',24);34   };35   oDiv2.onmouseout=function (){36     startMove(this,'fontSize',12);37   };38   oDiv3.onmouseover = function (){39     startMove(this,'width',400);40   };41   oDiv3.onmouseout = function (){42     startMove(this,'width',100);43   };44   oDiv4.onmouseover = function (){45     startMove(this,'height',400);46   };47   oDiv4.onmouseout = function (){48     startMove(this,'height',50);49   };50 };51 function getStyle(obj,name){52   if(obj.currentStyle){53     return obj.currentStyle[name];54   }55   else{56     return getComputedStyle(obj,false)[name];57   }58 }59 function startMove(obj,attr,iTarget){60   clearInterval(obj.timer);61   obj.timer = setInterval(function (){62     var cur = 0;  //用于存储物体当前的属性,方便之后调用63     if(attr == 'opacity'){64       cur = Math.round(parseFloat(getStyle(obj,attr))*100);65     }66     else{67       cur = parseInt(getStyle(obj,attr));68     }69     var speed = (iTarget - cur)/10;70     speed = speed>0 ? Math.ceil(speed) : Math.floor(speed);71     if(cur == iTarget){72       clearInterval(obj.timer);73     }74     else{75       if(attr == 'opacity'){76         obj.style.opacity = (cur+speed)/100;77         obj.style.filter = "alpha(opacity: '+ (cur + speed) +')";78       }79       else{80         obj.style[attr] = cur + speed + "px";81       }82     }83   },30);84 }85 </script>86 </head>87 <body>88 <div id="div1">改变透明度</div>89 <div id="div2">改变文字大小</div>90 <div id="div3">改变宽度</div>91 <div id="div4">改变高度</div>92 </body>93 </html>

  上面的代码,不只改变了 div 的透明度,也改变了宽度和高高度,还改变了字体的大小,当然也可以使用该框架改变他的边框等其他属性。

  

  4、链式运动

  所谓链式运动,就像链条一样,一个扣一个,当一个运动结束后,开始下一个运动,要实现这样的效果,只需要放一个回调函数,也就是在运动停止时,执行的函数,再调用一次 startMove 框架,开始下一次运动,那这样就好办了,既然还是使用使用之前的框架,那么就再给他传入一个参数,这样就可以完成链式运动,下面我们就来看一下在代码中具体是怎么实现的。

 1 <!DOCTYPE html> 2 <html> 3 <head> 4   <meta charset="UTF-8"> 5   <title>链式运动框架</title> 6 <style> 7 div{ 8   width:100px; 9   height:100px;10   background:red;11   filter:alpha(opacity:30);12   opacity:0.3;13 }14 </style>15 <script>16 window.onload = function (){17   var oDiv = document.getElementById('div1');18   oDiv.onmouseover = function (){19     startMove(oDiv,'width',300,function(){20       startMove(oDiv,'height',300,function (){21         startMove(oDiv,'opacity',100)22       });23     });24   };25   oDiv.onmouseout = function (){26     startMove(oDiv,'opacity',30,function (){27       startMove(oDiv,'height',100,function (){28         startMove(oDiv,'width',100);29       });30     });31   };32 };33 function getStyle(obj,name){34   if(obj.currentStyle){35     return obj.currentStyle[name];36   }37   else{38     return getComputedStyle(obj,false)[name];39   }40 }41 function startMove(obj,attr,iTarget,fn){42   clearInterval(obj.timer);43   obj.timer = setInterval(function (){44     var cur = 0;45     if(attr == 'opacity'){46       cur = Math.round(parseFloat(getStyle(obj,attr))*100);47     }48     else{49       cur = parseInt(getStyle(obj,attr));50     }51     var speed = (iTarget - cur)/10;52     speed = speed>0 ? Math.ceil(speed) : Math.floor(speed);53     if(cur == iTarget){54       clearInterval(obj.timer);55       if(fn)fn();56     }57     else{58       if(attr == 'opacity'){59         obj.style.opacity = (cur+speed)/100;60         obj.style.filter = "alpha(opacity: '+ (cur + speed) +')";61       }62       else{63         obj.style[attr] = cur + speed + "px";64       }65     }66   },30);67 }68 </script>69 </head>70 <body>71 <div id="div1"></div>72 </body>73 </html>

  上面的代码,div 的初始透明度为0.3,宽100px 高100px,鼠标移入后,div 的宽度先从100px 运动到300px 处停止,然后高度再从100px 运动到300px 处停止,最后透明度从0.3变为1,当鼠标移开后,以相反的顺序运动,即 div 的透明度先从1变回0.3,然后高度从300px 运动回100px,最后宽度从300px 运动回100px,这就是一个简易的链式运动。

  观察上面的代码,我们要完成链式运动,那么当一次运动结束后,就需要再执行一次 startMove 框架,那么再传入一个 fn 参数,fn 就是一个函数,当执行完一段代码之后,没有马上结束,而是再调用一次这个函数,再执行一个新的程序。这里最需要注意的是,当传入一个参数进来后,需要检测运动停止,在运动结束时清空定时器后,就要判断一下,if(fn)fn(),只有当这个函数传进来再调用,也就是第一次运动停止后,如果有这个 fn,也就是传入了一个 fn,那么就让这个 fn 执行一次,如果没有最后一个参数,那么就不需要再往下执行,运动就停止了。

  

  5、同时运动

  我们刚封装了链式运动框架,就是一个扣着一个,div 先变宽完成之后再变高,最后再改变透明度,这些都是单一的运动效果,即每一次运动都只能改变一个属性,我们在某些网站应都见过这么一种效果,当鼠标移入时图片的宽度和高度会同时发生变化,这样一种效果就是多物体动画同时运动效果,也可以叫做多值运动。

  那要怎么完成这种动画效果呢?可以先要之前封装好的运动框架尝试一下,既然是同时改变 div 的宽和高,那么就不能像做链式运动那样,传入一个回调函数,当某一个值运动结束后再运动下一个值,这样就成了链式运动,而不是同时运动,那如果定义两次呢,先来看一下。

 1 <!DOCTYPE html> 2 <html> 3 <head> 4   <meta charset="UTF-8"> 5   <title>现有框架的问题</title> 6 <style> 7 div{ 8   width:100px; 9   height:100px;10   background:red;11 }12 </style>13 <script>14 window.onload = function (){15   oBtn = document.getElementById('btn1');16   oDiv = document.getElementById('div1');17   oBtn.onclick = function (){18     startMove(oDiv,'width',300);19     startMove(oDiv,'height',300);20   };21 };22 function getStyle(obj,name){23   if(obj.currentStyle){24     return obj.currentStyle[name];25   }26   else{27     return getComputedStyle(obj,false)[name];28   }29 }30 function startMove(obj,attr,iTarget,fn){31   clearInterval(obj.timer);32   obj.timer = setInterval(function (){33     var cur = 0;34     if(attr == 'opacity'){35       cur = Math.round(parseFloat(getStyle(obj,attr))*100);36     }37     else{38       cur = parseInt(getStyle(obj,attr));39     }40     var speed = (iTarget - cur)/10;41     speed = speed>0 ? Math.ceil(speed) : Math.floor(speed);42     if(cur == iTarget){43       clearInterval(obj.timer);44       if(fn)fn();45     }46     else{47       if(attr == 'opacity'){48         obj.style.opacity = (cur+speed)/100;49         obj.style.filter = "alpha(opacity: '+ (cur + speed) +')";50       }51       else{52         obj.style[attr] = cur + speed + "px";53       }54     }55   },30);56 }57 </script>58 </head>59 <body>60 <input id="btn1" type="button" value="运动">61 <div id="div1"></div>62 </body>63 </html>

  上面的代码,div 的初始宽高都为100px,我们同时定义了让 div 的宽和高都运动到300px,点击运动按钮,会发现 div 的高度从100px 运动到300px 了,而宽并没有改变。想要同时改变 div 的宽和高,而 attr 参数只能传一个,如果定义两次,系统会执行后边的参数设置,因为执行 startMove 前先清空计时器,刚清空了计时器之后,后便的传参就会立马执行,也就是前边的一个参数被覆盖了,这是现有框架的一个小问题,不能让几个值同时运动,要解决这个问题,可以使用 JSON 完成,现在我们先回忆一下 JSON。

1 <script>2 var json = {a:5,b:12};3 for(var i in json){4   alert(i + ' = ' + json[i]);5 }6 </script>

  JSON 是一种轻量级的数据交互格式,JSON 语法是 JS 对象表示语法的一个子集,数据在键值对中,是以对值出现的,即 名称/值,名称和值由冒号连接,并用逗号分隔,花括号保存对象,方括号保存数组,JSON 值可以是:数字(整数或浮点数)、字符串(包含在双引号中)、对象(包含在花括号中)、数组(包含在方括号中)、逻辑值(true 或 false)、null。可以使用 for in 循环遍历 JSON 中的数据,var i in json ,i 是定义的变量,in 就是在什么里,那就是在 json 里循环遍历,上面的代码,先弹出 a = 5,再弹出 b = 12,那么 i 就表示 json 中的名称,而 json[i] 就表示 json 中某项的值,也就是 json 中变量 i 所对应的值。

  了解了 JSON 之后,那么我们如何使用 JSON 完成同时运动呢,现有的运动框架有4个参数,分别是 obj(对象)、attr(属性)、iTarget(目标)、fn(回调函数),在这4个参数中,attr 和 iTarget 也就是属性值和目标值是一对值,属性就是 json 中的名称,让谁做运动,目标就是值,到哪个位置时结束运动。那么就是说现有的运动框架,只能改变一对值,而不能实现多对值的变化,如果要实现多对值的变化,就需要用到 JSON 格式,因此就可以把 startMove 写为 startMove(obj, {attr1 : iTarget1, attr2 : iTarget1}, fn),把参数中的这一对值变成 JSON 的格式,用函数传参的方式就写为 startMove(obj, json, fn)。

  使用了 JSON 之后,还需要把 JSON 中的数据循环遍历出来,再配合 for in 循环,那要怎样循环呢,之前的运动框架,先要开启定时器,然后取当前的值,再计算速度,最后检测停止,完成这一系列的过程,当前物体触发的运动才算整个完成,那现在我们要完成多对值同时触发运动,就是让现在这整个过程多做几次循环就可以了,下面我们再来看一下代码的实现。

 1 <!DOCTYPE html> 2 <html> 3 <head> 4   <meta charset="UTF-8"> 5   <title>同时运动</title> 6 <style> 7 div{ 8   width:100px; 9   height:100px;10   background:red;11 }12 </style>13 <script>14 window.onload = function (){15   oBtn = document.getElementById('btn1');16   oDiv = document.getElementById('div1');17   oBtn.onclick = function (){18     startMove(oDiv, {width:300, height:300});19   };20 };21 function getStyle(obj,name){22   if(obj.currentStyle){23     return obj.currentStyle[name];24   }25   else{26     return getComputedStyle(obj,false)[name];27   }28 }29 function startMove(obj, json, fn){30   clearInterval(obj.timer);31   obj.timer = setInterval(function (){32     for(var attr in json){33       //1、取当前值34       var cur = 0;35       if(attr == 'opacity'){36         cur = Math.round(parseFloat(getStyle(obj,attr))*100);37       }38       else{39         cur = parseInt(getStyle(obj,attr));40       }41       //2、算速度42       var speed = (json[attr] - cur)/10;43       speed = speed>0 ? Math.ceil(speed) : Math.floor(speed);44       //3、检测停止45       if(cur == json[attr]){46         clearInterval(obj.timer);47         if(fn)fn();48       }49       else{50         if(attr == 'opacity'){51           obj.style.opacity = (cur+speed)/100;52           obj.style.filter = "alpha(opacity: '+ (cur + speed) +')";53         }54         else{55           obj.style[attr] = cur + speed + "px";56         }57       }58     }59   },30);60 }61 </script>62 </head>63 <body>64 <input id="btn1" type="button" value="运动">65 <div id="div1"></div>66 </body>67 </html>

   上面的代码,点击运动按钮后,div 的宽和高同时从100px 变为300px。在开启定时器之后,就使用 for in 循环,将之前框架中 attr 参数定义为一个变量,就是在 json 中循环遍历 attr,也就是存储在 json 中的属性值,此时已经没有 iTarget 参数了,那么要设置目标值,就要把原来的 iTarget 改为 json[attr],最后在调用 startMove 时就可以用 JSON 格式传入多个值了,startMove(oDiv, {width:300, height:300}),这样就完成了同时运动。

  

  6、完美运动框架

  我们已经封装了同时运动框架,到这一刻,我们离完美的运动框架就已经不远了,那为什么还有一点距离呢,我们通过下面的实例来看一下。

 1 <!DOCTYPE html> 2 <html> 3 <head> 4   <meta charset="UTF-8"> 5   <title>同时运动</title> 6 <style> 7 *{margin:0;padding:0} 8 div{ 9   width:200px;10   height:100px;11   background:red;12   border:2px solid black;13   filter:alpha(opacity:30);14   opacity:0.3;15 }16 </style>  17 </head>18 <script>19 window.onload = function (){20   oDiv = document.getElementById('div1');21   oDiv.onmousemove = function (){22     startMove(oDiv, {width:201, height:200, opacity:100});23   };24   oDiv.onmouseout = function (){25     startMove(oDiv, {width:200, height:100, opacity:30});26   };27 };28 function getStyle(obj,name){29   if(obj.currentStyle){30     return obj.currentStyle[name];31   }32   else{33     return getComputedStyle(obj,false)[name];34   }35 }36 function startMove(obj, json, fn){37   clearInterval(obj.timer);38   obj.timer = setInterval(function (){39     for(var attr in json){40       //1、取当前值41       var cur = 0;42       if(attr == 'opacity'){43         cur = Math.round(parseFloat(getStyle(obj,attr))*100);44       }45       else{46         cur = parseInt(getStyle(obj,attr));47       }48       //2、算速度49       var speed = (json[attr] - cur)/10;50       speed = speed>0 ? Math.ceil(speed) : Math.floor(speed);51       //3、检测停止52       if(cur == json[attr]){53         clearInterval(obj.timer);54         if(fn)fn();55       }56       else{57         if(attr == 'opacity'){58           obj.style.opacity = (cur+speed)/100;59           obj.style.filter = "alpha(opacity: '+ (cur + speed) +')";60         }61         else{62           obj.style[attr] = cur + speed + "px";63         }64       }65     }66   },30);67 }68 </script>69 <body>70 <div id="div1"></div>71 </body>72 </html>

  上面的代码,使用了同时运动框架,在其实例中,多值运动并没有什么问题,多个值可以同时变化,观察上面的代码,div 的初始宽度为200px,高度为100px,透明度为0.3,在鼠标移入后,我们让他的宽度变为201px,高度变为200px,透明度变为1,这时候就出事了,打开调试工具进行测试,可以看到在鼠标移入时,div 的宽度确实是从200px 运动到201px,但是他的高度和透明度远远没有达到我们的要求,我们来分析一下为什么会出现这种情况呢,只有宽度到达了目标值,而高度和透明度并没有达到目标值,整个运动就停止了。

  回过头检查同时运动框架,我们不难发现,在做检测停止时,if(cur == json[attr]),我们判断如果当前值达到目标值时,就关闭定时器,同时运动框架和完美运动框架的距离就产生在这了,我们并没有去判断是不是所有的运动都到达了终点,那么只要有一个运动达到了终点,他就会关闭定时器,那这样肯定是不行的,就好比参加旅游团,不可能人没到齐就出发了,这样肯定是不靠谱的。那要怎么解决呢,我们需要去判断是不是所有的运动都到达了目标值,如果所有的运动都达到了目标值,那么才能关闭定时器,也就是说,如果有没有达到的,就不能关闭定时器。

  下面我们就来看看完美的运动框架到底长什么样。

 1 <script> 2 function startMove(obj, json, fn){ 3   clearInterval(obj.timer); 4   obj.timer = setInterval(function (){ 5     var bStop = true;  //假设所有的值都达到了目标值,这一次运动就结束了。 6     for(var attr in json){ 7       //1.取当前值 8       var cur = 0; 9       if(attr == 'opacity'){10         cur = Math.round(parseFloat(getStyle(obj,attr))*100);11       }12       else{13         cur = parseInt(getStyle(obj,attr));14       }15       //2.算速度16       var speed = (json[attr]-cur)/6;17       speed = speed>0 ? Math.ceil(speed) : Math.floor(speed);18       //3.检测停止19       //如果有一个值不等于当前值。20       if(cur != json[attr]){21         bStop = false;22       }23       if(attr == 'opacity'){24         obj.style.opacity = (cur+speed)/100;25         obj.style.filter = "alpha(opacity: "+(cur + speed)+")";26       }27       else{28         obj.style[attr] = cur + speed + "px";29       }30     }31     //如果所有的值都达到了目标值,再关闭定时器。32     if(bStop){33       clearInterval(obj.timer);34       if(fn){35         fn();36       }37     }38   },30);39 }40 </script>

  我们把同时运动框架稍微完善以下,就是完美的运动框架了,首先在开启定时器执行循环之前,首先定义一个变量 bStop 值为 true,假设所有值都达到了目标值,那么就一次运动才算是结束了,然后在做检测停止时,再做判断,如果有一个值不等于当前值,也就是有一个值还没有达到目标值,就说明 bStop 为 false,那么就让他继续执行他的运动,在完成了整个循环之后,还要再做一个判断,如果所有值都达到了目标值,那么再关闭定时器。

  到现在,我们这个运动框架就算是真正的完美了,我们可以把这个框架保存为一个单独的 JS 文件,命名为 move.js,对于上面的问题可以简单的验证以下,再做一些其他值的运动。

 1 <!DOCTYPE html> 2 <html> 3 <head> 4   <meta charset="UTF-8"> 5   <title>完美运动</title> 6 <style> 7 *{margin:0;padding:0} 8 div{ 9   width:200px;10   height:100px;11   background:red;12   border:2px solid black;13   filter:alpha(opacity:30);14   opacity:0.3;15 }16 </style>17 <script src="JS/move++.js"></script>18 <script>19 window.onload = function (){20   oDiv = document.getElementById('div1');21   oDiv.onmousemove = function (){22     startMove(oDiv, {width:301, height:402, opacity:100, fontSize:30, borderWidth:5});23   };24   oDiv.onmouseout = function (){25     startMove(oDiv, {width:200, height:100, opacity:30, fontSize:12, borderWidth:2});26   };27 };28 </script>29 </head>30 <body>31 <div id="div1">多值同时运动</div>32 </body>33 </html>

  上面我们使用了完美运动框架,鼠标移入时,同时改变了 div 的宽、高、透明度、字体大小和边框宽度,鼠标移出时恢复初始定义,一点问题都没有,非常完美。

  

  7、运动框架的总结

  其实我们完全可以在网上找一个完美的运动框架,直接拿来使用,万能的互联网,出产任何你能想到的东西,这样也相当省事,但是对于一个学习者来说,如果所有的东西都直接搬来用,这样是没有任何问题,但是你并不了解他具体是怎么实现的,这个实现的过程才是最值得研究的,也是最有价值的,看着我们写的代码,一步步的趋于完美,在这个过程中,才能更好的锻炼逻辑思维,培养编程的思想,只有最基础的知识掌握了,根基扎实了,才能在编程的道路上走的更高更远,所谓一法通则万法通,多悟多总结,很多东西都可以触类旁通,道法自然,当然每个人都有自己的学习方式,只要是适合自己的,那就是最好的。

  闲话就不多说了,我们来看一下运动框架的演变过程。

  最简单的运动框架:startMove(iTarget)

  我们在刚开始定义了一个 startMove 函数,只需要开一个定时器,就可以让 div 动起来,但是根本停不下来,我们又做了判断,如果达到了目标值就清空定时器,但是还有一点小问题,虽然到目标位置了,定时器也清空了,再点击按钮,div 还是会运动一下,因为点击按钮之后 startMove 还会被执行一次,所以我们在执行 startMove 时,先要清空定时器,之后我们做了匀速运动的分享到侧边栏效果,因为有2个目标值,我们定义了 startMove 和 stopMove 两个函数,用于内容容器的展示和隐藏,这两个函数长的几乎一样,具有相同的功能,我们又做了代码优化,把不同的东西找出来,用参数的方式传入。

  起初我们给 startMove 定义了2个参数,speed 和 iTarget,在功能相同的情况下,传入的参数越少越好,目标值参数是不可以省略的,因为得有一个终点,而速度值是可以省略的,因为不管速度快还是慢,都会到达终点。

  我们还说了侧边栏分享效果使用缓冲运动做效果更好。

  多物体运动框架:startMove(obj, iTarget)

  想让多个物体运动起来,只需要再传入一个参数,也就是好对象参数,我们传入哪个对象,就运动哪个对象。

  任意值运动框架:startMove(obj, attr, iTarget)

  多个物体运动起来还不够,如果想让任意属性值运动,那就再传入一个参数,也就是属性参数,我们传入哪个属性,哪个属性就发生变化。

  我们还了解了 offset 属性的一个 bug,我们想让 div 的宽度逐渐变窄时,给他加了边框属性,不但没有变窄,反而是逐渐变宽了,因为 offset 属性受到其他属性的影响,他判断当前的属性值为宽度值加上边框值,最好的解决办法就是获取非行间样式,我们又封装了一用于获取非行间样式的函数 getStyle。

  链式运动框架:startMove(obj, attr, iTarget, fn)

  链式运动就是让宽变完之后再变高,高变完之后再变其他属性,一环扣一环,那么使用回调函数就可以解决了,当一次运动结束后,再调用一次 startMove,开始一个新的运动,那么再给他传一个参数。

  同时运动框架:startMove(obj, json)

  之前的框架都做的是单一的运动,那么既想让宽变化,也想让高变化,最好的办法就是使用 JSON 格式,我们简单的回忆了一下 JSON,他是一种轻量级的数据交换格式,在 startMove 这4个参数中,分别是对象、属性、目标值、回调函数,属性值和目标值是一对值,那么就以 JSON 的方式传入,再使用 for in 循环执行整个运动,就可以实现多值的运动了。

  完美运动框架:startMove(obj, json, fn)

  在封装了同时运动框架时,我们离完美运动框架就差一个判断了,同时运动框架,在检测停止时,判断如果是达到了目标值就清空定时器,而如果有一个值达到了目标值,其他值并未达到目标值,他也会清空定时器,这显然是不科学的,这并不是先到先到,所以我们定义了一个变量,假设所有值都达到了目标值,这一次运动才算结束,然后再做判断,如果有一个值没有达到目标值,那么就继续执行他的运动,最后直到所有值都达到目标值了,再关闭定时,这就完成了完美运动框架的封装。

  

  现在,我们就可以使用完美运动框架来完成网站中一些常见的动画效果了,比如图片轮播、幻灯片展示、以及微博实时滚动效果等。