动画效果在网站中是一种非常常见的交互式体验效果,比如侧边栏分享、图片淡入淡出,我们把这种动画效果就叫做运动,也就是让物体动起来。如果想让一个物体动起来,无非就是改变它的速度,也就是改变属性值,比如 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='/images/loading.gif' data-original="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)
在封装了同时运动框架时,我们离完美运动框架就差一个判断了,同时运动框架,在检测停止时,判断如果是达到了目标值就清空定时器,而如果有一个值达到了目标值,其他值并未达到目标值,他也会清空定时器,这显然是不科学的,这并不是先到先到,所以我们定义了一个变量,假设所有值都达到了目标值,这一次运动才算结束,然后再做判断,如果有一个值没有达到目标值,那么就继续执行他的运动,最后直到所有值都达到目标值了,再关闭定时,这就完成了完美运动框架的封装。
现在,我们就可以使用完美运动框架来完成网站中一些常见的动画效果了,比如图片轮播、幻灯片展示、以及微博实时滚动效果等。
原标题:JavaScript学习总结【11】、JS运动
关键词:JavaScript