前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >JavaScript的动画组件

JavaScript的动画组件

作者头像
meteoric
发布2019-02-25 16:50:50
7910
发布2019-02-25 16:50:50
举报
文章被收录于专栏:游戏杂谈

可自定义动画执行时长、动画执行频率、动画的类型。

运行示例代码:

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd"> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> <title>动画组件</title> <style type="text/css"> #motion { background: #ccc; width: 500px; height: 500px; position: relative; overflow: hidden; float: left; } #motion #ball { width: 40px; height: 40px; background: red; position:absolute; *font-size: 0px; } #motion #flag { background: yellow; width: 4px; height: 4px; position:absolute; *font-size: 0px; } #status { float: left; margin: 0px 10px; } #status .item { margin: 1em 0px; } #chart { *zoom: 1; margin: 0px 20px 10px 0px;; } #chart { position: relative; width: 250px; height: 250px; background: #eaeaea; } #chart div { width:4px; height:4px; cursor:pointer; background: green; position: absolute; *font-size: 0px; } #chart:selected { background: red; } </style> </head> <body> <div id="container"> <div id="motion"></div> <div id="status"> <div class="item"> 时长(毫秒): <select id="duration"> <option value="100">100</option> <option value="200">200</option> <option value="300" selected="selected">300</option> <option value="500">500</option> <option value="800">800</option> <option value="1000">1000</option> <option value="1200">1200</option> <option value="1500">1500</option> <option value="1800">1800</option> <option value="2000">2000</option> </select> </div> <div class="item"> FPS: <select id="fps"> <option value="5">5</option> <option value="10">10</option> <option value="20">20</option> <option value="24" selected="selected">24</option> <option value="35">35</option> <option value="50">50</option> <option value="80">80</option> </select> </div> <div class="item"> 动画类型: <select id="type"> <option value="simple">simple</option> <option value="regularEaseIn">regularEaseIn</option> <option value="regularEaseOut">regularEaseOut</option> <option value="regularEaseInOut">regularEaseInOut</option> <option value="backEaseIn">backEaseIn</option> <option value="backEaseOut">backEaseOut</option> <option value="backEaseInOut">backEaseInOut</option> <option value="bounceEaseOut">bounceEaseOut</option> <option value="bounceEaseIn">bounceEaseIn</option> <option value="bounceEaseInOut">bounceEaseInOut</option> <option value="strongEaseIn">strongEaseIn</option> <option value="strongEaseOut">strongEaseOut</option> <option value="strongEaseInOut">strongEaseInOut</option> <option value="elasticEaseIn">elasticEaseIn</option> <option value="elasticEaseOut">elasticEaseOut</option> <option value="elasticEaseInOut">elasticEaseInOut</option> </select> </div> <div class="item"> <div>运动轨迹:</div> <div id="chart"></div> </div> <div class="item"> <div id="result"></div> </div> </div> </div> <div id="idChart" style="border:1px solid #000;height:200px; width:550px; margin-top:20px; position:relative; clear:both; margin-top:10px;"> </div> </body> </html> 预览代码

动画组件的核心代码:

代码语言:javascript
复制
/**
 * @author ZhangYi
 */

/**
 * 
 * @param {HTMLElement|String} el HTMLElement|DOM对象
 * @param {Object|null} attributes 执行动画时需要更新的属性值
 * @param {Number} duration	动画执行时间
 * @param {String|Null} transition 动画算子函数
 */
function Fx(el, attributes, duration, func) {
	this._obj = Fx.DOM.get(el);
	this._attributes = attributes || {};
	this._duration = duration || 10;
	
	if(!!func && typeof(Fx.transitions[func]) === "function") {
		this._animateType = func;
	} else {
		this._animateType = "simple";
	}
	
	this._func = Fx.transitions[this._animateType];
	
	this.isPlayed = false;
	this.isLoop = false;
	
	this._intervalTimer = null;
	this._startTime = 0;
	this._moveTime = 0;
	
	//动画执行频率
	this._freq = 24;
	
	this.units = {};
	this.frame = {};
	this.endAttr = {};
	this.startAttr = {};
	
	//开始执行动画
	this.onMotionStart = function() {};
	
	//动画正在执行
	this.onMotionChange = function() {};
	
	//动画执行结束
	this.onMotionStop = function() {};
	
	return this;
}

