javascript运动系列第二篇——变速运动
前面的话
前面介绍过匀速运动的实现及注意事项,本文在匀速运动的基础上,更进一步,实现各种变速运动,包括加速运动、减速运动、缓冲运动、重力运动和弹性运动
准备工作
匀速运动
在原生javascript中实现运动的主要工具是定时器,通过设置固定的间隔时间,使元素在确定的间隔时间内实现距离的变化。而运动变化的主要表现形式是距离的变化
例如,定时器频率可如下列代码所示,设置为30ms。每30ms对s的值进行更新,使其增加一个步长step的距离,来实现视觉上的元素运动效果
setInterval(function(){s = s + step },30)
而step的值如何变化就决定了何种运动形式
s = v * t;
当step是一个恒定的值(如10),则说明相同时间间隔内,距离变化相同,说明速度是一个恒定的值,该运动为匀速运动
<button id="btn">开始运动</button> <button id="reset">还原</button> <div id="test" style="height: 100px;width: 100px;background-color: pink;position:absolute;left:0;"></div> <div style="background-color:red;width:1px;height:100px;position:absolute;left:500px;"></div> <script> var timer; reset.onclick = function(){history.go();} btn.onclick = function(){clearInterval(timer);//每30ms,位移变化10pxvar step = 10;//声明当前值变量curvar cur;var target = parseFloat('500px');timer = setInterval(function(){cur = test.offsetLeft;//若步长设置值使得元素超过目标点时,将步长设置值更改为目标点值 - 当前值 if(cur+step-target>0){step = target - cur;}test.style.left = cur + step + 'px';if(step == target - cur){clearInterval(timer);}},20); } </script>
小数解析
在CSS解析中,是可以识别小数的;但在javascript中,不同的解析方式对于小数识别有区别
如果使用getComputedStyle或currentStyle是可以识别小数的,但是使用offset值,则返回对应的四舍五入值
[注意]IE7-浏览器不支持小数
<div id="test" style="height: 100px;width: 100.7px;"></div> <script> console.log(test.offsetWidth);//101 console.log(getComputedStyle(test).width);//'100.7px' </script>
在上面的代码中,元素以100.7px的宽度进行渲染;但是,通过offsetWidth获取的值是100.7四舍五入后的值101;通过getComputedStyle计算样式获取的值是实际渲染值100.7px
所以,为了保证结果准备尽量使用计算样式,而不要使用offset值
function getCSS(obj,style){if(window.getComputedStyle){return getComputedStyle(obj)[style];}return obj.currentStyle[style]; }
加速运动
说到加速运动,必须要提到一个物理名词——加速度
v = v0 + a*t; s = (v0+v)*t/2 = v0*t + 1/2*a*t*t;
如果v0是初始速度,v1是定时器第n次经过20ms之后元素变化后的速度,v2是定时器第n+1次经过20ms之后元素变化后的速度
s1 = v0*t1 + 1/2*a*t1*t1; s2 = v0*t2 + 1/2*a*t2*t2; s2 - s1 = (t2-t1)(v0+ 1/2*a*(t2+t1)) = 0.02(v0+a*(0.02n+0.01))
所以,下列代码中的步长step值是0.02(v0+a*(0.02n+0.01))
step = 0.02(v0+a*(0.02n+0.01)) = 2/10000(100*v0+a(2n+1))
v0代表初始速度,a代表加速度,n代表定时器执行的次数
由于n的值是以+1的形式递增,当a为正数时,step值不断增加,则为加速运动;当a为负数时,step值不断减小,则为减速运动
假设初始速度v0等于0,加速度a等于200,则step = 0.04(2n+1)
setInterval(function(){s = s + step },20)
<button id="btn">开始运动</button> <button id="reset">还原</button> <div id="test" style="height: 100px;width: 100px;background-color: pink;position:absolute;left:0;"></div> <div style="background-color:red;width:1px;height:100px;position:absolute;left:500px;"></div> <script> function getCSS(obj,style){if(window.getComputedStyle){return getComputedStyle(obj)[style];}return obj.currentStyle[style]; } reset.onclick = function(){history.go();} btn.onclick = function(){//声明定时器运行次数var index=-1;//声明步长值stepvar step;//声明当前值curvar cur;//声明目标值var target = parseFloat('500px');clearInterval(test.timer);test.timer = setInterval(function(){//更新定时器的工作次数 index++;//更新步长值 step = 0.04*(2*index+1);//更新当前值 cur = parseFloat(getCSS(test,'left'));//若步长设置值使得元素超过目标点时,将步长设置值更改为目标点值 - 当前值if(cur+step-target>0){step = target - cur;}//更新left值 test.style.left = cur + step + 'px';//当元素到达目标点时,停止定时器if(step == target - cur){clearInterval(test.timer);} },20); } </script>
重力运动
重力运动是加速运动的特殊情况,相当于初始速度为0,加速度为9.8m/s2的特值情况
这时,涉及到长度单位m变换为像素单位px的过程
1cm = 37.8px 1m = 100cm
所以9.6m = 9.6*37.8*100px = 36288px
step = 0.02(v0+a*(0.02n+0.01)) = 2/10000(100*v0+a(2n+1))
当v0=0,a=36288时,step = 7.2576(2n+1)
这里,我们把运动的距离设置为300px,实际上,转换为常用长度单位时,只有8cm。如果,我们要以300px模拟8m的重力效果,则可以粗略地将加速度缩小为原来的1/100
此时,修正过的step值为0.072576(2n+1)
<button id="btn">开始运动</button> <button id="reset">还原</button> <div id="test" style="height: 100px;width: 100px;background-color: pink;position:absolute;top:30px;"></div> <div style="background-color:red;height:1px;width:100px;position:absolute;top:300px;"></div> <script> function getCSS(obj,style){if(window.getComputedStyle){return getComputedStyle(obj)[style];}return obj.currentStyle[style]; } reset.onclick = function(){history.go();} btn.onclick = function(){//声明定时器运行次数var index=-1;//声明步长值stepvar step;//声明当前值curvar cur;//声明目标值var target = parseFloat('300px');clearInterval(test.timer);test.timer = setInterval(function(){//更新定时器的工作次数 index++;//更新步长值 step = 0.072576*(2*index+1);//更新当前值 cur = parseFloat(getCSS(test,'top'));//若步长设置值使得元素超过目标点时,将步长设置值更改为目标点值 - 当前值if(cur+step-target>0){step = target - cur;}//更新top值 test.style.top = cur + step + 'px';//当元素到达目标点时,停止定时器if(step == target - cur){clearInterval(test.timer);} },20); } </script>
减速运动
相对于加速运动来说,减速运动有一个临界点的问题。如果元素运动到指定的位置前,速度已经减到0,则停到当前速度为0的位置
同样以定时器20ms的频率为例,位移变化的step值是0.02(v0+a*(0.02n+0.01))
假设初始速度v0为100px/s,加速度为-10,则step = 0.02(99.9-0.2n)
<button id="btn">开始运动</button> <button id="reset">还原</button> <div id="test" style="height: 100px;width: 100px;background-color: pink;position:absolute;left:0;"></div> <div style="background-color:red;width:1px;height:100px;position:absolute;left:500px;"></div> <script> function getCSS(obj,style){if(window.getComputedStyle){return getComputedStyle(obj)[style];}return obj.currentStyle[style]; } reset.onclick = function(){history.go();} btn.onclick = function(){//声明定时器运行次数var index=-1;//声明步长值stepvar step;//声明当前值curvar cur;//声明目标值var target = parseFloat('500px');clearInterval(test.timer);test.timer = setInterval(function(){//更新定时器的工作次数 index++;//更新步长值 step = 0.02*(99.9-0.2*index);if(step < 0){clearInterval(test.timer);}//更新当前值 cur = parseFloat(getCSS(test,'left'));//若步长设置值使得元素超过目标点时,将步长设置值更改为目标点值 - 当前值if(cur+step-target>0){step = target - cur;}//更新left值 test.style.left = cur + step + 'px';console.log(index,cur,step,target,test.style.left)//当元素到达目标点时,停止定时器if(step == target - cur){clearInterval(test.timer);} },20); } </script>
缓冲运动
缓冲运动是减速运动的一种特殊形式,指元素做减速运动,速度减到0时,恰好停在目标点位置
以定时器20ms的频率为例
step = 0.02(v0+a*(0.02n+0.01)) = 2/10000(100*v0+a(2n+1))
假设初始速度v0为100px/s,最终的v为0
v = v0 - a*t s = (v0+v)/2*t
所以,a = -5000/s ,step = 2 - (2n+1)/s
<button id="btn">开始运动</button> <button id="reset">还原</button> <div id="test" style="height: 100px;width: 100px;background-color: pink;position:absolute;left:0;"></div> <div style="background-color:red;width:1px;height:100px;position:absolute;left:500px;"></div> <script> function getCSS(obj,style){if(window.getComputedStyle){return getComputedStyle(obj)[style];}return obj.currentStyle[style]; } reset.onclick = function(){history.go();} btn.onclick = function(){//声明定时器运行次数var index=-1;//声明步长值stepvar step;//声明当前值curvar cur;//声明目标值var target = parseFloat('500px');clearInterval(test.timer);test.timer = setInterval(function(){//更新定时器的工作次数 index++;//更新步长值 step = 2 - (2*index+1)/target;//更新当前值 cur = parseFloat(getCSS(test,'left'));//若步长设置值使得元素超过目标点时,将步长设置值更改为目标点值 - 当前值if((cur+step-target)*step>0){step = target - cur;}//更新left值 test.style.left = cur + step + 'px';//当元素到达目标点时,停止定时器if(step == target - cur){clearInterval(test.timer);} },20); } </script>
加减速运动
加减速运动是加速运动和减速运动的结合。前半段运动时,做加速运动。到达指定点时,做减速运动,最终到达终点停止
step = 0.02(v0+a*(0.02n+0.01)) = 2/10000(100*v0+a(2n+1))
假设v0=0,最终速度v=100,距离s = 200
所以a = v*v/(2*s) = 5000/s = 25
则加速运动的step = (2n+1)/s =(2n+1)/200
在加速运动中,s=1/2*a*t*t;
所以加速运动总时间t = s/50 = 4,定时器运行次数n = t/0.02=200次
减速运动的step=0.02(v0-(2n+1)),此时的v0相应于加速运动结束时的瞬时速度100,a= -5000/s = -25
所以,减速运动的step=2-(2n+1)/s = 2-(2n+1)/200
<button id="btn">开始运动</button> <button id="reset">还原</button> <div id="test" style="height: 100px;width: 100px;background-color: pink;position:absolute;left:0;"></div> <div style="background-color:blue;width:1px;height:100px;position:absolute;left:200px;"></div> <div style="background-color:red;width:1px;height:100px;position:absolute;left:400px;"></div> <script> function getCSS(obj,style){if(window.getComputedStyle){return getComputedStyle(obj)[style];}return obj.currentStyle[style]; } reset.onclick = function(){history.go();} btn.onclick = function(){//声明定时器运行次数var index=-1;//声明步长值stepvar step;//声明当前值curvar cur;//声明目标值var target = parseFloat('400px');clearInterval(test.timer);test.timer = setInterval(function(){//更新定时器的工作次数 index++;//当index为200时,说明进行完一次运动,则将index置0if(index == 200){index = 0;}; //更新当前值 cur = parseFloat(getCSS(test,'left'));//更新步长值//加速运动if(cur < 200){step =(2*index+1)/(target/2); }else{//减速运动 step = 2-(2*index+1)/(target/2);}//若步长设置值使得元素超过目标点时,将步长设置值更改为目标点值 - 当前值if((cur+step-target)*step>0){step = target - cur;}//更新left值 test.style.left = cur + step + 'px';//当元素到达目标点时,停止定时器if(step == target - cur){clearInterval(test.timer);} },20); } </script>
往复运动
往复运动相当于加减速运动的升级版。元素先加速后减速,当减速到0时,元素并不停止,而是做反向的先加速后减速运动,如此反复
加速运动和减速运动的公式与加减速运动的公式相同
加速运动:step = (2n+1)/s =(2n+1)/200
减速运动:step = 2-(2n+1)/s = 2-(2n+1)/200
<button id="btn">开始运动</button> <button id="reset">还原</button> <div id="test" style="height: 100px;width: 100px;background-color: pink;position:absolute;left:0;"></div> <div style="background-color:green;width:1px;height:100px;position:absolute;left:0px;"></div> <div style="background-color:blue;width:1px;height:100px;position:absolute;left:200px;"></div> <div style="background-color:red;width:1px;height:100px;position:absolute;left:400px;"></div> <script> function getCSS(obj,style){if(window.getComputedStyle){return getComputedStyle(obj)[style];}return obj.currentStyle[style]; } reset.onclick = function(){history.go();} btn.onclick = function(){//声明定时器运行次数var index=-1;//声明步长值stepvar step;//声明当前值curvar cur;//声明目标值var target = parseFloat('400px');//声明运动的次数,一个方向的加速和减速运动总共算一个运动var num=0;clearInterval(test.timer);test.timer = setInterval(function(){//更新定时器的工作次数 index++;//当index为200时,说明进行完一次运动,则将index置0if(index == 200){index = 0;num += 0.5;}; //更新当前值 cur = parseFloat(getCSS(test,'left'));//更新步长值if(Math.floor(num)%2 == 0){//加速运动if(cur < 200){step =(2*index+1)/200; }else{//减速运动 step = 2-(2*index+1)/200; }}else{//加速运动if(cur > 200){step =-(2*index+1)/200; }else{//减速运动 step = (2*index+1)/200-2; } } //更新left值 test.style.left = cur + step + 'px';},20); } </script>
变速函数
以上介绍的各种变速运动其中大部分代码相同,只是步长公式不同而已。所以,我们可以把变速运动也封装成一个函数形式,命名为varMove.js
function getCSS(obj,style){if(window.getComputedStyle){return getComputedStyle(obj)[style];}return obj.currentStyle[style]; } function varMove(json){var obj = json.obj;var attr = json.attr;var target = json.target;var type = json.type;var value = json.value;var fn = json.fn;//如果没有建立定时器对象,则在obj下建立定时器对象if(!obj.timers){obj.timers = {};}//清除定时器if(obj.timers[attr]){return;}//声明定时器运行次数var index=-1;//声明当前值变量curvar cur = parseFloat(getCSS(obj,attr));//声明距离为distancevar distance= target - cur;//声明运动的次数,一个方向的加速和减速运动总共算一个运动var num=0;//开启定时器obj.timers[attr] = setInterval(function(){//更新定时器的工作次数index++; //获取样式当前值并赋值给curcur = parseFloat(getCSS(obj,attr));//根据不同的type值来设置步长switch(type){//如果type设置为'linear',则为匀速运动case 'linear'://linear的value值为步长stepstep = Number(value) || 10;break;//如果type设置为'speedup',则为加速运动case 'speedup'://'speedup'的value值为总时间tvalue = Number(value) || 2;step = (4*distance/(value*value*10000))*(2*index+1)break;//如果type设置为'gravity',则为重力运动case 'gravity':step = 0.072576*(2*index+1);break;//如果type设置为'speeddown',则为减速运动//'speeddown'的value值为初始速度v0case 'speeddown':value = Number(value) || 100;step = (2/10000)*(100*value-(value*value)/(2*distance)*(2*index+1))break;//如果type设置为'speedupAndDown',则为先加速后减速运动//'speedupAndDown'的value值为总时间tcase 'speedupAndDown':value = Number(value) || 2;//当index为25*value时,说明进行完一次运动,则将index置0if(index == 25*value){index = 0;}; //加速运动if(cur < distance/2){step =8*distance/(10000*value*value)*(2*index+1); }else{//减速运动step = distance/(25*value)-8*distance/(10000*value*value)*(2*index+1);}break;//如果type设置为'repeat',则为往复运动//'repeat'的value值为一次运动(一次加速和一次减速)的时间case 'repeat':value = Number(value) || 2;//当index为25*value时,说明进行完一次运动,则将index置0if(index == 25*value){index = 0;num += 0.5;}; if(Math.floor(num)%2 == 0){//加速运动if(cur < distance/2){step =8*distance/(10000*value*value)*(2*index+1); }else{//减速运动step = distance/(25*value)-8*distance/(10000*value*value)*(2*index+1);}}else{//加速运动if(cur > distance/2){step =-8*distance/(10000*value*value)*(2*index+1); }else{//减速运动step = 8*distance/(10000*value*value)*(2*index+1)-distance/(25*value);} } break;//如果没有设置,则默认为'linear'匀速运动default: step = 10; }//若步长设置值使得元素超过目标点时,将步长设置值更改为目标点值 - 当前值if(((cur + step - target)*step > 0) && type != 'repeat'){step = target - cur;}//将合适的步长值赋值给元素的样式obj.style[attr] = cur + step + 'px';//当元素到达目标点后,停止定时器if((step == target - cur) && type != 'repeat'){clearInterval(obj.timers[attr]);obj.timers[attr] = 0;fn && fn.call(obj); } },20); }
下面以varMove函数为基础,进行一些简单应用
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Document</title> <style> #box{margin-bottom:10px; } #test{height: 100px;width: 100px;background-color:lightblue;border-radius: 50%;position: absolute;left:0; } .backup{height: 100px;width: 1px;position: absolute; } .backup:nth-child(1){left:0px;background-color:red; } .backup:nth-child(2){left:300px;background-color:green; } .backup:nth-child(3){left:600px;background-color:blue; } </style> </head> <body> <div id="box"><button id="btn1">匀速运动</button><button id="btn2">加速运动</button><button id="btn3">减速运动</button><button id="btn4">加减速运动</button><button id="btn5">往复运动</button><button id="reset">还原</button> </div> <div id="test"></div> <div><div class="backup"></div><div class="backup"></div><div class="backup"></div> </div> <script src="http://files.cnblogs.com/files/xiaohuochai/varMove.js"></script> <script> reset.onclick = function(){history.go();} btn1.onclick = function(){varMove({obj:test,attr:'left',target:'600'}) } btn2.onclick = function(){varMove({obj:test,attr:'left',target:'600',type:'speedup'}) } btn3.onclick = function(){varMove({obj:test,attr:'left',target:'600',type:'speeddown'}) } btn4.onclick = function(){varMove({obj:test,attr:'left',target:'600',type:'speedupAndDown'}) } btn5.onclick = function(){varMove({obj:test,attr:'left',target:'600',type:'repeat'}) } </script> </body> </html>
javascript运动系列第二篇——变速运动相关推荐
- 深入理解javascript函数系列第二篇——函数参数
前面的话 javascript函数的参数与大多数其他语言的函数的参数有所不同.函数不介意传递进来多少个参数,也不在乎传进来的参数是什么数据类型,甚至可以不传参数.本文是深入理解javascript函数 ...
- javascript动画系列第二篇——磁性吸附
前面的话 上一篇,我们介绍了元素拖拽的实现.但在实际应用中,常常需要为拖拽的元素限定范围.而通过限定范围,再增加一些辅助的措施,就可以实现磁性吸附的效果 范围限定 如果我们限定元素只可以在可视范围内移 ...
- javascript运动系列第九篇——碰撞运动
前面的话 碰撞可以分为碰壁和互碰两种形式,上篇介绍了碰壁运动,本文将从浅入深地介绍碰撞运动的互碰形式 碰撞检测 对于互碰形式的碰撞运动来说,首先要解决的是碰撞检测.对于矩形元素的碰撞检测前面的博文已经 ...
- JavaScript设计模式系列—模式篇总结(上)
转载请注明预见才能遇见的博客:http://my.csdn.net/ 原文地址:https://blog.csdn.net/pcaxb/article/details/102517956 JavaSc ...
- 前端工程师技能之photoshop巧用系列第二篇——测量篇
前端工程师使用photoshop进行的大量工作实际上是测量.本文是photoshop巧用系列第二篇--测量篇 测量信息 在网页制作中需要使用photoshop测量的信息分为两类,分别是尺寸信息和颜色信 ...
- 焱老师带你学习MYSQL系列 第二篇 (MYSQL 数据结构)
相关系列链接 焱老师带你学习MYSQL系列 第六篇 (MYSQL是如何实现锁的) 焱老师带你学习MYSQL系列 第五篇 (MYSQL事务隔离级别是如何实现的) 焱老师带你学习MYSQL系列 第四篇 ( ...
- javascript运动系列第八篇——碰壁运动
前面的话 碰撞运动可能是运动系列里面比较复杂的运动了.碰撞可以分为碰壁和互碰两种形式,而碰撞前后的运动形式也可以分为变速和匀速两种,如果再涉及到阻力,具有速度损耗的话,就更加复杂了.本文先介绍碰壁运动 ...
- javascript动画系列第一篇——模拟拖拽
前面的话 从本文开始,介绍javascript动画系列.javascript本身是具有原生拖放功能的,但是由于兼容性问题,以及功能实现的方式,用的不是很广泛.javascript动画广泛使用的还是模拟 ...
- 阿里出品移动研发“神器” 阿里移动云系列第二篇|“移”步到位:一站式移动应用研发体系...
摘要:2017杭州云栖大会阿里移动云峰会专场上,阿里巴巴高级技术专家小木带来一站式应用研发体系方面的演讲.本文主要以互联网的应用背景开始谈起,进而阐述了已拥有APP的企业在APP的生命周期中会遇见哪些 ...
最新文章
- 脑电分析系列[MNE-Python-2]| MNE中数据结构Epoch及其创建方法
- 【Git】Git 分支管理 ( 解决分支合并冲突 | 创建并切换分支 git switch -c feature1 | 修改 feature1 分支并提交 | 修改 master 主版本并提交 )
- MongoDB【最新版V2.6】- 发行说明
- Linux硬链接 软链接
- BZOJ2125 最短路
- bzoj4636: 蒟蒻的数列
- linux docker安装_Linux上安装docker的完美教程
- vue数据双向绑定的原理
- Educational Codeforces Round 68 (Rated for Div. 2)-D. 1-2-K Game
- 微信小程序获取上一页路由 获取从哪个页面跳转进来的
- UVM入坑系列笔记(一)
- Topological Spaces(拓扑空间)
- android: 多线程编程基础
- 一键备份服务器文件夹权限,教大家一键设置局域网共享文件夹权限
- Android平台车牌识别SDK
- 中英对照泰戈尔《飞鸟集》(一)
- 用javascript和jquery部分知识实现的打地鼠小游戏
- 工业控制计算机固态硬盘,我们如何选择一款好的工业级固态硬盘?
- 成都百知教育:做Shopee店铺没有方向,这3大层级必须理清!
- kafka监控获取logSize, offset, lag等信息