js动画框架设计

Section One

游戏动画,Flash动画里一个比较重要的概念是帧频,即每秒播放多少帧动画,一般动画是30帧/秒,单位为fps(frames per second)。

对于匀速运动来说:如果一个动画的持续时间duration为500ms,帧频frequence为30fps,则总帧数frames为(500/1000)*30 = 15,即该动画过程有15个“画面”,每走一帧,都计算出一个画面:画面当前位置 = 开始位置 + (当前帧/总帧数)(结束位置-开始位置),如果当前帧是最后一帧,则动画结束。其中setTimeout或setInterval每隔(500/15)ms时间段调用一次函数,即计算一个画面。

来看下线性运动Linear缓动算法函数,t表示当前帧,b表示开始位置,c表示发生偏移的距离值,即当前位置-开始位置,d表示总帧数,符合上面的推理解释,对于其他的算法函数,道理其实都是一样,只不过在运动过程中的曲线不同,有些呈现抛物线,有些呈现线性指数,对于数学感兴趣的可以研究下这些算法函数,我也是略知皮毛:

Linear: function (t, b, c, d) {
	return c * t / d + b;
}

清楚了以上问题后,对于js的动画框架设计,就迎刃而解了,废话不多说,来个 demo 先。

Section Two

代码总体结构,具体说明看注释,需注意的问题:1)在私有作用域里定义的变量,要在外部能访问到,需挂在window全局对象下,如window.Anim = Anim;2)动画元素需要设置定位position属性;3)传入的外边距参数需要驼峰式命名,并且当同时设置targetPos(元素目标位置)和外边距时,外边距的值会覆盖targetPos的值,如marginLeft的值会覆盖targetPos.left的值,因为外边距实现动画的原理也是利用元素的left、top值:

(function(window) {
	/*
	 * 工具对象
	 * 包含基本的dom操作,event操作
	 */
	var util = {};
	util.dom = {
		// 获取元素计算样式
		getPropValue: function(element, propName) {
		},
		// 设置透明度
		setOpacity: function(obj, num) {
			document.all ? obj.filters.alpha.opacity = num : obj.style.opacity = num / 100;
	    }
	    // ......
	};
	util.event = {
		// 获取事件对象
		getEvent: function(event) {
		},
		// 获取事件源目标
		getTarget: function(event) {
		},
		// 注册事件
		addEvent: function(element, event, handler) {
		},
		// 删除事件
		removeEvent: function(element, event, handler) {
		},
		// 阻止默认行为
		preventDefault: function(event) {
		},
		// 阻止事件冒泡
		stopPropagation: function(event) {
		}
		// ......
	};

	/*
	* 动画缓动函数
	*/
	var Tween = {
	    Linear: function (t, b, c, d) { return c * t / d + b; },
	    Quad: {
	        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;
	        },
	        easeInOut: function (t, b, c, d) {
	            if ((t /= d / 2) < 1) return c / 2 * t * t + b;
	            return -c / 2 * ((--t) * (t - 2) - 1) + b;
	        }
	    },
	    // ...
	};

	/*
	 * 核心动画
	 * @elem 要执行动画的元素
	 * @options left、top、opacity、width、height、border、marginLeft、marginRight、marginTop、marginBottom
	 */
	function Anim(elem, options) {
		this.elem = elem;
		this.options = options;
		// 默认样式属性
		this.defaults = {
		};
	}
	Anim.prototype = {
		constructor: Anim,
		// 初始化动画
		init: function() {
			this.isStart = false;
			this.isStop = false;
			this.isComplete = false;
			this.isBack = false;
			this.start();
		},
		// 初始化数据
		before: function() {
		},
		// 开始动画
		start: function() {
		},
		// 动画过程
		run: function() {
			this.before();
			// 动画参数匹配
			this.match();
			// 原路返回
			if (this.isBack) {
				// ...
			}
			if (this.isStart) {
				this.onStart.call(this.elem);
				// 计算动画
				this.count();
			}
		},
		// 匹配动画的参数
		match: function() {
		},
		// 计算动画
		count: function() {
		},
		// 原路返回
		back: function() {
		},
		// 停止动画
		stop: function() {
		},
		// 重置元素
		reset: function() {
		}
	};

	// 全局使用
	if (!window.util) {
		window.util = util;
	}
	if (!window.Anim) {
		window.Anim = Anim;
	}
	if (!window.Tween) {
		window.Tween = Tween;
	}

})(window);

Last

使用非常简单,初始化参数对象,然后调用构造函数Anim即可:

// 动画参数设置
var options = {
	duration: 2000, // 动画持续时间
	frequence: 30, // 帧频
	tweenFunc: Tween.Linear, // 动画缓动函数
	targetPos: {left: 400, top: 300}, // 元素目标位置
	opacity: 40, // 透明度(可选)
	width: 80, // 宽度(可选)
	height: 80, // 高度(可选)
	// marginTop: 100, // 上边距(可选)
	border: '2px solid red', // 边框(可选)
	// 动画开始callback(可选)
	onStart: function() {
		// this指向当前动画元素
	},
	// 动画停止callback(可选)
	onStop: function() {
		// this指向当前动画元素
	},
	// 动画完成callback(可选)
	onComplete: function() {
		// this指向当前动画元素
	}
};
var anim = new Anim(animElem, options);

最后附上 源代码 以及easing算法函数的 demo

认真对待每一个人,每一件事,会有意想不到的收获!