Fx.prototype = {
	//启动定时器执行动画
	_runTime : function() {
		clearInterval(this._intervalTimer);
		
		if (this.isPlayed) {
			this._moveTime = new Date().getTime() - this._startTime;
			
			this._playTime( (this._moveTime) / 1000 );
			
			var $pointer = this;
			var delay = Math.floor(1000 / this._freq);
			
			this._intervalTimer = setInterval(function() {$pointer._runTime.apply($pointer);}, delay);
		}
	},
	/**
	 * 定时器启动时执行的动画
	 * @param {Number} time
	 */
	_playTime : function(time) {
		var _isEnd = false;
		if (time > this._duration) {
			time = this._duration;
			_isEnd = true;
			this.frame = Fx.objectClone(this.endAttr);
		} else {
			for(attr in this.startAttr){ 
				if( '[object Array]' === Object.prototype.toString.apply(this.startAttr[attr]) ){
					this.frame[attr] = [];
					for(var i=0; i < this.startAttr[attr].length; i++){
						this.frame[attr][i] = this._ease(this.startAttr[attr][i], this.endAttr[attr][i], time);
					}
				} else {
					this.frame[attr] = this._ease(this.startAttr[attr], this.endAttr[attr], time);
				}
			}
		}
		
		this.setAttributes();
		this.onMotionChange.apply(this, arguments);
		
		// 判断是否播放结束
		if (_isEnd) {
			this.isPlayed = false;
			
			this.onMotionStop.apply(this);
			
			// 循环播放
			if (this.isLoop) {
				this.isPlayed = true;
				this.frame = Fx.objectClone(this.startAttr);
				this._reloadTimer();
			}
			
			if (window.CollectGarbage) {
				CollectGarbage();
			}
		}
	},
	//重新计算动画开始时间
	_reloadTimer : function() {
		this._startTime = +new Date();
	},
	/**
	 * 调用动画算子函数,返回执行结果
	 * 
	 * @param {Number} start
	 * @param {Number} end
	 * @param {Number} time
	 */
	_ease : function(start, end, time) {
		return this._func(time, start, end - start, this._duration);
	},
	/**
	 * 设置动画执行的频率 1秒执行多少次
	 */
	setFrequency : function(freq) {
		this._freq = freq;
	},
	/**
	 * 开始播放动画
	 * @param {Boolean} loop
	 */
	start : function(loop) {
		this.getAttributes();
		this._reloadTimer();
		
		this._start.apply(this, arguments);
	},
	/**
	 * 共用的开始执行动画函数
	 * @param {Boolean} loop
	 */
	_start : function(loop) {
		this.isPlayed = true;
		this.isLoop = loop ? true : false;
		this._runTime();
		this.onMotionStart.apply(this);
	},
	/**
	 * 继续播放
	 * @param {Boolean} loop
	 */
	play : function(loop) {
		this._startTime = +new Date() - this._moveTime;
		
		this._start.apply(this, arguments);
	},
	/**
	 * 重新播放
	 * @param {Boolean} loop 是否循环播放动画
	 */
	rePlay : function(loop) {
		this._reloadTimer();
		
		this._start.apply(this, arguments);
	},
	/**
	 * 暂停播放动画
	 */
	pause : function() {
		this.isPlayed = false;
		this.isLoop = false;
	},
	/**
	 * 停止播放动画
	 */
	stop : function() {
		this.pause();
		
		this._playTime(this._duration + 0.1);
	},
	getAttributes : function() {
		for(var attr in this._attributes) {
			
			if(!this._attributes.hasOwnProperty(attr)) {
				continue;
			}
			
			switch(attr){
				case 'color':
				case 'borderColor':
				case 'border-color':
				case 'backgroundColor':
				case 'background-color':
					this.startAttr[attr] = Fx.parseColor(this._attributes[attr].from || Fx.DOM.getStyle(this._obj, attr));
					this.endAttr[attr] = Fx.parseColor(this._attributes[attr].to);
					break;
				case 'scrollTop':
				case 'scrollLeft':
					var el = (this._obj === document.body) ? (/AppleWebKit/i.test(navigator.userAgent) ? document.body : document.documentElement) : this._obj;
					
					var start = this._attributes[attr].from || el[attr];
					var end = this._attributes[attr].to;
					if(end && /^([+-])(\d+)$/.test(this._attributes[attr].to)) {
						if("+" === RegExp['$1']) {
							end = start + RegExp['$2']*1;
						} else {
							end = start - RegExp['$2']*1;
						}
					}
					
					this.startAttr[attr] = start;
					this.endAttr[attr] = end;
					
					break;
				default:
					var start = parseFloat(this._attributes[attr].from);
					var end = parseFloat(this._attributes[attr].to);
					var units = this._attributes[attr].units || "";
					
					if(attr !== "opacity" && !units) {
						units = "px";
					}
					
					if(!start) {
						start = parseFloat(Fx.DOM.getStyle(this._obj, attr)) || 0;//会转换成px
						
						if(units != "px" && document.defaultView){
							Fx.DOM.setStyle(this._obj, attr, start + units);
						}
					}
					
					if(end && /^([+-])(\d+)$/.test(this._attributes[attr].to)) {
						if("+" === RegExp['$1']) {
							end = start + RegExp['$2']*1;
						} else {
							end = start - RegExp['$2']*1;
						}
					}
					
					this.units[attr] = units;
					this.endAttr[attr] = end;
					this.startAttr[attr] = start;
					
					break;
			}
		}
	},
	setAttributes : function() {
		for(var attr in this.frame){
			switch(attr) {
				case 'opacity':
					Fx.DOM.setStyle(this._obj, attr, this.frame[attr]);
					break;
				case 'scrollLeft':
				case 'scrollTop':
					var el = (this._obj === document.body) ? (/AppleWebKit/i.test(navigator.userAgent) ? document.body : document.documentElement) : this._obj;
					el[attr] = this.frame[attr];
					break;
				case 'color':
				case 'borderColor':
				case 'border-color':
				case 'backgroundColor':
				case 'background-color':
					var rgb = 'rgb('+Math.floor(this.frame[attr][0])+','+Math.floor(this.frame[attr][1])+','+Math.floor(this.frame[attr][2])+')';
					Fx.DOM.setStyle(this._obj, attr, rgb);
					break;
				default:
					Fx.DOM.setStyle(this._obj, attr, this.frame[attr] + this.units[attr]);
					break;
			}
		}
	},
	//获取当前动画已经执行的百分比
	getPercent : function() {
		var percent = this._moveTime/(this._duration*1000);
			percent = Math.min(percent, 1);
			
		return Math.floor(percent*100) + "%";
	}
}

