标签:
动画效果在网站中是一种非常常见的交互式体验效果,比如侧边栏分享、图片淡入淡出,我们把这种动画效果就叫做运动,也就是让物体动起来。如果想让一个物体动起来,无非就是改变它的速度,也就是改变属性值,比如 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,所以除以100 44 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); //返回:3 3 alert(0.05*100); //返回:5 4 alert(10/3); //返回:3.33……35 5 alert(0.07*100); //返回:7.00……01 6 </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)
在封装了同时运动框架时,我们离完美运动框架就差一个判断了,同时运动框架,在检测停止时,判断如果是达到了目标值就清空定时器,而如果有一个值达到了目标值,其他值并未达到目标值,他也会清空定时器,这显然是不科学的,这并不是先到先到,所以我们定义了一个变量,假设所有值都达到了目标值,这一次运动才算结束,然后再做判断,如果有一个值没有达到目标值,那么就继续执行他的运动,最后直到所有值都达到目标值了,再关闭定时,这就完成了完美运动框架的封装。
现在,我们就可以使用完美运动框架来完成网站中一些常见的动画效果了,比如图片轮播、幻灯片展示、以及微博实时滚动效果等。
标签:
原文地址:http://www.cnblogs.com/Mtime/p/5130927.html