FED

©FrontEndDev.org
2015 - 2024
web@2.23.0 api@2.21.1

组件分享-javascript 运动框架

JS原生运动组件

Why?

大家都知道JQuerry有animate方法来给DOM元素进行运动,CSS3中也有transition、transform来进行运动。而使用原生的Javascript来控制元素运动,需要写很多运动的细节以及兼容。

然而,当你的BOSS不让你使用庞大的JQ框架,而且你开发的产品也需要在一些不兼容CSS3的浏览器运行的时候,你是否觉得每次都要开个定时器来琢磨运动该怎么进行,是件很费力的事情呢?

那么福利来了,笔者最近总结了两个常用的运动框架,并将其写成组件, 只要按照下面的方法调用,即可方便使用。

1.在你的页面中引入js

<script src="Mover.js"></script>

2.在你的js代码中创建Mover对象

<script>
window.onload=function(){
	var mover = new Mover();
};
</script>

3.开始使用mover!

用法说明:笔者写的组件包含了两种运动框架供使用,一种是基于速度的;一种是基于时间的,让我们来先看看基于速度的运动框架的用法

startMoveBySpeed(obj, json, endFn);

  • 参数obj : 传入要运动的对象

  • 参数json : 以json的形式传入要运动的属性,包括left、top、width、height等以px为单位的属性和透明度opacity,他们的值是运动的终点

  • 参数endFn(可选) : 结束运动后要执行的方法

<script>
//基于速度的运动框架用法
window.onload=function(){
	//得到你要运动的元素
	var oDiv = document.getElementsByTagName('div')[0];
	
	//使用运动框架
	var mover = new Mover();
	oDiv.onclick=function(){	
		mover.startMoveBySpeed(this,{'left':200,'width':300,'opacity':50});
	//使oDiv的left运动到200px,宽度变为300px,透明度变为50%
	}
	
};
</script>

让我们来看看另外一种基于时间的运动框架

