JavaScript完美运动框架进阶之旅

导语:

运动框架的实现思路运动,其实就是在一段时间内改变left、right、width、height、opacity的值,到达目的地之后停止。现在按照以下步骤来进行我们的运动框架的封装:匀速运动、缓冲运动、多物体运动、任意值变化。

运动框架的实现思路

(一) 匀速运动
速度动画
运动基础

思考:如何让 div 动起来?

如下:
设置元素绝对定位,只有绝对定位后,left,top等值才会生效。offsetLeft(当前元素相对父元素的位置)。

1.  /** 
2.  * 运动框架-1-动起来
3. *@param {HTMLElement} element 进行运动的节点
4.  * /
5.  var timer = null;
6.  function startMove(element){
7.     timer = setInterval( function () { // 定时器      
8.            element.style.left = element.offsetLeft + 5 + "px;
9.      },30);
10. }

你没看错,就是那么简单。但是等等,what ? 怎么不会停?! WTF !
那是因为我们没有运动终止条件。好在还是比较简单,之间在定时器内部,判断到达目标值,清除定时器就行啦!

1.  /** 
2.  * 运动框架-2-运动终止
3.  *@param {HTMLElement} element 进行运动的节点
4.  *@param {number}    iTarget 运动终止条件
5.  */
6.  var timer = null;
7. function startMove( element, iTarget) {
8.      timer = setInterval ( function ( ) {
9.          element.style.left = element.offsetLeft + 5 + "px";
10.        if (element.offsetLeft ===iTarget) { // 停止条件
11.              clearInterval ( timer );
12.         }; 
13.     }, 30);
14.  }

就这样是不是就完成了呢? no ,还有一些bug需处理。

运动中的bug

速度取到某些值会无法停止
到达位置之后再点击还会动——把运动和停止隔开(if/else)
重复点击速度加快——在开始运动时,关闭已有定时器
速度无法更改——把速度用变量保存

解决bug

速度取到某些值会无法停止(这个Bug稍后解决,在进化过程中自然解决)

1.  /**
2.  * 运动框架‐3‐解决Bug
3.  */
4. var timer=null;
5.  function startMove ( element , iTarget ) {
6.      clearInterval ( timer ) ; // 开始运动前关闭已有定时器
7.      timer = setInterval ( function ( ) {
8.            var iSpeed = 5 ;//把速度用变量保存
9.            // 把运动和停止隔开( if / else )
10.          if (element.offsetLeft == iTarget ) { // 结束运动
11.                clearInterval ( timer );
12.           } else {
13.                element.style.left = element.offsetLeft + iSpeed + "px";
14.            };
15.     } , 30);
16. }
(二)缓冲动画

思考:怎么样才是缓冲动画?
应该有以下几点:
1、逐渐变慢,最后停止
2、距离越远、速度越大,速度由距离决定:速度=(目标值-当前值)/缩放系数
Bug:速度取整(使用Math方法),不然会闪
向上取整。Math.ceil(iSpeed)
向下取整。Math.floor(iSpeed)
还是对速度作文章:

1.   /**
2.   * 运动框架-4-缓冲动画
3.   */
4.   functionstartMove ( element,iTarget ) {
5.        clearInterval ( timer );
6.        timer = setInterval ( function ( ) {
7.        // 因为速度要动态改变,所以必须放在定时器中
8.               var iSpeed = ( iTarget - element.offsetLeft ) / 10; // (目标值 - 当前值) / 缩放系数 = 速度
9.               ISpeed = ISpeed  > 0 ? Math.ceil ( iSpeed ) : Math.floor ( iSpeed ) ; // 速度取整
10.             if (element.offsetLeft === iTarget ) // 结束运动
11.                    clearInterval ( timer ) ;
12.              } else {
13.                      element.style.left = element.offsetLeft + iSpeed + "px";
14.              }
15.        }, 30);
16.  }

做到这里,(速度取到某些值会无法停止)这个Bug就自动解决啦!

(三)多物体运动

思考:如何实现多物体运动?
1、每个div一个定时器,直接使用element.timer把定时器变成对象上的一个属性。
2、参数的传递:物体/目标值
比较简单把上面框架的进行如下更改:timer-->element.timer
就这样就行啦!

(四)任意值变化

我们来给div加个1px的边框。boder :1px solid #000
然后来试试下面的代码

1.  setInterval ( function() {
2.      oDiv.style.width=oDiv.offsetWidth‐1+"px";
3.  },30)