Fx.DOM = {
	/**
	 * 获取DOM节点
	 * @param {String} 需要查找的元素的ID
	 * @return {Element} 返回DOM对象
	 */
	get: function(id){
		return (typeof id === "string") ? document.getElementById(id) : id;
	},
	/**
	 * 获取元素的指定样式的属性
	 * @param {Element} el 目标元素
	 * @param {String} prop 属性名
	 * @return {Number} 该元素的指定属性的属性值
	 */
	getStyle: function(el, prop){
		prop = this.toCamelCase(prop);
		var view = document.defaultView;
		if(view && view.getComputedStyle){
			return view.getComputedStyle(el, "")[prop] || null;
		}else{
			if(prop == 'opacity'){
				var opacity = el.filters['alpha'] ? el.filters['alpha']['opacity'] : NaN;
				return isNaN(opacity) ? 1 : (opacity ? opacity / 100 : 0);
			}
			return el.currentStyle[prop] || null;
		}
	},
	/**
	 * 设置元素的样式属性
	 * @param {Element} el 目标元素
	 * @param {String} prop  属性名
	 * @param {String} value 属性值
	 */
	setStyle: function(el, prop, value){
		if(prop == 'opacity'){
			el.style.filter = "alpha(opacity=" + value * 100 + ")";
			el.style.opacity = value;
		} else {
			prop = this.toCamelCase(prop);
			if(prop === "height" || prop === "width") {//避免出现负值
				value = Math.max(0, parseInt(value, 10));
			}
			el.style[prop] = value;
		}
	},
	/**
	 * 将一个CSS属性转换为驼峰形式,例如(font-size --> fontSize)
	 * @param {String} 需要转换的CSS属性
	 * @return {String} 转换为驼峰式后的字符串
	 */
	toCamelCase : (function(){
		var cache = {};
		
		return function(str){
			if(!cache[str]){
				var parts = str.split('-'), camel = parts[0];
				if(parts.length > 1){
					for(var i=1, len=parts.length; i < len; i++){
						camel += parts[i].charAt(0).toUpperCase() + parts[i].substring(1);
					}
				}
				return cache[str] = camel;
			} else {
				return cache[str];
			}
		}
	})()
}
/**
 * 解析颜色值, 支持16进制与RGB颜色值(#FFFFFF, #FFF, rgb(255, 0, 0))
 * @param {String} 颜色值字符串
 * @return {Array} RGB的颜色数组,默认返白色
 */
