javascript动画浅析


Posted in Javascript onAugust 30, 2012

动画原理

所谓的动画,就是通过一些列的运动形成的动的画面。在网页中,我们可以通过不断的改变元素的css值,来达到动的效果。

用到的公式

总距离S = 总时间T * 速度V 即: V = S/T
当前距离s = S/T * 已耗时t 即: s = S * (t/T)
即:当前距离 = 总距离 * (已耗时/总时间)
即:动画元素开始值 + (动画元素结束值 - 动画元素开始值) * (当前时间-开始时间) / (动画需要时间) + 值的格式

有了上面这些公式,我们就能利用javascript的setInterval或者setTimeout来做一个简单的动画了。
然而想要做一个动画库,就不得不考虑另外一些因素了。 比如同一个元素的动画,必须要有顺序的执行。不同元素的动画可以同步运行。

如此一来,就必须得用另外一个对象来管理这些动画了。我开始的想法是讲每个元素都放在一个数组里,用几个setInterval来循环取出数组中的动画函数依次执行。

animate1 = [{elem,fn},{elem,fn}];
animate2 = [{elem,fn},{elem,fn}];

这样就能达到,相同的元素动画,是有顺序的执行,而不同的则可以同时运行了。然后这样却存在一个问题,那就是如果超过10个元素的动画。程序就要开十个setInterval。

为了避免这样的情况发生,就在上面的基础上做了一些改进。使得,不论多少个动画。都使用一个setInterval来完成。修改后结构如下。

[ 
[elem,[fn,fn,fn,fn]], 
[elem,[fn,fn,fn,fn]], 
[elem,[fn,fn,fn,fn]] 
]

这样一来,就可以用一个setInterval来完成所有动画了。 所需要做就是,循环取出elem,并执行elem后面一个元素的头一个fn,fn执行完毕后删除fn。调用下一个fn,如果fn全部为空则从大的数组中删除elem,如果elem为空时,则清楚setInterval。这样一来,逻辑上便可以走得通了。

然而动画最关键的因素还有一个,那就是缓动。 如果没有缓动,那么动画效果看起来就非常的死板。千篇一律。目前做js动画用到的缓动算法是很多的,大致分为两类。
一种是flash类,一种是prototype类。

flash的需要四个参数。分别是,
1.时间初始话的时间t
2.动画的初始值b
3.动画的结束值c
4.动画持续的时间d
下面是一个flash 类的匀速运动算法
Linear: function(t,b,c,d){ return c*t/d + b; }
另一种则是prototype,这一类的参数只需要一个,那就是当前时间t与持续时间d的比值 (t/d)
我采用了第二种,因为它的参数方便。也更加适合上面的动画公式,下面是一个prototype类的匀速运动算法
linear : function(t){ return t;}.
加入缓动后上面的公式变为
动画元素开始值 + (动画元素结束值 - 动画元素开始值) * 缓动函数((当前时间-开始时间) / (动画需要时间)) + 值的格式。
至此便是整个动画类设计便结束了。其中参考了一些其它人的博客,在此表示感谢!
最后,还是贴一下详细代码吧。