嗯,神奇的事情发生了!what?我设置的不是宽度在减吗?怎么尼玛增加了! 
一起找找资料,看看文档,原来offset这一系列的属性都会存在被其他属性干扰的问题。
好吧,既然不能用,那么我们就顺便把任意值变化给做了吧。

第一步:获取实际样式

使用offsetLeft..等获取样式时, 若设置了边框, padding, 等可以改变元素宽度高度的属性时会出现BUG..
通过查找发现element.currentStyle(attr)可以获取计算过之后的属性。
但是因为兼容性的问题,需封装getStyle函数。(万恶的IE)
当然配合CSS的box-sizing属性设为border-box可以达到一样的效果 ? (自认为,未验证)。

1.   /**
2.    * 获取实际样式函数
3.    * @param {HTMLElement} element 需要寻找的样式的html节点
4.    * @param {String]}attr在对象中寻找的样式属性
5.    * @returns {String} 获 取 到 的 属 性
6.    */
7.    functiongetStyle(element,attr){
8.        //IE写法
9.        if ( element.currentStyle ) { 
10.            return element.currentStyle[attr];
11.      // 标准
12.      } else {
13.            return getComputedStyle ( element , false ) [attr]; 
14.       }
15.   }
第二步:改造原函数

添加参数,attr表示需要改变的属性值
更改element.offsetLeft为getStyle(element, attr)。
需要注意的是:getStyle(element, attr)不能直接使用,因为它获取到的字符串,例:10px。
变量iCurrent使用parseInt(),将样式转成数字。
element.style.left为element.style[attr]。

1.   /**
2.    * 运动框架‐4‐任意值变化
3.    * @param {HTMLElement} element 运 动 对 象
4.    *@param{string} attr 需要改变的属性。
5.    * @param {number} iTarget 目 标 值
6.    */
7.    functionstartMove ( element , attr , iTarget ) { 
8.         clearInterval(element.timer);
9.         element.timer = setInterval ( function ( ) {
10.       //因为速度要动态改变,所以必须放在定时器中
11.       variCurrent=0;
12.       iCurrent=parseInt(getStyle(element,attr));//实际样式大小
13.             variSpeed=(iTarget‐iCurrent)/10;//(目标值‐当前值)/缩放系数=速度
14.             iSpeed=iSpeed>0?Math.ceil(iSpeed):Math.floor(iSpeed);//速度取整
15.             if(iCurrent===iTarget){//结束运动
16.                    clearInterval(element.timer);
17.              } else {
18.                    element.style[attr]=iCurrent+iSpeed+"px";
19.              };
20.         },30);
21.    };

试一试,这样是不是就可以了呢?
还记得上面我们写的透明度变化吗? 再试试
果然还是不行, (废话,你见过透明度有"px"单位的么? - -白眼 )

第三步:透明度兼容处理

思考:需要对那些属性进行修改?
判断attr是不是透明度属性opacity 。
对于速度进行处理。
为透明度时,由于获取到的透明度会是小数,所以需要 * 100
并且由于计算机储存浮点数的问题,还需要将小数,进行四舍五入为整数。使用:
Math.round(parseFloat(getStyle(element, attr)) * 100)。
否则,继续使用默认的速度。
对结果输出部分进行更改。
判断是透明度属性,使用透明度方法
否则,使用使用默认的输出格式。