startMoveByTime(obj, json, options, endFn )

  • 参数obj : 传入要运动的对象

  • 参数json : 以json的形式传入要运动的属性,包括left、top、width、height等以px为单位的属性和透明度opacity,他们的值是运动的终点

  • 参数options : 以json的形式传入传入运动的总时间和运动方式,如: { 'times' : 1000,//运动总时间为1s 'fx' : 'linear' // 运动形式为匀速 } 当options传入参数为空json{}时,就使用默认设置(运动时间500ms,运动形式为匀速)

  • 参数endFn(可选) : 结束运动后要执行的方法

//基于事件的运动框架用法
window.onload=function(){
	//得到你要运动的元素
	var oDiv = document.getElementsByTagName('div')[0];
	//使用运动框架
	var mover = new Mover();
	oDiv.onclick=function(){
		mover.startMoveByTime(
			this, 
			{'left':500,'top':200}, 
			{'times':1000,'fx':'easeIn'}, 
			function(){
				mover.startMoveByTime(this,{'opacity':20},{});
		});
		//使oDiv的left变为500px,高度变为200px,结束上述运动后再使透明度变为20%
	}
}

区别

现在来说说两种方式的区别吧,使用第一种方式进行运动时,元素的各项需要改变的属性的运动速度相同,而由于每项属性起点到终点的距离不一样,所以各项属性到达运动终点的时间也不一样。而第二种运动直接固定了运动总时间相同,所以所有传入参数的属性一起改变、一起终止。

源码

以下是Mover.js组件的代码,欢迎指正,后面有时间也会将弹性运动、碰撞运动、重力运动一起写入框架

/*
	js原生运动组件
*/

//Mover构造函数
function Mover(){
	this.setting = {
		'times' : 500,
		'fx' : 'linear'
	}
}

//获取当前样式
Mover.prototype.getStyle = function(obj,attr)
{
	return obj.currentStyle ? obj.currentStyle[attr] : getComputedStyle(obj)[attr];
}

//获取当前时间
Mover.prototype.now = function(){
		return (new Date()).getTime();
}

//速度版运动框架
Mover.prototype.startMoveBySpeed = function (obj,json,fnEnd)
{
	clearInterval(obj.timer);
	_this = this;
	obj.timer = setInterval(function(){
		obj.bStop = true;
		for(var attr in json)
		{
			var cur = 0;
			var speed = 0;
			if(attr === 'opacity')
			{
				cur = _this.getStyle(obj,attr);
				cur = Math.round( parseFloat(cur*100) );
			}
			else
			{
				cur = parseInt(_this.getStyle(obj,attr));
			}

			var speed = (json[attr]-cur)/8;

			speed = speed ? Math.ceil(speed):Math.floor(speed);

			if(cur !== json[attr])
			{
				obj.bStop = false;
			}

			if(attr === 'opacity')
			{
				obj.style.opacity = (cur+speed)/100;
				obj.style.filter = 'Alpha(opacity:'+(cur+speed)+')';
			}
			else
			{
				obj.style[attr] = (cur+speed)+'px';
			}
		}

		if(obj.bStop)
		{
			clearInterval(obj.timer);
			fnEnd && fnEnd.call(obj);
		}

	},20);
}

//时间版运动框架
Mover.prototype.startMoveByTime = function(obj,json,options,endFn){
	//对于时间版框架来说,初始值b是固定的,所以写在定时器外面
	var _this = this;
	//默认设置
	extend(_this.setting,options);

	var iCur = {};
	//获取当前值
	for(attr in json)
	{
		iCur[attr] = 0;

		if(attr === 'opacity')
		{
			iCur[attr] = Math.round( parseFloat( _this.getStyle(obj,attr) )*100 );
		}else{
			iCur[attr] = parseInt( _this.getStyle(obj,attr) );
		}

	};
	
	var iStartTime = _this.now();
	clearInterval(obj.timer);
	obj.timer = setInterval(function(){
		var iCurTime = _this.now();
		var t = _this.setting.times-
		Math.max(0,iStartTime-iCurTime+_this.setting.times);
		for(attr in json)
		{	
			/*
				Tween[fx]函数4个参数
				t:current  time(当前时间)
				b:beginning  value(初始值)
				c: change  in  value(变化量)
				d:duration(持续时间)
				return  (目标点)	
			*/
			var value = _this.Tween[_this.setting.fx](
				t,  					//t  0~times
				iCur[attr],				//b
				json[attr]-iCur[attr],	//c
				_this.setting.times 					//d
			 );
			if(attr === 'opacity')
			{
				obj.style[attr] =  parseFloat(value/100);
				obj.style.filter = 'alpha(opacity:'+value+')';
			}else{
				obj.style[attr] = value + 'px';
			}

		}

		if( t === _this.setting.times )
		{
			clearInterval(obj.timer);
			endFn && endFn.call(obj);
		}
	},13);
		
}
//覆盖默认设置
function extend(child,father){
	for(var attr in father)
	{
		child[attr] = father[attr];
	}
}
//Tween运动算法
Mover.prototype.Tween = {
	/*
		4个参数
		t:current  time(当前时间)
		b:beginning  value(初始值)
		c: change  in  value(变化量)
		d:duration(持续时间)
		return  (目标点)

	*/

	linear: function (t, b, c, d){  //匀速
		return c*t/d + b;
	},
	easeIn: function(t, b, c, d){  //加速曲线
		return c*(t/=d)*t + b;
	},
	easeOut: function(t, b, c, d){  //减速曲线
		return -c *(t/=d)*(t-2) + b;
	},
	easeBoth: function(t, b, c, d){  //加速减速曲线
		if ((t/=d/2) < 1) {
			return c/2*t*t + b;
		}
		return -c/2 * ((--t)*(t-2) - 1) + b;
	},
	easeInStrong: function(t, b, c, d){  //加加速曲线
		return c*(t/=d)*t*t*t + b;
	},
	easeOutStrong: function(t, b, c, d){  //减减速曲线
		return -c * ((t=t/d-1)*t*t*t - 1) + b;
	},
	easeBothStrong: function(t, b, c, d){  //加加速减减速曲线
		if ((t/=d/2) < 1) {
			return c/2*t*t*t*t + b;
		}
		return -c/2 * ((t-=2)*t*t*t - 2) + b;
	}
}