/** 
* create time 2012/08/29 
* @author lynx cat. 
* @version 0.77beta. 
*/ (function(win,doc){ 
var win = win || window; 
var doc = doc || win.document, 
pow = Math.pow, 
sin = Math.sin, 
PI = Math.PI, 
BACK_CONST = 1.70158; 
var Easing = { 
// 匀速运动 
linear : function(t){ 
return t; 
}, 
easeIn : function (t) { 
return t * t; 
}, 
easeOut : function (t) { 
return ( 2 - t) * t; 
}, 
easeBoth : function (t) { 
return (t *= 2) < 1 ? 
.5 * t * t : 
.5 * (1 - (--t) * (t - 2)); 
}, 
easeInStrong : function (t) { 
return t * t * t * t; 
}, 
easeOutStrong : function (t) { 
return 1 - (--t) * t * t * t; 
}, 
easeBothStrong: function (t) { 
return (t *= 2) < 1 ? 
.5 * t * t * t * t : 
.5 * (2 - (t -= 2) * t * t * t); 
}, 
easeOutQuart : function(t){ 
return -(pow((t-1), 4) -1) 
}, 
easeInOutExpo : function(t){ 
if(t===0) return 0; 
if(t===1) return 1; 
if((t/=0.5) < 1) return 0.5 * pow(2,10 * (t-1)); 
return 0.5 * (-pow(2, -10 * --t) + 2); 
}, 
easeOutExpo : function(t){ 
return (t===1) ? 1 : -pow(2, -10 * t) + 1; 
}, 
swingFrom : function(t) { 
return t*t*((BACK_CONST+1)*t - BACK_CONST); 
}, 
swingTo: function(t) { 
return (t-=1)*t*((BACK_CONST+1)*t + BACK_CONST) + 1; 
}, 
sinusoidal : function(t) { 
return (-Math.cos(t*PI)/2) + 0.5; 
}, 
flicker : function(t) { 
var t = t + (Math.random()-0.5)/5; 
return this.sinusoidal(t < 0 ? 0 : t > 1 ? 1 : t); 
}, 
backIn : function (t) { 
if (t === 1) t -= .001; 
return t * t * ((BACK_CONST + 1) * t - BACK_CONST); 
}, 
backOut : function (t) { 
return (t -= 1) * t * ((BACK_CONST + 1) * t + BACK_CONST) + 1; 
}, 
bounce : function (t) { 
var s = 7.5625, r; 
if (t < (1 / 2.75)) { 
r = s * t * t; 
} 
else if (t < (2 / 2.75)) { 
r = s * (t -= (1.5 / 2.75)) * t + .75; 
} 
else if (t < (2.5 / 2.75)) { 
r = s * (t -= (2.25 / 2.75)) * t + .9375; 
} 
else { 
r = s * (t -= (2.625 / 2.75)) * t + .984375; 
} 
return r; 
} 
}; 
/** 
* 基石 用于返回一个包含对话方法的对象 
* @param elem 
* @return {Object} 
*/ 
function catfx(elem){ 
elem = typeof elem === 'string' ? doc.getElementById(elem) : elem; 
return new fx(elem); 
} 
/** 
* 内部基石 用于返回一个包含对话方法的对象 
* @param elem 
* @return {Object} 
*/ 
function fx(elem){ 
this.elem = elem; 
return this; 
} 
/** 
* 基础类 包含一些基础方法,和不变量 
*/ 
var fxBase = { 
speed : { 
slow : 600, 
fast : 200, 
defaults : 400 
}, 
fxAttrs : [], 
fxMap:[], 
/** 
* 返回对象元素的css值 
* @param elem 
* @param p 
* @return css value 
*/ 
getStyle : function(){ 
var fn = function (){}; 
if('getComputedStyle' in win){ 
fn = function(elem, p){ 
var p = p.replace(/\-(\w)/g,function(i,str){ 
return str.toUpperCase(); 
}); 
var val = getComputedStyle(elem, null)[p]; 
if(~(' '+p+' ').indexOf(' left right top bottom ') && val === 'auto'){ 
val = '0px'; 
} 
return val; 
} 
}else { 
fn = function(elem, p){ 
var p = p.replace(/\-(\w)/g,function(i,str){ 
return str.toUpperCase(); 
}); 
var val = elem.currentStyle[p]; 
if(~(' '+p+' ').indexOf(' width height') && val === 'auto'){ 
var rect = elem.getBoundingClientRect(); 
val = ( p === 'width' ? rect.right - rect.left : rect.bottom - rect.top ) + 'px'; 
} 
if(p === 'opacity'){ 
var filter = elem.currentStyle.filter; 
if( /opacity/.test(filter) ){ 
val = filter.match( /\d+/ )[0] / 100; 
val = (val === 1 || val === 0) ? val.toFixed(0) : val.toFixed(1); 
}else if( val === undefined ){ 
val = 1; 
} 
} 
if(~(' '+p+' ').indexOf(' left right top bottom ') && val === 'auto'){ 
val = '0px'; 
} 
return val; 
} 
} 
return fn; 
}(), 
/** 
* 返回对象元素的css值 
* @param 颜色值(暂不支持red,pink,blue等英文) 
* @return rgb(x,x,x) 
*/ 
getColor : function(val){ 
var r, g, b; 
if(/rgb/.test(val)){ 
var arr = val.match(/\d+/g); 
r = arr[0]; 
g = arr[1]; 
b = arr[2]; 
}else if(/#/.test(val)){ 
var len = val.length; 
if( len === 7 ){ 
r = parseInt( val.slice(1, 3), 16); 
g = parseInt( val.slice(3, 5), 16); 
b = parseInt( val.slice(5), 16); 
} 
else if( len === 4 ){ 
r = parseInt(val.charAt(1) + val.charAt(1), 16); 
g = parseInt(val.charAt(2) + val.charAt(2), 16); 
b = parseInt(val.charAt(3) + val.charAt(3), 16); 
} 
}else{ 
return val; 
} 
return { 
r : parseFloat(r), 
g : parseFloat(g), 
b : parseFloat(b) 
} 
}, 
/** 
* 返回解析后的css 
* @param prop 
* @return {val:val,unit:unit} 
*/ 
parseStyle : function(prop){ 
var val = parseFloat(prop), 
unit = prop.replace(/^[\-\d\.]+/, ''); 
if(isNaN(val)){ 
val = this.getColor(unit); 
unit = ''; 
} 
return {val : val, unit : unit}; 
}, 
/** 
* 设置元素的透明度 
* @param elem 
* @param val 
*/ 
setOpacity : function(elem, val){ 
if( 'getComputedStyle' in win ){ 
elem.style.opacity = val === 1 ? '' : val; 
}else{ 
elem.style.zoom = 1; 
elem.style.filter = val === 1 ? '' : 'alpha(opacity=' + val * 100 + ')'; 
} 
}, 
/** 
* 设置元素的css值 
* @param elem 
* @param prop 
* @param val 
*/ 
setStyle : function(elem, prop, val){ 
if(prop != 'opacity'){ 
prop = prop.replace(/\-(\w)/g,function(i,p){ 
return p.toUpperCase(); 
}); 
elem.style[prop] = val; 
}else{ 
this.setOpacity(elem, val); 
} 
}, 
/** 
* 返回解析后的prop 
* @param prop 
* @return {prop} 
*/ 
parseProp : function(prop){ 
var props = {}; 
for(var i in prop){ 
props[i] = this.parseStyle(prop[i].toString()); 
} 
return props; 
}, 
/** 
* 修正用户的参数 
* @param elem 
* @param duration 
* @param easing 
* @param callback 
* @return {options} 
*/ 
setOption : function(elem,duration, easing, callback){ 
var options = {}; 
var _this = this; 
options.duration = function(duration){ 
if(typeof duration == 'number'){ 
return duration; 
}else if(typeof duration == 'string' && _this.speed[duration]){ 
return _this.speed[duration]; 
}else{ 
return _this.speed.defaults; 
} 
}(duration); 
options.easing = function(easing){ 
if(typeof easing == 'function'){ 
return easing; 
}else if(typeof easing == 'string' && Easing[easing]){ 
return Easing[easing]; 
}else{ 
return Easing.linear; 
} 
}(easing); 
options.callback = function(callback){ 
var _this = this; 
return function (){ 
if(typeof callback == 'function'){ 
callback.call(elem); 
} 
} 
}(callback) 
return options; 
}, 
/** 
* 维护setInterval的函数,动画的启动 
*/ 
tick : function(){ 
var _this = this; 
if(!_this.timer){ 
_this.timer = setInterval(function(){ 
for(var i = 0, len = _this.fxMap.length; i < len; i++){ 
var elem = _this.fxMap[i][0]; 
var core = _this.data(elem)[0]; 
core(elem); 
} 
},16); 
} 
}, 
/** 
* 停止所有动画 
*/ 
stop : function(){ 
if(this.timer){ 
clearInterval(this.timer); 
this.timer = undefined; 
} 
}, 
/** 
* 存储或者拿出队列 
* @param elem 
*/ 
data : function(elem){ 
for(var i = 0, len = this.fxMap.length; i < len; i++){ 
var data = this.fxMap[i]; 
if(elem === data[0]){ 
return data[1]; 
} 
} 
this.fxMap.push([elem,[]]); 
return this.fxMap[this.fxMap.length - 1][1]; 
}, 
/** 
* 删除队列 
* @param elem 
*/ 
removeData : function(elem){ 
for(var i = 0, len = this.fxMap.length; i < len; i++){ 
var data = this.fxMap[i]; 
if(elem === data[0]){ 
this.fxMap.splice(i, 1); 
if(this.isDataEmpty()){ 
this.stop(); 
} 
} 
} 
}, 
isDataEmpty : function(){ 
return this.fxMap.length == 0; 
} 
}, $ = fxBase; 
/** 
* 核心对象,用于生成动画对象。 
* @param elem 
* @param props 
* @param options 
* @return {Object} 
*/ 
function fxCore(elem, props, options){ 
this.elem = elem; 
this.props = props; 
this.options = options; 
this.start(); 
} 
fxCore.prototype = { 
constructor : fxCore, 
/** 
* 将动画函数加入到队列中,并启动动画。 
*/ 
start : function(){ 
var cores = $.data(this.elem); 
cores.push(this.step()); 
$.tick(); 
}, 
/** 
* 核心方法,控制每一帧元素的状态。 
* @return function 
*/ 
step : function(){ 
var _this = this; 
var fn = function(elem){ 
var t = Date.now() - this.startTime; 
if(Date.now() < this.startTime + this.options.duration){ 
if(t <= 1){ t = 1;} 
for(var i in this.target){ 
if(typeof this.source[i]['val'] === 'number'){ 
var val = parseFloat((this.source[i]['val'] + (this.target[i]['val'] - this.source[i]['val']) * this.options.easing(t / this.options.duration)).toFixed(7)); 
}else{ 
var r = parseInt(this.source[i]['val']['r'] + (this.target[i]['val']['r'] - this.source[i]['val']['r']) * this.options.easing(t / this.options.duration)); 
var g = parseInt(this.source[i]['val']['g'] + (this.target[i]['val']['g'] - this.source[i]['val']['g']) * this.options.easing(t / this.options.duration)); 
var b = parseInt(this.source[i]['val']['b'] + (this.target[i]['val']['b'] - this.source[i]['val']['b']) * this.options.easing(t / this.options.duration)); 
var val = 'rgb(' + r + ',' + g + ',' + b + ')'; 
} 
$.setStyle(this.elem,i,val + this.source[i]['unit']); 
} 
}else{ 
for(var i in this.target){ 
if(typeof this.target[i]['val'] === 'number'){ 
var val = this.target[i]['val']; 
}else{ 
var val = 'rgb(' + this.target[i]['val']['r'] + ',' + this.target[i]['val']['g'] + ',' + this.target[i]['val']['b'] + ')'; 
} 
$.setStyle(elem,i,val + this.source[i]['unit']); 
} 
var cores = $.data(elem); 
cores.shift(); 
this.options.callback(); 
if(cores.length == 0){ 
$.setStyle(elem,'overflow',this.overflow); 
$.removeData(elem); 
} 
} 
} 
return function(elem){ 
if(!_this.startTime){ 
var source = {}; 
_this.target = _this.props; 
for(var i in _this.props){ 
var val = $.getStyle(_this.elem, i); 
source[i] = $.parseStyle(val); 
} 
_this.source = source; 
_this.startTime = Date.now(); 
_this.overflow = $.getStyle(elem,'overflow'); 
$.setStyle(elem,'overflow','hidden'); 
} 
fn.call(_this,elem); 
} 
} 
} 
/** 
* 外部接口类。 
*/ 
fx.prototype = { 
constructor : fx, 
/** 
* 动画方法 
* @param prop 
* @param duration 
* @param easing 
* @param callback 
* @return {Object} 
*/ 
animate : function(prop, duration, easing, callback){ 
if(arguments.length == 3 && typeof easing === 'function'){ //多数时候用户第三个参数是回调 
callback = easing; 
easing = undefined; 
} 
var props = $.parseProp(prop); 
var options = $.setOption(this.elem,duration,easing,callback); 
var core = new fxCore(this.elem,props,options); 
return this; 
}, 
/** 
* 停止动画方法 
* 使用方法 catjs('your element id').stop(); 
*/ 
stop : function(){ 
$.removeData(this.elem); 
} 
} 
win.catfx = catfx; 
})(this,document);

