原生javascript写自己的运动框架(匀速运动篇)
网上有很多JavaScript的运动库,这里和大家分享一下用原生JavaScript一步一步写一个运动函数的过程,如读者有更好的建议欢迎联系作者帮助优化完善代码。这个运动函数完成后,就可以用这个运动函数写轮播、选项卡、滚动文字的特效。运动的模式有很多,如匀速运动、匀加速运动、变加速运动等等。这里我们先以匀速运动为例,后续再介绍其他运动模式。
现在我们先用HTML和CSS将我们的运动对象建好
1 <!DOCTYPE html> 2 <html> 3 <head> 4 <meta charset="utf-8"> 5 <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1"> 6 <title>move</title> 7 <meta name="description" content=""> 8 <meta name="keywords" content=""> 9 <link href="" rel="stylesheet"> 10 <style type="text/css"> 11 .box1{width:100px; height: 100px; background: red; position: absolute; left: 0px; top:50px;opacity:1; filter: alpha(opacity=100);} 12 span{display: block; width:1px; height:300px; background:black; position: absolute; left: 500px; top: 0;} 13 </style> 14 </head> 15 <body> 16 <div class="box1"></div> 17 <span></span> 18 </body> 19 </html>
打开浏览器会看到一个红色的块,和一条黑色的线,现在我们要让红色的左边线移动到黑线处。那么怎么移动过去呢?如果用老式电影播放机看过电影,应该知道我们看的电影实际上是有一张张的图片组成的,这些图片播放的速度很多也就成了一组连续的动作。我们这里的运动函数也是基于这种原理。
我们先获取当前left的位置,然后设定一个定时器,定时器执行一次left加10个像素,这样红色部分就可以向右运动了。这里我们设置定时器16毫秒执行一次,也就相当于1秒钟执行62.5次,大概就是60帧。在执行left加10个像素前我们还要进行一个判断用来判断何时关定时器。判断条件是如果当前left到目标点的距离小于10就关闭定时器,并让left的值等于目标值。
现在开始写JavaScript运动函数
1 window.onload = function(){ 2 var oDiv = document.getElementsByTagName('div')[0]; 3 oDiv.onclick = function(){ 4 startMove(); 5 } 6 } 7 var timer = null; 8 function startMove(target){ 9 //获取运动目标 10 var oDiv = document.getElementsByTagName('div')[0]; 11 timer = setInterval(function(){ 12 //如果当前left到目标点的距离(500)小于10就关闭定时器; 13 if(500-oDiv.offsetLeft<10){ 14 clearInterval(timer) 15 oDiv.style.left = 500 + 'px'; 16 }else{ 17 oDiv.style.left = oDiv.offsetLeft + 10 + 'px'; 18 } 19 }, 16); 20 }
这样我们就可以完成了一个向右运动的过程,但是不完美,只能向右运动(这里的10如果是-10就可以向左运动),只能运动到函数内指定的位置(即目标值),我们用speed来替换10,用target替换500,target由函数参数传入。speed=(目标值-当前值)/60,这里的60相当于是每秒60帧。目标值>当前值,speed就是一个正数,当前值向目标值靠近;目标值<当前值,speed就是一个负数,当前值也是向目标值靠近。我们用于判断的条件换成绝对值比较。
优化后的代码
1 window.onload = function(){ 2 var oDiv = document.getElementsByTagName('div')[0]; 3 oDiv.onclick = function(){ 4 startMove(500); 5 } 6 } 7 var timer = null; 8 function startMove(target){ 9 //获取运动目标 10 var oDiv = document.getElementsByTagName('div')[0]; 11 var speed = (target-oDiv.offsetLeft)/60; 12 timer = setInterval(function(){ 13 //如果当前left到目标点的距离(target)小于speed就关闭定时器; 14 if(Math.abs(target-oDiv.offsetLeft)<Math.abs(speed)){ 15 clearInterval(timer) 16 oDiv.style.left = target + 'px'; 17 }else{ 18 oDiv.style.left = oDiv.offsetLeft + speed + 'px'; 19 } 20 }, 16); 21 }
测试div的left为0或者1000的时候,都运动到了目标位置500,但是还是不完美,只能移动left,如果要移动top还得改函数内部的left,这里我们将移动属性也作为参数传入函数。offsetLeft也换个形式,我们引入一个获取对象属性值的函数
1 window.onload = function(){ 2 var oDiv = document.getElementsByTagName('div')[0]; 3 oDiv.onclick = function(){ 4 startMove('top', 500); 5 6 } 7 } 8 9 //获取对象属性值 10 function getStyle(obj, attr){ 11 if(window.getComputedStyle){ 12 return window.getComputedStyle(obj, false)[attr]; 13 }else{ 14 return obj.currentStyle[attr]; 15 } 16 } 17 var timer = null; 18 19 //运动函数 20 function startMove(attr, target){ 21 //获取运动目标 22 var oDiv = document.getElementsByTagName('div')[0]; 23 var speed = (target-parseFloat(getStyle(oDiv, attr)))/60; 24 25 timer = setInterval(function(){ 26 var fCur = parseFloat(getStyle(oDiv, attr)); 27 //如果当前位置到目标点的距离,小于speed就关闭定时器; 28 if(Math.abs(target-fCur)<Math.abs(speed)){ 29 clearInterval(timer) 30 oDiv.style[attr] = target + 'px'; 31 }else{ 32 oDiv.style[attr] = fCur + speed + 'px'; 33 } 34 35 }, 16); 36 }
现在我们已经可以做多个属性值的运动了,但是还是有bug,运行下列代码试试
1 window.onload = function(){ 2 var oDiv = document.getElementsByTagName('div')[0]; 3 oDiv.onmouseover = function(){ 4 startMove('width', 500); 5 } 6 oDiv.onmouseout = function(){ 7 startMove('width', 100); 8 } 9 }
你会发现用的有时不受控制,这是因为我们对同一个对象开了多个定时运动导致的,我们再加一行代码clearInterval(timer);加在timer = setInterval()之前,在每个运动的定时器开启前去除其他的定时器,这样问题就解决了。但是还是不完美,只能一个对象运动,现在我要让多个对象运动,就不行,那是因为运动对象在我们的函数了,现在我们将运动对象也作为参数传进函数里。
1 <!DOCTYPE html> 2 <html> 3 <head> 4 <meta charset="utf-8"> 5 <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1"> 6 <title>move</title> 7 <meta name="description" content=""> 8 <meta name="keywords" content=""> 9 <link href="" rel="stylesheet"> 10 <style type="text/css"> 11 .box1{width:100px; height: 50px; background: red; position: absolute; left: 0px; top: 0px;opacity:1; filter: alpha(opacity=100);/* border: 1px solid black;*/} 12 .box2{width:100px; height: 50px; background: red; position: absolute; left: 0; top: 80px;} 13 .box3{width:100px; height: 50px; background: red; position: absolute; left: 0; top: 160px;} 14 span{display: block; width:1px; height:300px; background:black; position: absolute; left: 500px; top: 0;} 15 </style> 16 </head> 17 <body> 18 <div class="box1"></div> 19 <div class="box2"></div> 20 <div class="box3"></div> 21 <span></span> 22 </body> 23 </html>
1 window.onload = function(){ 2 var oDiv = document.getElementsByTagName('div'); 3 oDiv[0].onmouseover = function(){ 4 startMove(this, 'width', 500); 5 } 6 oDiv[0].onmouseout = function(){ 7 startMove(this, 'width', 100); 8 } 9 oDiv[1].onmouseover = function(){ 10 startMove(this, 'width', 500); 11 } 12 oDiv[1].onmouseout = function(){ 13 startMove(this, 'width', 100); 14 } 15 oDiv[2].onmouseover = function(){ 16 startMove(this, 'width', 500); 17 } 18 oDiv[2].onmouseout = function(){ 19 startMove(this, 'width', 100); 20 } 21 } 22 23 //获取对象属性值 24 function getStyle(obj, attr){ 25 if(window.getComputedStyle){ 26 return window.getComputedStyle(obj, false)[attr]; 27 }else{ 28 return obj.currentStyle[attr]; 29 } 30 } 31 32 //运动函数 33 function startMove(obj, attr, target){ 34 var speed = (target-parseFloat(getStyle(obj, attr)))/60; 35 clearInterval(obj.timer); 36 37 obj.timer = setInterval(function(){ 38 var fCur = parseFloat(getStyle(obj, attr)); 39 //如果当前位置到目标点的距离,小于speed就关闭定时器; 40 if(Math.abs(target-fCur)<Math.abs(speed)){ 41 clearInterval(obj.timer) 42 obj.style[attr] = target + 'px'; 43 }else{ 44 obj.style[attr] = fCur + speed + 'px'; 45 } 46 47 }, 16); 48 }
现在可以进行多物体的运动了,但是一个物体运动时其他物体就停止了运动,那是因为多个物体公用一个定时器导致的,我们再对代码进行改进,给每个对象添加属于自己的定时器。
即把startMove函数里的所有timer改成obj.timer就可以进行多个物体同时运动了。但是还是不完美,一个对象一次只能运动一个属性,不能同时改变多个属性,现在我们将startMove的参数attr和target用json数据对象传入。代码修改如下:
1 window.onload = function(){ 2 var oDiv = document.getElementsByTagName('div'); 3 //测试一个对象同时运动对个属性 4 oDiv[0].onclick = function(){ 5 startMove(this, {'width':50, 'left':500, 'height':200, 'top':100}); 6 } 7 } 8 9 //获取对象属性值 10 function getStyle(obj, attr){ 11 if(window.getComputedStyle){ 12 return window.getComputedStyle(obj, false)[attr]; 13 }else{ 14 return obj.currentStyle[attr]; 15 } 16 } 17 18 //运动函数 19 function startMove(obj, json){ 20 var speed = {}; 21 //给每个属性设一个速度 22 for (var attr in json){ 23 speed[attr] = (json[attr]-parseFloat(getStyle(obj, attr)))/60; 24 } 25 clearInterval(obj.timer);attr 26 27 obj.timer = setInterval(function(){ 28 var fCur = 0; 29 var btn = true; 30 for(var attr in json){ 31 fCur = parseFloat(getStyle(obj, attr)); 32 //判断运动的完成条件, 33 if(Math.abs(fCur-json[attr])<Math.abs(speed[attr])){ 34 btn = false; 35 } 36 obj.style[attr] = fCur + speed[attr] + 'px'; 37 } 38 //因为速度是根据(目标值-当前值)/60来确定的,所以每个属性的完成时间是一样的 39 //在运动完成时给所以属性值设置为目标值,并清楚定时器。 40 if(btn == false){ 41 for(var attr in json){ 42 obj.style[attr] = json[attr] + 'px'; 43 } 44 clearInterval(obj.timer); 45 } 46 47 }, 16); 48 }
到现在,一个运动函数基本完成了,但还有优化的地方。可以给函数增加一个参数,参数是一个函数,即要求运动完成后执行一个函数。另外我们的运动函数不能修改透明度,我们对透明度单独处理,实现透明度的修改就可以做淡入谈出了。如果还想控制运动的快慢可以在增加一个时间参数。现在来修改我们的运动函数,使它可以完成上述三个功能。
1 window.onload = function(){ 2 var oDiv = document.getElementsByTagName('div'); 3 //测试一个对象同时运动对个属性 4 oDiv[0].onclick = function(){ 5 startMove(this, {'width':50, 'left':500, 'opacity':0.3, 'height':200, 'top':100}, null, function(){ 6 startMove(oDiv[0], {'width':200, 'left':1000, 'opacity':1, 'height':50, 'top':0}) 7 }); 8 } 9 } 10 11 //获取对象属性值 12 function getStyle(obj, attr){ 13 if(window.getComputedStyle){ 14 return window.getComputedStyle(obj, false)[attr]; 15 }else{ 16 return obj.currentStyle[attr]; 17 } 18 } 19 20 //运动函数 21 function startMove(obj, json, time, fun){ 22 var iTime = 1000; 23 if (parseInt(time)){ 24 iTime = time; 25 } 26 var iFrame = iTime/16; 27 var speed = {}; 28 //给每个属性设一个速度 29 for (var attr in json){ 30 speed[attr] = (json[attr]-parseFloat(getStyle(obj, attr)))/iFrame; 31 } 32 clearInterval(obj.timer);attr 33 34 obj.timer = setInterval(function(){ 35 var fCur = 0; 36 var btn = true; 37 for(var attr in json){ 38 fCur = parseFloat(getStyle(obj, attr)); 39 //判断运动的完成条件, 40 if(Math.abs(fCur-json[attr])<Math.abs(speed[attr])){ 41 btn = false; 42 } 43 //判断attr如果是opacity则执行透明度修改的代码,否则执行其他属性值修改代码 44 if(attr=='opacity'){ 45 obj.style[attr] = fCur + speed[attr]; 46 obj.style.filter = 'alpha(opacity=' + (fCur + speed[attr])*100 + ')'; 47 }else{ 48 obj.style[attr] = fCur + speed[attr] + 'px'; 49 } 50 } 51 //因为速度是根据(目标值-当前值)/60来确定的,所以每个属性的完成时间是一样的 52 //在运动完成时给所以属性值设置为目标值,并清楚定时器。 53 if(btn == false){ 54 for(var attr in json){ 55 obj.style[attr] = json[attr] + 'px'; 56 } 57 clearInterval(obj.timer); 58 if(fun){ 59 fun(); 60 } 61 } 62 63 }, 16); 64 }
这里有一个小问题,当不传fun时,time是可选的,但是如果要传fun,就必须给一个time,否则程序就不会运行。可以给空字符串,null这样就会用默认值,也可以传具体的运行时间。
这样我们的匀速运动函数就完成了,有了它就可以写轮播、文字滚动等运动特效了。后期会增加变速运动部分,读者也可自行修改代码,使其可以做变速运动。现将我们最终版的匀速运动函数提取出来放在一个单独的js文件中,这样以后直接在html文件中导入就行了。
1 //获取对象属性值 2 function getStyle(obj, attr){ 3 if(window.getComputedStyle){ 4 return window.getComputedStyle(obj, false)[attr]; 5 }else{ 6 return obj.currentStyle[attr]; 7 } 8 } 9 10 //运动函数 11 function startMove(obj, json, time, fun){ 12 var iTime = 1000; 13 if (parseInt(time)){ 14 iTime = time; 15 } 16 var iFrame = iTime/16; 17 var speed = {}; 18 //给每个属性设一个速度 19 for (var attr in json){ 20 speed[attr] = (json[attr]-parseFloat(getStyle(obj, attr)))/iFrame; 21 } 22 clearInterval(obj.timer);attr 23 24 obj.timer = setInterval(function(){ 25 var fCur = 0; 26 var btn = true; 27 for(var attr in json){ 28 fCur = parseFloat(getStyle(obj, attr)); 29 //判断运动的完成条件, 30 if(Math.abs(fCur-json[attr])<Math.abs(speed[attr])){ 31 btn = false; 32 } 33 //判断attr如果是opacity则执行透明度修改的代码,否则执行其他属性值修改代码 34 if(attr=='opacity'){ 35 obj.style[attr] = fCur + speed[attr]; 36 obj.style.filter = 'alpha(opacity=' + (fCur + speed[attr])*100 + ')'; 37 }else{ 38 obj.style[attr] = fCur + speed[attr] + 'px'; 39 } 40 } 41 //因为速度是根据(目标值-当前值)/60来确定的,所以每个属性的完成时间是一样的 42 //在运动完成时给所以属性值设置为目标值,并清楚定时器。 43 if(btn == false){ 44 for(var attr in json){ 45 obj.style[attr] = json[attr] + 'px'; 46 } 47 clearInterval(obj.timer); 48 if(fun){ 49 fun(); 50 } 51 } 52 53 }, 16); 54 }
下一篇: IDC:大数据不是一个简单的定义