zoukankan      html  css  js  c++  java
  • JavaScript的动画组件

    image

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

    运行示例代码:

    动画组件的核心代码:

    /**
     * @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;
    }

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

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

  • 相关阅读:
    [网络流24题] 深海机器人问题
    [网络流24题] 数字梯形问题
    处理银行卡号的格式
    每天十点的倒计时
    HTML meta标签总结与属性使用介绍
    禁止的一些操作
    input输入大于0的小数和整数
    cf 1037D BFS
    cf 1051F 树+图
    cf 911F 树的直径+贪心
  • 原文地址:https://www.cnblogs.com/meteoric_cry/p/1851221.html
Copyright © 2011-2022 走看看