欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

原生javascript写自己的运动框架(匀速运动篇)

程序员文章站 2022-05-18 09:04:13
网上有很多JavaScript的运动库,这里和大家分享一下用原生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运动函数

原生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 }
View Code

这样我们就可以完成了一个向右运动的过程,但是不完美,只能向右运动(这里的10如果是-10就可以向左运动),只能运动到函数内指定的位置(即目标值),我们用speed来替换10,用target替换500,target由函数参数传入。speed=(目标值-当前值)/60,这里的60相当于是每秒60帧。目标值>当前值,speed就是一个正数,当前值向目标值靠近;目标值<当前值,speed就是一个负数,当前值也是向目标值靠近。我们用于判断的条件换成绝对值比较。

优化后的代码

原生javascript写自己的运动框架(匀速运动篇)
 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 }
View Code

测试div的left为0或者1000的时候,都运动到了目标位置500,但是还是不完美,只能移动left,如果要移动top还得改函数内部的left,这里我们将移动属性也作为参数传入函数。offsetLeft也换个形式,我们引入一个获取对象属性值的函数

原生javascript写自己的运动框架(匀速运动篇)
 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 }
View Code

现在我们已经可以做多个属性值的运动了,但是还是有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()之前,在每个运动的定时器开启前去除其他的定时器,这样问题就解决了。但是还是不完美,只能一个对象运动,现在我要让多个对象运动,就不行,那是因为运动对象在我们的函数了,现在我们将运动对象也作为参数传进函数里。

原生javascript写自己的运动框架(匀速运动篇)
 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>
html修改测试多物体运动
原生javascript写自己的运动框架(匀速运动篇)
 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 }
View Code

现在可以进行多物体的运动了,但是一个物体运动时其他物体就停止了运动,那是因为多个物体公用一个定时器导致的,我们再对代码进行改进,给每个对象添加属于自己的定时器。

即把startMove函数里的所有timer改成obj.timer就可以进行多个物体同时运动了。但是还是不完美,一个对象一次只能运动一个属性,不能同时改变多个属性,现在我们将startMove的参数attr和target用json数据对象传入。代码修改如下:

原生javascript写自己的运动框架(匀速运动篇)
 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 }
View Code

到现在,一个运动函数基本完成了,但还有优化的地方。可以给函数增加一个参数,参数是一个函数,即要求运动完成后执行一个函数。另外我们的运动函数不能修改透明度,我们对透明度单独处理,实现透明度的修改就可以做淡入谈出了。如果还想控制运动的快慢可以在增加一个时间参数。现在来修改我们的运动函数,使它可以完成上述三个功能。

原生javascript写自己的运动框架(匀速运动篇)
 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 }
View Code

这里有一个小问题,当不传fun时,time是可选的,但是如果要传fun,就必须给一个time,否则程序就不会运行。可以给空字符串,null这样就会用默认值,也可以传具体的运行时间。

这样我们的匀速运动函数就完成了,有了它就可以写轮播、文字滚动等运动特效了。后期会增加变速运动部分,读者也可自行修改代码,使其可以做变速运动。现将我们最终版的匀速运动函数提取出来放在一个单独的js文件中,这样以后直接在html文件中导入就行了。

原生javascript写自己的运动框架(匀速运动篇)
 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 }
View Code