Fx.parseColor = (function(){
	var hex6 = (/^#?(\w{2})(\w{2})(\w{2})$/);		
	var hex3 = (/^#?(\w{1})(\w{1})(\w{1})$/);	
	var rgb = (/^rgb\((\d{1,3}),\s*(\d{1,3}),\s*(\d{1,3})\)$/); 
						   
	return function(str){      
		var color = str.match(hex6);
		if(color && color.length == 4){
			return [parseInt(color[1], 16), parseInt(color[2], 16), parseInt(color[3], 16)];
		}
		
		color = str.match(rgb);
		if(color && color.length == 4){
			return [parseInt(color[1], 10), parseInt(color[2], 10), parseInt(color[3], 10)];
		}
	
		color = str.match(hex3);
		if(color && color.length == 4){
			return [parseInt(color[1] + color[1], 16), parseInt(color[2] + color[2], 16), parseInt(color[3] + color[3], 16)];
		}
		
		return [255, 255, 255];
	}
})();

/**
 * 动画的算子函数
 */
Fx.transitions = {
	//linear
	linearEase : function(t, b, c, d) {
		return c*t/d + b;
	},
	simple : function(time, startValue, changeValue, duration) {
		return changeValue * time / duration + startValue;
	},
	//circ
	circEaseIn : function(t, b, c, d) {
		return -c * (Math.sqrt(1 - (t/=d)*t) - 1) + b;
	},
	circEaseOut : function(t, b, c, d) {
		return c * Math.sqrt(1 - (t=t/d-1)*t) + b;
	},
	circEaseInOut : function(t, b, c, d) {
		if ((t/=d/2) < 1) {
			return -c/2 * (Math.sqrt(1 - t*t) - 1) + b;
		}

		return c/2 * (Math.sqrt(1 - (t-=2)*t) + 1) + b;
	},
	//cubic
	cubicEaseIn : function(t, b, c, d) {
		return c*(t/=d)*t*t + b;
	},
	cubicEaseOut : function(t, b, c, d) {
		return c*((t=t/d-1)*t*t + 1) + b;
	},
	cubicEaseInOut : function(t, b, c, d) {
		if ((t/=d/2) < 1) {
			return c/2*t*t*t + b;
		}
		return c/2*((t-=2)*t*t + 2) + b;
	},
	//expo
	expoEaseIn : function(t, b, c, d) {
		return (t==0) ? b : c * Math.pow(2, 10 * (t/d - 1)) + b;
	},
	expoEaseOut : function(t, b, c, d) {
		return (t==d) ? b+c : c * (-Math.pow(2, -10 * t/d) + 1) + b;
	},
	expoEaseInOut : function(t, b, c, d) {
		if (t==0) return b;
		if (t==d) return b+c;
		if ((t/=d/2) < 1) return c/2 * Math.pow(2, 10 * (t - 1)) + b;
		return c/2 * (-Math.pow(2, -10 * --t) + 2) + b;
	},	
	//quad
	quadEaseIn : function(t, b, c, d) {
		return c*(t/=d)*t + b;
	},
	quadEaseOut : function(t, b, c, d) {
		return -c *(t/=d)*(t-2) + b;
	},
	quadEaseInOut : function(t, b, c, d) {
		if ((t/=d/2) < 1) return c/2*t*t + b;
		return -c/2 * ((--t)*(t-2) - 1) + b;
	},
	//quart
	quartEaseIn : function(t, b, c, d) {
		return c*(t/=d)*t*t*t + b;
	},
	quartEaseOut : function(t, b, c, d) {
		return -c * ((t=t/d-1)*t*t*t - 1) + b;
	},
	quartEaseInOut : 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;
	},
	//quint
	quintEaseIn : function(t, b, c, d) {
		return c*(t/=d)*t*t*t*t + b;
	},
	quintEaseOut : function(t, b, c, d) {
		return c*((t=t/d-1)*t*t*t*t + 1) + b;
	},
	quintEaseInOut : function(t, b, c, d) {
		if ((t/=d/2) < 1) return c/2*t*t*t*t*t + b;
		return c/2*((t-=2)*t*t*t*t + 2) + b;
	},
	//sine
	sineEaseIn : function(t, b, c, d) {
		return -c * Math.cos(t/d * (Math.PI/2)) + c + b;
	},
	sineEaseOut : function(t, b, c, d) {
		return c * Math.sin(t/d * (Math.PI/2)) + b;
	},
	sineEaseInOut : function(t, b, c, d) {
		return -c/2 * (Math.cos(Math.PI*t/d) - 1) + b;
	},
	//regular
	regularEaseIn : function(t, b, c, d) {
		return c * (t /= d) * t + b;
	},
	regularEaseOut : function(t, b, c, d) {
		return -c * (t /= d) * (t - 2) + b;
	},
	regularEaseInOut : function(t, b, c, d) {
		if ((t /= d / 2) < 1) {
			return c / 2 * t * t + b;
		}
		return -c / 2 * ((--t) * (t - 2) - 1) + b;
	},
	//back
	backEaseIn : function(t, b, c, d) {
		var s = 1.70158;
		return c * (t /= d) * t * ((s + 1) * t - s) + b;
	},

	backEaseOut : function(t, b, c, d, a, p) {
		var s = 1.70158;
		return c * ((t = t / d - 1) * t * ((s + 1) * t + s) + 1) + b;
	},

	backEaseInOut : function(t, b, c, d, a, p) {
		var s = 1.70158;
		if ((t /= d / 2) < 1) {
			return c / 2 * (t * t * (((s *= (1.525)) + 1) * t - s)) + b;
		}
		return c / 2 * ((t -= 2) * t * (((s *= (1.525)) + 1) * t + s) + 2) + b;
	},
	//bounce
	bounceEaseOut : function(t, b, c, d) {
		if ((t /= d) < (1 / 2.75)) {
			return c * (7.5625 * t * t) + b;
		} else if (t < (2 / 2.75)) {
			return c * (7.5625 * (t -= (1.5 / 2.75)) * t + 0.75) + b;
		} else if (t < (2.5 / 2.75)) {
			return c * (7.5625 * (t -= (2.25 / 2.75)) * t + 0.9375) + b;
		} else {
			return c * (7.5625 * (t -= (2.625 / 2.75)) * t + 0.984375) + b;
		}
	},

	bounceEaseIn : function(t, b, c, d) {
		return c - Fx.transitions.bounceEaseOut(d - t, 0, c, d) + b;
	},

	bounceEaseInOut : function(t, b, c, d) {
		if (t < d / 2) {
			return Fx.transitions.bounceEaseIn(t * 2, 0, c, d) * 0.5 + b;
		} else
			return Fx.transitions.bounceEaseOut(t * 2 - d, 0, c, d) * 0.5 + c * 0.5 + b;
	},
	//strong
	strongEaseIn : function(t, b, c, d) {
		return c * (t /= d) * t * t * t * t + b;
	},

	strongEaseOut : function(t, b, c, d) {
		return c * ((t = t / d - 1) * t * t * t * t + 1) + b;
	},
	strongEaseInOut : function(t, b, c, d) {
		if ((t /= d / 2) < 1) {
			return c / 2 * t * t * t * t * t + b;
		}
		return c / 2 * ((t -= 2) * t * t * t * t + 2) + b;
	},
	//elastic
	elasticEaseIn : function(t, b, c, d, a, p) {
		if (t == 0)
			return b;
		if ((t /= d) == 1)
			return b + c;
		if (!p)
			p = d * 0.3;
		if (!a || a < Math.abs(c)) {
			a = c;
			var s = p / 4;
		} else {
			var s = p / (2 * Math.PI) * Math.asin(c / a);
		}
		return -(a * Math.pow(2, 10 * (t -= 1)) * Math.sin((t * d - s) * (2 * Math.PI) / p)) + b;
	},
	elasticEaseOut : function(t, b, c, d, a, p) {
		if (t == 0)
			return b;
		if ((t /= d) == 1)
			return b + c;
		if (!p)
			p = d * 0.3;
		if (!a || a < Math.abs(c)) {
			a = c;
			var s = p / 4;
		} else {
			var s = p / (2 * Math.PI) * Math.asin(c / a);
		}
		return (a * Math.pow(2, -10 * t) * Math.sin((t * d - s) * (2 * Math.PI) / p) + c + b);
	},
	elasticEaseInOut : function(t, b, c, d, a, p) {
		if (t == 0) {
			return b;
		}

		if ((t /= d / 2) == 2) {
			return b + c;
		}

		if (!p) {
			var p = d * (0.3 * 1.5);
		}

		if (!a || a < Math.abs(c)) {
			var a = c;
			var s = p / 4;
		} else {
			var s = p / (2 * Math.PI) * Math.asin(c / a);
		}
		if (t < 1) {
			return -0.5 * (a * Math.pow(2, 10 * (t -= 1)) * Math.sin((t * d - s) * (2 * Math.PI) / p)) + b;
		}
		return a * Math.pow(2, -10 * (t -= 1)) * Math.sin((t * d - s) * (2 * Math.PI) / p) * 0.5 + c + b;
	}
}
/**
 * 深度复制对象
 * 
 * @param {Object} obj	复制对象
 * @param {Property} preventName 不需要进行复制的对象属性
 */
Fx.objectClone = function(obj, preventName) {
	if ((typeof obj) == 'object') {
        var res = '[object Array]' === Object.prototype.toString.apply(obj) ? [] : {};
        for (var i in obj) {
            if (i != preventName) res[i] = arguments.callee(obj[i], preventName);
        }
        return res;
    } else if ((typeof obj) == 'function') {
        return (new obj()).constructor;
    }
    return obj;
}

再来一个例子,可以记录动画播放的历史动作:

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd"> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> <title>动画测试</title> <style type="text/css"> button {margin-left:20px;} </style> </head> <body> <button onclick="clearTimer();animate()">开建执行动画</button> <button onclick="clearTimer(); fx.pause();" disabled=true>暂停</button> <button onclick="clearTimer(); fx.play()" disabled=true>继续播放</button> <button onclick="clearTimer(); fx.rePlay();" disabled=true>重新播放</button> <div style="margin-top:10px;">当前动画的执行的百分比:<span id="animatePercent"></span></div> <div id="bar" style="width:10em; height:30%; position:absolute; border:1px solid red; margin-top:50px; left:50px;"></div> <div style="height:2800px;"></div> </body> </html>

预览代码

剩下的就是发挥想像了~~~

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2010-10-14 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档