1.   /**
2.    * 运动框架‐5‐兼容透明度
3.    * @param {HTMLElement} element 运 动 对 象
4.    *@param{string} attr 需要改变的属性。
5.    * @param {number} iTarget 目 标 值
6.    */
7.    function  startMove ( element , attr , iTarget ) {
8.          clearInterval ( element.timer );
9.          element.timer = setInterval ( function ( ) {
10.              //因为速度要动态改变,所以必须放在定时器中
11.              var iCurrent=0;
12.              if ( attr === "opacity" ){//为透明度时执行。
13.                    iCurrent = Math.round ( parseFloat ( getStyle ( element , attr ) ) * 100 );
14.              } else { //默认情况
15.                    iCurrent=parseInt(getStyle(element,attr));//实际样式大小 }
16.              variSpeed=(iTarget‐iCurrent)/10;//(目标值‐当前值)/缩放系数=速度
17.              iSpeed=iSpeed>0?Math.ceil(iSpeed):Math.floor(iSpeed);//速度取整
18.              if(iCurrent===iTarget){//结束运动
19.                    clearInterval(element.timer);
20.               } else {
21.                      if ( attr==="opacity"){//为透明度时,执行
22.                          element.style.filter="alpha(opacity:"+(iCurrent+iSpeed)+")";//IE
23.                          element.style.opacity=(iCurrent+iSpeed)/100;//标准
24.                      }else{//默认
25.                          element.style [ attr ] =iCurrent+iSpeed+"px";
26.                      }
27.                  }
28.              },30);
29.     }

到这里,这个运动框架就基本上完成了。但是,我们是追求完美的不是吗?
继续进化!

(五)链式动画

链式动画:顾名思义,就是在该次运动停止时,开始下一次运动。
如何实现呢?
使用回调函数:运动停止时,执行函数
添加func形参(回调函数)。
在当前属性到达目的地时iCurrent === iTarget,判断是否有回调函数存在,有则执行。
if (iCurrent === iTarget) {//结束运动
clearInterval(element.timer);
if (func) {
func();//回调函数
}
}
good,链式动画完成!距离完美还差一步!

(六)同时运动

思考:如何实现同时运动?
使用JSON传递多个值
使用for in循环,遍历属性与值。
定时器问题!(运动提前停止)
在循环外设置变量,假设所有的值都到达了目的值为true
在循环中检测是否到达目标值,若没有值未到则为false
在循环结束后,检测是否全部达到目标值.是则清除定时器
实现:
删除attr与iTarget两个形参,改为json
在函数开始时,设置一个标记var flag = true; //假设所有运动到达终点.
在定时器内使用for in,遍历属性与目标,改写原来的attr与iTarget,为json的属性与值
修改运动终止条件,只有每一项的实际属性值iCurrent,等于目标值json[attr]时,flag才为true。清除定时器,判断是否回调。
否则,继续执行代码,直到所有属性值等于目标值。

完美运动框架(我自己有更改)
<!DOCTYPE html>
<html lang="en">
<head>
	<meta charset="UTF-8">
	<title>animate</title>
</head>
<body>
	<div id="ani" style="width:100px;height:100px;background-color:red;opacity:0.8;"></div>
	<div id="ani2" style="width:100px;height:100px;background-color:blue;opacity:0.8;"></div>
</body>
<script type="text/javascript">
	var ani = document.getElementById('ani');
	var json = {"opacity":50,"width":500,"height":300};
	var func = function(){
		startMove(ani2,json);
	}
	startMove(ani,json,func);
	/**
	* 获取实际样式函数
	*@param {HTMLElement} element 需要寻找的样式的html节点
	*@param {String]}attr在对象中寻找的样式属性
	* @returns {String} 获 取 到 的 属 性
	*/
	function getStyle(element,attr){
		//IE写法
		if ( element.currentStyle ) {
			return element.currentStyle[attr];
		//标准
		} else {
		    return getComputedStyle(element,false)[attr];
		}
	}
	/**
	* 完美运动框架
	*@param{HTMLElement}element 运动对象
	*@param{JSON}  json 属性:目标值
	* @property{String}attr 属性值
	* @config {Number}target 目标值
	*@param{function} func 可选,回调函数,链式动画。
	*/
	function startMove ( element , json , func ) {
		var flag=true;//假设所有运动到达终点.
		clearInterval(element.timer);
		element.timer=setInterval(function(){
			for(var attr in json){
				//1.取当前的属性值。
				var iCurrent=0;
				if(attr==="opacity"){//为透明度时执行。
					iCurrent=Math.round(parseFloat(getStyle(element,attr))*100);
				}else{//默认情况
					iCurrent=parseInt(getStyle(element,attr));//实际样式大小
				}
				//2.算运动速度,动画缓冲效果
				var iSpeed = (json[attr] - iCurrent)/10;//(目标值‐当前值)/缩放系数=速度
				iSpeed=iSpeed > 0 ? Math.ceil(iSpeed):Math.floor(iSpeed);//速度取整
				//3.未到达目标值时,执行代码
				if(iCurrent!=json[attr]){
					flag=false;//终止条件
					if(attr==="opacity"){//为透明度时,执行
						element.style.filter="alpha(opacity:"+(iCurrent+iSpeed)+")";//IE
						element.style.opacity=(iCurrent+iSpeed)/100;//标准
					}else{//默认
						element.style[attr]=iCurrent+iSpeed+"px";
					}
				}else{
					flag=true;
				}
				//4. 运动终止,是否回调
				if(flag){
					clearInterval(element.timer);
					if(func){
						func();
					}
				}
			}
		},30);
	}
</script>
</html>


作者:逍遥至尊灬寳
链接:https://www.jianshu.com/p/7f94a9c851d3
來源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值