使用起来也比较简单.直接catfx('ID').animate({'margin-left':200,'background-color':'#ff0000'},600,'easeOut',function(){});

跟jquery的使用方法差不多,如果不传第二个参数,则默认为400毫秒。不传第三个参数则默认匀速。第三个参数为函数,并且总共只有三个参数时。第三个参数为回调。

例:catfx('ID').animate({'margin-left':200,'background-color':'#ff0000'},600,function(){alert('洒家是回调函数~')});

Javascript 相关文章推荐
HTML中Select不用Disabled实现ReadOnly的效果
Apr 07 Javascript
Jquery乱码的一次解决过程 图解教程
Feb 20 Javascript
js中各浏览器中鼠标按键值的差异
Apr 07 Javascript
js字符串转成JSON
Nov 07 Javascript
jquery.cookie() 方法的使用(读取、写入、删除)
Dec 05 Javascript
JS 弹出层 定位至屏幕居中示例
May 21 Javascript
ArtEditor富文本编辑器增加表单提交功能
Apr 18 Javascript
jquery实现弹窗功能(窗口居中显示)
Feb 27 Javascript
vue router路由嵌套不显示问题的解决方法
Jun 17 Javascript
lhgcalendar时间插件限制只能选择三个月的实现方法
Jul 03 Javascript
JavaScript中Object值合并方法详解
Dec 22 Javascript
vue + el-form 实现的多层循环表单验证
Nov 25 Vue.js
jquery方法+js一般方法+js面向对象方法实现拖拽效果
Aug 30 #Javascript
JS跨域代码片段
Aug 30 #Javascript
JS跨域总结
Aug 30 #Javascript
js中判断Object、Array、Function等引用类型对象是否相等
Aug 29 #Javascript
xml转json的js代码
Aug 28 #Javascript
基于jquery创建的一个图片、视频缓冲的效果样式插件
Aug 28 #Javascript
javascript 判断中文字符长度的函数代码
Aug 27 #Javascript
You might like
ajax在joomla中的原生态应用代码
2012/07/19 PHP
php检查字符串中是否包含7位GSM字符的方法
2015/03/17 PHP
PHP版微信公众平台红包API
2015/04/02 PHP
PHP图像裁剪缩略裁切类源码及使用方法
2016/01/07 PHP
PHP上传图片、删除图片简单实例
2016/11/12 PHP
javascript加号&quot;+&quot;的二义性说明
2013/03/04 Javascript
动态标签 悬停效果 延迟加载示例代码
2013/11/21 Javascript
jQuery使用ajaxSubmit()提交表单示例
2014/04/04 Javascript
使用jquery获取url及url参数的简单实例
2016/06/14 Javascript
JS仿百度自动下拉框模糊匹配提示
2016/07/25 Javascript
详解Vue学习笔记进阶篇之列表过渡及其他
2017/07/17 Javascript
js封装成插件的步骤方法
2017/09/11 Javascript
vue.js 微信支付前端代码分享
2018/02/10 Javascript
微信小程序scroll-view实现字幕滚动
2018/07/14 Javascript
Node.js Koa2使用JWT进行鉴权的方法示例
2018/08/17 Javascript
Vue SSR 即时编译技术的实现
2020/05/06 Javascript
python操作xml文件示例
2014/04/07 Python
Python实现过滤单个Android程序日志脚本分享
2015/01/16 Python
python3+selenium实现126邮箱登陆并发送邮件功能
2019/01/23 Python
详解python列表(list)的使用技巧及高级操作
2019/08/15 Python
python针对mysql数据库的连接、查询、更新、删除操作示例
2019/09/11 Python
python 进程间数据共享multiProcess.Manger实现解析
2019/09/23 Python
Python爬虫抓取指定网页图片代码实例
2020/07/24 Python
荷兰包包购物网站:The Little Green Bag
2018/03/17 全球购物
房地产销售员的自我评价分享
2013/12/04 职场文书
体育教师自荐信范文
2013/12/16 职场文书
优秀民警事迹材料
2014/01/29 职场文书
省优秀教师事迹材料
2014/01/30 职场文书
中班中秋节活动反思
2014/02/18 职场文书
爱心活动计划书
2014/04/26 职场文书
责任担保书范文
2014/05/21 职场文书
乡镇党员群众路线教育实践活动对照检查材料思想汇报
2014/10/05 职场文书
十八大宣传标语
2014/10/09 职场文书
中班下学期个人总结
2015/02/12 职场文书
2015年公务员工作总结
2015/04/24 职场文书
JavaScript 对象创建的3种方法
2021/11/17 Javascript